修饰器(Decorator)是一个函数,用来修改类的行为。修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码
类的修饰
//修饰器函数1:为类加上静态属性isTestable
//修饰器函数的第一个参数,就是所要修饰的目标类
function testable(target) {
target.isTestable = true;
}
//修饰类的行为。写在类上方,表示修饰器的目标是这整个类
@testable
class MyTestableClass {}
console.log(MyTestableClass.isTestable) // true
//修饰器函数2:添加实例属性
function testable(target) {
target.prototype.isTestable = true;
}
@testable
class MyTestableClass {}
let obj = new MyTestableClass();
obj.isTestable // true
修饰器的实际行为
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
类的方法的修饰
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
修饰器的参数
修饰器函数一共可以接受三个参数:
- 所要修饰的目标对象
- 所要修饰的属性名
- 该属性的描述对象
function nonenumerable(target, name, descriptor) {
descriptor.enumerable = false; //修改了描述对象的属性enumerable,使得不可遍历
return descriptor;
}
class Person {
@nonenumerable //修饰该属性
get kidCount() { return this.children.length; }
}
@log修饰器例子:输出日志
function log(target, name, descriptor) {
var oldValue = descriptor.value;
descriptor.value = function() {
console.log(`Calling "${name}" with`, arguments);
return oldValue.apply(null, arguments);
};
return descriptor;
}
class Math {
@log
add(a, b) {
return a + b;
}
}
const math = new Math();
// passed parameters should get logged now
math.add(2, 4);
修饰器有注释作用
@testable
class Person {
@readonly
@nonenumerable
name() { return `${this.first} ${this.last}` }
}
//可看出Person类是可测试的,而name方法是只读和不可枚举的
core-decorators.js
core-decorators.js是一个第三方模块,提供了几个常见的修饰器,通过它可以更好地理解修饰器
-
@autobind:使得方法中的
this
对象,绑定原始对象import { autobind } from 'core-decorators'; class Person { @autobind getPerson() { return this; } } let person = new Person(); let getPerson = person.getPerson; getPerson() === person; // true
-
@readonly:使得属性或方法不可写
import { readonly } from 'core-decorators'; class Meal { @readonly entree = 'steak'; } var dinner = new Meal(); dinner.entree = 'salmon'; // Cannot assign to read only property 'entree' of [object Object]
-
@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) }
-
@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 facepalming person.facepalmHarder(); // DEPRECATION Person#facepalmHarder: We stopped facepalming // // See http://knowyourmeme.com/memes/facepalm for more details. //
Mixin
在修饰器的基础上,可以实现Mixin
模式。所谓Mixin
模式,就是对象继承的一种替代方案,中文译为“混入”(mixin),意为在一个对象之中混入另外一个对象的方法。
以往写法:
//一个对象,里面有个foo方法
const Foo = {
foo() { console.log('foo') }
};
//一个类
class MyClass {}
//通过Object.assign在类的原型对象上添加Foo对象上的方法添加进去,
Object.assign(MyClass.prototype, Foo);
let obj = new MyClass();
obj.foo() // 'foo'
现在:部署一个通用脚本mixins.js
,将mixin写成一个修饰器
export function mixins(...list) {
return function (target) {
Object.assign(target.prototype, ...list);
};
}
//原理:传入参数(这里是对象类型),然后将该参数中的方法添加在类的原型对象中
使用
import { mixins } from './mixins';
const Foo = {
foo() { console.log('foo') }
};
@mixins(Foo) //传入Foo对象,把Foo对象中foo方法的添加在MyClass的原型对象中
class MyClass {}
let obj = new MyClass();
obj.foo() // "foo"
Trait修饰器
效果与Mixin类似,但是提供更多功能,比如防止同名方法的冲突、排除混入某些方法、为混入的方法起别名等等
下面采用traits-decorator这个第三方模块作为例子。这个模块提供的traits修饰器,不仅可以接受对象,还可以接受ES6类作为参数
import { traits } from 'traits-decorator';
//TFoo类中有foo方法
class TFoo {
foo() { console.log('foo') }
}
//TBar对象中有bar方法
const TBar = {
bar() { console.log('bar') }
};
//把TFoo类中有foo方法和TBar对象中有bar方法加进MyClass类的原型对象中
@traits(TFoo, TBar)
class MyClass { }
let obj = new MyClass();
obj.foo() // foo
obj.bar() // bar
另外,Trait不允许“混入”同名方法
Babel转码器的支持
目前,Babel转码器已经支持Decorator。
首先,安装babel-core
和babel-plugin-transform-decorators
。由于后者包括在babel-preset-stage-0
之中,所以改为安装babel-preset-stage-0
亦可
$ npm install babel-core babel-plugin-transform-decorators
然后,设置配置文件.babelrc
{
"plugins": ["transform-decorators"]
}
这时,Babel就可以对Decorator转码了
脚本中打开的命令如下
babel.transform("code", {plugins: ["transform-decorators"]})