decorator 装饰器
许多面向对象都有decorator(装饰器)函数,比如python中也可以用decorator函数来强化代码,decorator相当于一个高阶函数,接收一个函数,返回一个被装饰后的函数。
注: javascript中也有decorator
相关的提案,只是目前node以及各浏览器中均不支持。只能通过安装babel插件来转换代码,插件名叫这个:transform-decorators-legacy
。也有在线试用](),安装好transform-decorators-legacy
之后,就能看到转义后的代码了
2.1 使用decorator的前期配置
1.vscode里面去除装饰器报错的方法
在vscode里打开设置=>用户设置里面加入(tips:打开设置后也可以直接点击右上角的'{}'进行用户设置)
"javascript.implicitProjectConfig.experimentalDecorators": true复制代码
就可以了;
2.搭建一个简单的webpack 来使用装饰器
由于目前浏览器和node暂时不支持装饰器,所以我们可以配置一个webpack来使用装饰器
全局安装
cnpm install webpack webpack-cli webpack-dev-server -g复制代码
启动配置 创建一个webpack.dev.js
var path = require('path')module.exports = { mode: "development", entry: { main: "./test.js" }, output: { path: path.resolve(__dirname, "./dist"), filename: "test.js" }, module: { rules: [ //webpack 4.X写法 { test: /.js$/, use: ['babel-loader'] } ] }}复制代码
下载依赖(webpack4.x 方法 )
npm install -D babel-loader @babel/core @babel/preset-env复制代码
配置.babelrc
{ "presets": [ "@babel/preset-env" ], "plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ], ]}复制代码
创建好webpack的目录结构是这样的
package.json的配置
{ "name": "decorator", "version": "1.0.0", "description": "", "main": "test.js", "scripts": { "build": "webpack --config=webpack.dev.js", "start": "node ./dist/bound.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@babel/core": "^7.4.4", "@babel/plugin-proposal-class-properties": "^7.4.4", "@babel/plugin-proposal-decorators": "^7.4.4", "@babel/preset-env": "^7.4.4", "babel-loader": "^8.0.5", "babel-plugin-transform-decorators-legacy": "^1.3.5", "babel-preset-env": "^1.7.0", "core-decorators": "^0.20.0" }}复制代码
2.2 开始使用decorator
1.类的修饰
许多面向对象的语言都有修饰器(Decorator)函数,用来修改类的行为。目前,有一个将这项功能,引入了 ECMAScript。 下面我们采用一个钢铁侠的例子来展开
@transformclass IronMan { // ...}function transform(target) { target.weapon = laser}console.log(IronMan.weapon) // laser复制代码
上面代码中,@transform就是一个修饰器。它修改了IronMan
这个类的行为,为它加上了武器属性weapon
。transform
函数的参数target
是IronMan
类本身。
2.结合redux库使用
实际开发中,React 与 Redux 库结合使用时,常常需要写成下面这样。
class MyReactComponent extends React.Component {}export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);复制代码
有了装饰器,就可以改写上面的代码。
@connect(mapStateToProps, mapDispatchToProps)export default class MyReactComponent extends React.Component {}复制代码
3.方法的修饰
修饰器不仅可以修饰类,还可以修饰类的属性。
class Person { @readonly name() { return `${ this.first} ${ this.last}` }}复制代码
上面代码中,修饰器readonly
用来修饰“类”的name
方法。
修饰器函数readonly
一共可以接受三个参数。
function readonly(target, name, descriptor){ // descriptor对象原来的值如下 // { // value: specifiedFunction, // enumerable: false, // configurable: true, // writable: true // }; descriptor.writable = false; return descriptor;}readonly(Person.prototype, 'name', descriptor);// 类似于Object.defineProperty(Person.prototype, 'name', descriptor);复制代码
修饰器第一个参数是类的原型对象,上例是Person.prototype
,修饰器的本意是要“修饰”类的实例,但是这个时候实例还没生成,所以只能去修饰原型(这不同于类的修饰,那种情况时target
参数指的是类本身);第二个参数是所要修饰的属性名,第三个参数是该属性的描述对象。
4.装饰器不能用于修饰函数
原本作者设计的时候 是可以使用这种方式的 比如修饰一个函数这么写
@RunOncefunction expensiveOperation() { return 1 + 1;}//语法糖之后的效果是这样的var expensiveOperation = RunOnce(function expensiveOperation() { return 1 + 1;});复制代码
这意味着装饰器可以用于任何任务,但是作者认为这样可能有点复杂 并且还有一个潜在的问题 装饰器和跟随变量一块提升 使得装饰器语法函数过早执行而导致因为位置的原因产生的一些错误 比如:
var readOnly = require("some-decorator");// 是否提升求值?// 如果是这样的话 `readOnly` 那么就是未定义 @readOnlyfunction foo() {}复制代码
总而言之,作者不希望产生这样的复杂性,所以去除了修饰函数,详情可以参考这篇作者参与讨论的
5.应用
至于decorator的应用场景在哪里?应该大部分AOP的场景都可以用,例如日志系统。 这里就手动来实现一个简单的日志系统。
const log = (type) => { return (target, name, descriptor) => { let method = descriptor.value ; // 具体三个方法 descriptor.value = (...args) => { console.log(`${type}`); let result ; try { result = method.apply(target,args); console.log(`${type} ${result} 成功`) } catch (error) { console.log(`${type} ${result} 失败`) } return result ; } }}class Man { @log('正在洗漱') wash() { return "洗漱"; } @log('正在吃饭') eat() { return "吃饭"; } @log('正在跑步') run() { return "跑步"; }}let m = new Man() ;m.wash();m.eat();m.run();复制代码
6.core-decorators.js
是一个第三方模块,提供了几个常见的修饰器,通过它可以更好地理解修饰器。
(1)@readonly
readonly
修饰器使得属性或方法不可写。
import { readonly } from 'core-decorators';class Meal { @readonly entree (){ console.log(111) };}var dinner = new Meal();dinner.entree = 'salmon';// Cannot assign to read only property 'entree' of [object Object]复制代码
(2)@override
override
修饰器检查子类的方法,是否正确覆盖了父类的同名方法,如果不正确会报错。
import { override } from 'core-decorators';class Parent { speak(first, second) {}}class Child extends Parent { @override speak() {} // SyntaxError: Child#speak() does not properly override Parent#speak(first, second)}// orclass Child extends Parent { @override speaks() {} // SyntaxError: No descriptor matching Child#speaks() was found on the prototype chain. // // Did you mean "speak"?}复制代码
(3)@deprecate (别名@deprecated)
deprecate
或deprecated
修饰器在控制台显示一条警告,表示该方法将废除。
import { deprecate } from 'core-decorators';class Person { @deprecate facepalm() {} @deprecate('We stopped facepalming') facepalmHard() {} @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' }) facepalmHarder() {}}let person = new Person();person.facepalm();// DEPRECATION Person#facepalm: This function will be removed in future versions.person.facepalmHard();// DEPRECATION Person#facepalmHard: We stopped facepalmingperson.facepalmHarder();// DEPRECATION Person#facepalmHarder: We stopped facepalming//// See http://knowyourmeme.com/memes/facepalm for more details.//复制代码
(4)@suppressWarnings
suppressWarnings
修饰器抑制deprecated
修饰器导致的console.warn()
调用。但是,异步代码发出的调用除外。
import { suppressWarnings } from 'core-decorators';class Person { @deprecated facepalm() {} @suppressWarnings facepalmWithoutWarning() { this.facepalm(); }}let person = new Person();person.facepalmWithoutWarning();// no warning is logged复制代码
7.Mixin
在修饰器的基础上,可以实现Mixin
模式。所谓Mixin
模式,就是对象继承的一种替代方案,中文译为“混入”(mix in),意为在一个对象之中混入另外一个对象的方法。
const Foo = { foo() { console.log('foo') }};class MyClass {}Object.assign(MyClass.prototype, Foo);let obj = new MyClass();obj.foo() // 'foo'复制代码
可以使用装饰器改写为
function mixins(...list) { return function (target) { Object.assign(target.prototype, ...list); };}const Foo = { foo() { console.log('foo') }};@mixins(Foo)class MyClass {}let obj = new MyClass();obj.foo() // "foo"复制代码
这中方法会改写Myclass的 prototype,所以也可以使用继承的方法混入
let MyMixin = (superclass) => class extends superclass { foo() { console.log('foo from MyMixin'); }};class MyClass extends MyMixin(MyBaseClass) { /* ... */}let c = new MyClass();c.foo(); // "foo from MyMixin"复制代码