什么是装饰器

装饰模式是一种结构型设计模式, 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。
就是能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。
通过装饰器可以在类运行时, 扩展一个类的功能, 并且去修改类本身的属性和方法, 使其可以在不同类之间更灵活的共用一些属性和方法。
在 javascript 中, @decorator 就是这种模式的具体实现,也就是本文的重点——装饰器
不过目前这个语法还在提案阶段,使用时需要配合 Babel 编译成 ES5 或 ES6 。

装饰器接收一个参数,也就是我们被装饰的目标方法,处理完扩展的内容以后再返回一个方法,供以后调用。
在 ES7 中,装饰器其实是个语法糖,它依赖 ES5 的 Object.defineProperty 方法。

类装饰器

@test
class TestClass {
  // ...
}

function test(target) {
  target.isTest = true;
}

TestClass.isTest // true

上面代码中,test 就是一个装饰器,它修改了 TestClass 这个类,给它添加了一个静态属性 isTest
如果需要动态传入值,还可以通过装饰器的参数来赋值:

function test(isTest) {
  return function(target) {
    target.isTest = isTest;
  }
}

@test(true)
class A {}
A.isTest // true

@test(false)
class B {}
B.isTest // false

需要注意的是,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。

类成员装饰器

装饰器不仅可以装饰类,还可以应用于类中的单个成员,属性、方法、getter 或 setter ,装饰器函数接受3个参数:

  • target 成员所在的类
  • name 类成员
  • descriptor 成员描述符
function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

class Example {
   a() {}

   @readonly
   b() {}
}

const myClass = new Example();
myClass.a = 10;
myClass.b = 20;  // TypeError

函数装饰器

和上面两种不一样的是,不能直接用 @ 指定函数的装饰器,因为存在函数提升,但是可以通过高阶函数的形式实现装饰器的功能。

function logDecorator(logger) {
  return function() {
    const result = logger.apply(this, arguments);
    console.log('running: ' + new Date());
  }
}

function add(x, y) {
  console.log('total: ' + (x + y));
}

const wrapperFn = logDecorator(add);
wrapperFn(1, 2);

总结

装饰器的主要目的是在类和类属性之间共享功能,还有就是,开发人员可以使用装饰器轻松地将功能的增强与代的码特性分开。允许在不增加代码复杂性的情况下向类和属性添加新功能,使得代码更易于维护和调试。