方法的装饰

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

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

上面代码中,装饰器readonly用来装饰“类”的name方法。

装饰器函数readonly一共可以接受三个参数。

  1. function readonly(target, name, descriptor){
  2. // descriptor对象原来的值如下
  3. // {
  4. // value: specifiedFunction,
  5. // enumerable: false,
  6. // configurable: true,
  7. // writable: true
  8. // };
  9. descriptor.writable = false;
  10. return descriptor;
  11. }
  12. readonly(Person.prototype, 'name', descriptor);
  13. // 类似于
  14. Object.defineProperty(Person.prototype, 'name', descriptor);

装饰器第一个参数是类的原型对象,上例是Person.prototype,装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型(这不同于类的装饰,那种情况时target参数指的是类本身);第二个参数是所要装饰的属性名,第三个参数是该属性的描述对象。

另外,上面代码说明,装饰器(readonly)会修改属性的描述对象(descriptor),然后被修改的描述对象再用来定义属性。

下面是另一个例子,修改属性描述对象的enumerable属性,使得该属性不可遍历。

  1. class Person {
  2. @nonenumerable
  3. get kidCount() { return this.children.length; }
  4. }
  5. function nonenumerable(target, name, descriptor) {
  6. descriptor.enumerable = false;
  7. return descriptor;
  8. }

下面的@log装饰器,可以起到输出日志的作用。

  1. class Math {
  2. @log
  3. add(a, b) {
  4. return a + b;
  5. }
  6. }
  7. function log(target, name, descriptor) {
  8. var oldValue = descriptor.value;
  9. descriptor.value = function() {
  10. console.log(`Calling ${name} with`, arguments);
  11. return oldValue.apply(this, arguments);
  12. };
  13. return descriptor;
  14. }
  15. const math = new Math();
  16. // passed parameters should get logged now
  17. math.add(2, 4);

上面代码中,@log装饰器的作用就是在执行原始的操作之前,执行一次console.log,从而达到输出日志的目的。

装饰器有注释的作用。

  1. @testable
  2. class Person {
  3. @readonly
  4. @nonenumerable
  5. name() { return `${this.first} ${this.last}` }
  6. }

从上面代码中,我们一眼就能看出,Person类是可测试的,而name方法是只读和不可枚举的。

下面是使用 Decorator 写法的组件,看上去一目了然。

  1. @Component({
  2. tag: 'my-component',
  3. styleUrl: 'my-component.scss'
  4. })
  5. export class MyComponent {
  6. @Prop() first: string;
  7. @Prop() last: string;
  8. @State() isVisible: boolean = true;
  9. render() {
  10. return (
  11. <p>Hello, my name is {this.first} {this.last}</p>
  12. );
  13. }
  14. }

如果同一个方法有多个装饰器,会像剥洋葱一样,先从外到内进入,然后由内向外执行。

  1. function dec(id){
  2. console.log('evaluated', id);
  3. return (target, property, descriptor) => console.log('executed', id);
  4. }
  5. class Example {
  6. @dec(1)
  7. @dec(2)
  8. method(){}
  9. }
  10. // evaluated 1
  11. // evaluated 2
  12. // executed 2
  13. // executed 1

上面代码中,外层装饰器@dec(1)先进入,但是内层装饰器@dec(2)先执行。

除了注释,装饰器还能用来类型检查。所以,对于类来说,这项功能相当有用。从长期来看,它将是 JavaScript 代码静态分析的重要工具。