跳转到内容

修饰器

类的修饰

修饰器是一个函数,可以改变类的行为,如添加静态属性,写法如下:

javascript
function dec(){
}

@dec()
class MyClass{
}

⚠ 注意

修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器本质就是编译时执行的函数。

修饰器函数的第一个参数就是所要修饰的目标类,如果一个参数不够用,可以在修饰器外面再封装一层函数。

javascript
function dec(id){
  return function(target){
    target.id = id;
  }
}

@dec('hello')
class HelloClass{
}

@dec('hi')
class HiClass{
}

HelloClass.id // hello
HiClass.id // hi

如果想给类添加实例属性,可以在修饰器函数中,修改类的 prototype 对象。

javascript
function dec(target){
  target.prototype.name = 'hello';
}

@dec
class MyClass{
}

let obj = new MyClass();
obj.name // hello

方法的修饰

修饰器不仅可以修饰类,还可以修饰类的属性。

javascript
class Person{
  @readonly
  name(){
    return `${this.first} ${this.last}`;
  }
}

此时,函数修饰器 readonly 接受3个参数:

  • target:要修饰的目标对象
  • name:所要修饰的属性名
  • descriptor:该属性的描述对象,原来的值如下:
    js
      {
        value:specifiedFunction,
        enumerable:false,
        configurable:true,
        writable:true
      }
javascript
function readonly(target,name,descriptor){
  descriptor.writable = false;
  return descriptor;
}

上方代码可以看出,修饰器会修改属性的描述对象,然后被修改的描述对象可以再用来定义属性。

⚠ 注意

修饰器取名最好有语义化,可以在阅读时起注释的作用,知道是什么意思。

js
@testable
class P {
   @readonly
   @nonenumerable
   method() { }
}

上方代码中,表明类 P 时可测试的,方法 method 是只读且不可遍历的。

如果有有多个修饰器,会从外到内进入修饰器函数,然后从内到外执行。

javascript
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

为什么修饰器不能用于函数

修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。

javascript
var num = 0

var add = function(){
  num++
}

@add
function foo() {}
javascript
@add
function foo() {}

var add
var num

num = 0
add = function(){
  num++
}

如果一定要修饰函数,可以采用高阶函数的形式直接执行。

js
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:绑定类中定义的方法到实例上。

    js
    import { 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:禁止修改一个属性。

    js
    import { readonly } from 'core-decorators';
    
    class Person {
      @readonly
      name = 'John';
    }
    
    let person = new Person();
    person.name = 'Mike'; // error!
  • @override:检查子类的方法,是否正确覆盖了父类的同名方法。

    js
    import { 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:在控制台显示一条警告,表示该方法将废除。

    js
    import { deprecate } from 'core-decorators';
    
    class Person {
      @deprecate
      facepalm() {
      }
    
      @deprecate('we now use punch() instead')
      punch() {
      }
    }
  • @suppressWarnings:禁止某个修饰器显示警告。

    js
    import { suppressWarnings } from 'core-decorators';
    
    @suppressWarnings
    class Person {
      @deprecate
      facepalm() {
      }
    
      @deprecate('we now use punch() instead')
      punch() {
      }
    }

Babel转码器支持

目前,Babel 转码器已经支持 Decorator。

首先,安装 babel-corebabel-plugin-transform-decorators

然后配置文件 .babelrc

json
{
  "plugins":["transform-decorators"]
}

脚本中打开的命令如下:

js
babel.transform('code', {
  plugins: ['transform-decorators']
})

勾选 Experimental 就能支持在线转码。

总结

修饰器是 JavaScript 的一种特殊语法,用于在编译时修改类或类成员的行为。它可以添加静态属性、改变方法的特性等。修饰器本身是一个函数,当用在类或类成员上时会在代码编译阶段执行。在使用修饰器时,如果要修饰类的实例属性,需要修改类的 prototype 对象。修饰器可以叠加使用,按照从外到内的顺序依次调用。

由于修饰器是基于 ES7 的一个提案,目前需要通过 Babel 等工具转码后才能在现有的 JavaScript 环境中使用。core-decorators.js 是一个第三方库,提供了一些常用的修饰器,如 @autobind@readonly 等,方便开发者使用。

修饰器不能用于普通函数,因为 JavaScript 中的函数提升特性会导致修饰器无法正确应用。如果需要修饰函数,可以使用高阶函数的方式。