ES^ Decoretors

类的修饰

许多面向对象的语言都有修饰器( Decorator )函数,用来修改类的行为。

@testable
class MyTestableClass {
    // ...
}

function testable(target) {
    target.isTestable = true
}

MyTestableClass.isTestable;         // true

testable 函数即为修饰器,为 MyTestableClass 类添加了名为 isTestable 的静态属性。

一个修饰器可以理解为包装之后跟着的类的函数。

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A

也就是说,修饰器是一个对类进行处理的函数。修饰器函数的第一个参数,就是所要修饰的目标类。

当然只要在 @ 符号后是一个函数即可,因此为了可以生成更加灵活的修饰器,我们可以通过函数返回函数的形式来编写修饰器。

function testable(isTestable) {
    return function(target) {
        target.isTestable = isTestable;
    }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable;         // true

@testable(false)
class MyClass {}
MyClass.isTestable;                 // false

testable 函数最终返回一个函数,符合修饰器的形式,同时通过传入不同的参数,就可以定制在不同情况下的修饰器。

方法的修饰

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

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

上述代码我们为 name 方法添加了一个修饰器 readonlyreadonly 同样也是一个函数,如下

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);

相比于类修饰器,方法修饰器多了两个参数,分别为:

  • name: 修饰器修饰的属性名。
  • descriptor: 该属性名对应属性的描述对象。

举个例子:使用 @log 修饰器,达成输出日志的目的

function log(target, name, descriptor) {
    var oldValue = descriptor.value;
    
    descriptor.value = function() {
        console.log(`Calling ${name} with`, arguments);
        return oldValue.apply(this, arguments);
    }
    
    return descriptor;
}

class Math {
    @log
    add(a, b) {
        return a + b;
    }
}

const math = new Math();

math.add(2, 4);
// Calling add with [2, 4];
// 6

这样就达成了在执行方法的时候,输出一条日志的目的,当然修饰器能做的事情可以有更多。

一些可以使用的修饰器的类库: