修饰器
类的修饰
修饰器是一个函数,可以改变类的行为,如添加静态属性,写法如下:
function dec(){
}
@dec()
class MyClass{
}
⚠ 注意
修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器本质就是编译时执行的函数。
修饰器函数的第一个参数就是所要修饰的目标类,如果一个参数不够用,可以在修饰器外面再封装一层函数。
function dec(id){
return function(target){
target.id = id;
}
}
@dec('hello')
class HelloClass{
}
@dec('hi')
class HiClass{
}
HelloClass.id // hello
HiClass.id // hi
如果想给类添加实例属性,可以在修饰器函数中,修改类的 prototype
对象。
function dec(target){
target.prototype.name = 'hello';
}
@dec
class MyClass{
}
let obj = new MyClass();
obj.name // hello
方法的修饰
修饰器不仅可以修饰类,还可以修饰类的属性。
class Person{
@readonly
name(){
return `${this.first} ${this.last}`;
}
}
此时,函数修饰器 readonly
接受3个参数:
target
:要修饰的目标对象name
:所要修饰的属性名descriptor
:该属性的描述对象,原来的值如下:js{ value:specifiedFunction, enumerable:false, configurable:true, writable:true }
function readonly(target,name,descriptor){
descriptor.writable = false;
return descriptor;
}
上方代码可以看出,修饰器会修改属性的描述对象,然后被修改的描述对象可以再用来定义属性。
⚠ 注意
修饰器取名最好有语义化,可以在阅读时起注释的作用,知道是什么意思。
@testable
class P {
@readonly
@nonenumerable
method() { }
}
上方代码中,表明类 P
时可测试的,方法 method
是只读且不可遍历的。
如果有有多个修饰器,会从外到内进入修饰器函数,然后从内到外执行。
function dec(id){
console.log('evaluated', id);
return function(target,name,descriptor){
target.id = id;
}
console.log('executed', id);
}
class MyClass{
@dec('hello')
@dec('hi')
name(){
return `${this.first} ${this.last}`;
}
}
// evaluated, hello
// evaluated, hi
// executed, hi
// executed, hello
为什么修饰器不能用于函数
修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。
var num = 0
var add = function(){
num++
}
@add
function foo() {}
@add
function foo() {}
var add
var num
num = 0
add = function(){
num++
}
如果一定要修饰函数,可以采用高阶函数的形式直接执行。
function doSomething(name){
console.log('Hello, '+name);
}
function loggingDecorator(wrapped){
return function(){
console.log('Starting '+wrapped.name);
var result = wrapped.apply(this,arguments);
console.log('Finished '+wrapped.name);
return result;
}
}
core-decorators.js
core-decorators.js
是一个第三方模块,提供了几个常见的修饰器,通过它可以更好地理解修饰器。
@autobind
:绑定类中定义的方法到实例上。jsimport { autobind } from 'core-decorators'; class Person { @autobind greet() { console.log(`Hello, my name is ${this.name}.`); } } let person = new Person(); let { greet } = person; greet(); // Hello, my name is undefined.
@readonly
:禁止修改一个属性。jsimport { readonly } from 'core-decorators'; class Person { @readonly name = 'John'; } let person = new Person(); person.name = 'Mike'; // error!
@override
:检查子类的方法,是否正确覆盖了父类的同名方法。jsimport { override } from 'core-decorators'; class Parent { greet() { console.log('Hello from Parent.'); } } class Child extends Parent { @override greet() { console.log('Hello from Child.'); } } let child = new Child(); child.greet(); // Hello from Child.
@deprecate
:在控制台显示一条警告,表示该方法将废除。jsimport { deprecate } from 'core-decorators'; class Person { @deprecate facepalm() { } @deprecate('we now use punch() instead') punch() { } }
@suppressWarnings
:禁止某个修饰器显示警告。jsimport { suppressWarnings } from 'core-decorators'; @suppressWarnings class Person { @deprecate facepalm() { } @deprecate('we now use punch() instead') punch() { } }
Babel转码器支持
目前,Babel 转码器已经支持 Decorator。
首先,安装 babel-core
和 babel-plugin-transform-decorators
。
然后配置文件 .babelrc
。
{
"plugins":["transform-decorators"]
}
脚本中打开的命令如下:
babel.transform('code', {
plugins: ['transform-decorators']
})
勾选 Experimental
就能支持在线转码。
总结
修饰器是 JavaScript 的一种特殊语法,用于在编译时修改类或类成员的行为。它可以添加静态属性、改变方法的特性等。修饰器本身是一个函数,当用在类或类成员上时会在代码编译阶段执行。在使用修饰器时,如果要修饰类的实例属性,需要修改类的 prototype
对象。修饰器可以叠加使用,按照从外到内的顺序依次调用。
由于修饰器是基于 ES7 的一个提案,目前需要通过 Babel 等工具转码后才能在现有的 JavaScript 环境中使用。core-decorators.js
是一个第三方库,提供了一些常用的修饰器,如 @autobind
、@readonly
等,方便开发者使用。
修饰器不能用于普通函数,因为 JavaScript 中的函数提升特性会导致修饰器无法正确应用。如果需要修饰函数,可以使用高阶函数的方式。