ES6 Decorator 装饰器

理解 ES6 装饰器的概念、用法和实际应用场景

问题

如何理解 ES6 中的 Decorator?有哪些使用场景?

解答

什么是装饰器

装饰器(Decorator)是一个普通函数,用于在不改变原类和使用继承的情况下,动态扩展类属性和类方法。

举个例子,定义一个士兵类和装备装饰器:

// 士兵类
class Soldier {}

// 装饰器函数
function strong(target) {
  target.hasAK = true;
}

// 使用装饰器
@strong
class Soldier {}

Soldier.hasAK // true

类的装饰

类装饰器接收一个参数:类本身。

@decorator
class A {}

// 等同于
class A {}
A = decorator(A) || A;

为类添加静态属性:

@testable
class MyTestableClass {
  // ...
}

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

传递参数需要在外层再封装一层函数:

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

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

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

类属性的装饰

类属性装饰器接收三个参数:

  • 类的原型对象
  • 需要装饰的属性名
  • 装饰属性名的描述对象

实现一个 readonly 装饰器:

function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

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

// 等同于
readonly(Person.prototype, 'name', descriptor);

多个装饰器的执行顺序

多个装饰器像洋葱一样,先从外到内进入,再由内到外执行:

function dec(id) {
  console.log('evaluated', id);
  return (target, property, descriptor) => console.log('executed', id);
}

class Example {
  @dec(1)
  @dec(2)
  method() {}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1

注意事项

装饰器不能用于修饰函数,因为函数存在变量提升:

var counter = 0;
var add = function() {
  counter++;
};

@add
function foo() {}

// 编译后变成
var counter;
var add;

@add
function foo() {}

counter // 0,而不是预期的 1

实际应用场景

简化 React Redux 连接:

// 传统写法
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);

// 使用装饰器
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}

实现 mixins:

function mixins(...list) {
  return function(target) {
    Object.assign(target.prototype, ...list);
  };
}

const Foo = {
  foo() { console.log('foo') }
};

@mixins(Foo)
class MyClass {}

let obj = new MyClass();
obj.foo() // "foo"

使用 core-decorators.js 库:

import { autobind, readonly, deprecate } from 'core-decorators';

class Person {
  // 绑定 this
  @autobind
  getPerson() {
    return this;
  }
  
  // 只读属性
  @readonly
  name = 'John';
  
  // 废弃警告
  @deprecate('该方法将废除')
  oldMethod() {}
}

关键点

  • 装饰器是一个函数,用于扩展类和类方法,不改变原有代码
  • 类装饰器接收类本身作为参数,类属性装饰器接收原型对象、属性名和描述对象
  • 多个装饰器执行顺序:外层先进入,内层先执行
  • 装饰器不能用于普通函数,因为存在变量提升问题
  • 常用于简化代码(如 Redux 连接)、mixins、方法增强(autobind、readonly)等场景