新版ES装饰器

372 阅读2分钟

ES Decorators 的最新版本是 Stage 3,在该版本中,装饰器的特性得到了进一步的扩展和改进,其中一些重要的变化如下:

1. 私有属性装饰器

在 ES6 中,引入了类和私有属性的概念,但是对于私有属性,我们无法在其上应用装饰器。而在私有属性装饰器的引入之后,我们可以使用装饰器来扩展私有属性的功能,例如,为私有属性添加缓存、验证、事件处理等功能。

function privateProperty(target, key) {
  const privateKey = Symbol(key);

  Object.defineProperty(target, key, {
    get() {
      if (this.hasOwnProperty(privateKey)) {
        return this[privateKey];
      }
      return undefined;
    },
    set(newValue) {
      Object.defineProperty(this, privateKey, {
        writable: true,
        configurable: true,
        enumerable: false,
        value: newValue
      });
    },
    enumerable: false,
    configurable: false
  });
}

class MyClass {
  @privateProperty
  myPrivateField;

  constructor() {
    this.myPrivateField = 'private value';
  }

  getMyPrivateField() {
    return this.myPrivateField;
  }
}

const myObject = new MyClass();

console.log(myObject.myPrivateField); // undefined
console.log(myObject.getMyPrivateField()); // 'private value'

2. 类表达式装饰器

在 ES6 中,我们可以使用 class 关键字来定义一个类,但是不能在类表达式上应用装饰器。而在类表达式装饰器的引入之后,我们可以使用装饰器来扩展类表达式的功能,例如,为类表达式添加日志、错误处理、性能监控等功能。

function log(target, name, descriptor) {
  const original = descriptor.value;

  descriptor.value = function(...args) {
    console.log(`[${name}] called with arguments: ${JSON.stringify(args)}`);
    const result = original.apply(this, args);
    console.log(`[${name}] returned: ${JSON.stringify(result)}`);
    return result;
  };

  return descriptor;
}

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

  subtract(a, b) {
    return a - b;
  }
}

const calculator = new Calculator();
calculator.add(2, 3); // [add] called with arguments: [2,3], [add] returned: 5
calculator.subtract(5, 2); // [subtract] called with arguments: [5,2], [subtract] returned: 3

4. 装饰器组合

在 ES6 中,我们可以使用 @decorator 来装饰一个类、方法或属性,但是只能应用一个装饰器。而在装饰器组合的引入之后,我们可以使用多个 @decorator 来组合使用。

function upperCase(target, name, descriptor) {
  const original = descriptor.value;
  descriptor.value = function(...args) {
    const result = original.apply(this, args);
    if (typeof result === 'string') {
      return result.toUpperCase();
    }
    return result;
  }
  return descriptor;
}

function reverse(target, name, descriptor) {
  const original = descriptor.value;
  descriptor.value = function(...args) {
    const result = original.apply(this, args);
    if (typeof result === 'string') {
      return result.split('').reverse().join('');
    }
    return result;
  }
  return descriptor;
}

class MyClass {
  @upperCase
  @reverse
  sayHello() {
    return 'hello world';
  }
}

const myClass = new MyClass();
console.log(myClass.sayHello()); // dlrow OLLEh

6. 装饰器工厂的返回值

该特性允许装饰器工厂返回任何类型的值,例如:

function repeat(numRepeats) {
    return function decoratorRepeat(target, key, descriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function(...args) {
      for (let i = 0; i < numRepeats; i++) {
        originalMethod.apply(this, args);
      }
    }

    return descriptor;
  }
}

class Greeting {
  @repeat(3)
  sayHello(name) {
    console.log(`Hello, ${name}!`);
  }
}

const greeting = new Greeting();
greeting.sayHello('John');

参考

ECMAScript 双月报告:装饰器提案进入 Stage 3