怎么理解ES6中 Decorator 的?使用场景? - 图1

一、介绍

Decorator,即装饰器,从名字上很容易让我们联想到装饰者模式

简单来讲,装饰者模式就是一种在不改变原类和使用继承的情况下,动态地扩展对象功能的设计理论。

ES6Decorator功能亦如此,其本质也不是什么高大上的结构,就是一个普通的函数,用于扩展类属性和类方法

这里定义一个士兵,这时候他什么装备都没有

  1. class soldier{
  2. }

定义一个得到 AK 装备的函数,即装饰器

  1. function strong(target){
  2. target.AK = true
  3. }

使用该装饰器对士兵进行增强

  1. @strong
  2. class soldier{
  3. }

这时候士兵就有武器了

  1. soldier.AK // true

上述代码虽然简单,但也能够清晰看到了使用Decorator两大优点:

  • 代码可读性变强了,装饰器命名相当于一个注释
  • 在不改变原有代码情况下,对原来功能进行扩展

二、用法

Docorator修饰对象为下面两种:

  • 类的装饰
  • 类属性的装饰

类的装饰

当对类本身进行装饰的时候,能够接受一个参数,即类本身

将装饰器行为进行分解,大家能够有个更深入的了解

  1. @decorator
  2. class A {}
  3. // 等同于
  4. class A {}
  5. A = decorator(A) || A;

下面@testable就是一个装饰器,target就是传入的类,即MyTestableClass,实现了为类添加静态属性

  1. @testable
  2. class MyTestableClass {
  3. // ...
  4. }
  5. function testable(target) {
  6. target.isTestable = true;
  7. }
  8. MyTestableClass.isTestable // true

如果想要传递参数,可以在装饰器外层再封装一层函数

  1. function testable(isTestable) {
  2. return function(target) {
  3. target.isTestable = isTestable;
  4. }
  5. }
  6. @testable(true)
  7. class MyTestableClass {}
  8. MyTestableClass.isTestable // true
  9. @testable(false)
  10. class MyClass {}
  11. MyClass.isTestable // false

类属性的装饰

当对类属性进行装饰的时候,能够接受三个参数:

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

首先定义一个readonly装饰器

  1. function readonly(target, name, descriptor){
  2. descriptor.writable = false; // 将可写属性设为false
  3. return descriptor;
  4. }

使用readonly装饰类的name方法

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

相当于以下调用

  1. readonly(Person.prototype, 'name', descriptor);

如果一个方法有多个装饰器,就像洋葱一样,先从外到内进入,再由内到外执行

  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)先执行

注意

装饰器不能用于修饰函数,因为函数存在变量声明情况

  1. var counter = 0;
  2. var add = function () {
  3. counter++;
  4. };
  5. @add
  6. function foo() {
  7. }

编译阶段,变成下面

  1. var counter;
  2. var add;
  3. @add
  4. function foo() {
  5. }
  6. counter = 0;
  7. add = function () {
  8. counter++;
  9. };

意图是执行后counter等于 1,但是实际上结果是counter等于 0

三、使用场景

基于Decorator强大的作用,我们能够完成各种场景的需求,下面简单列举几种:

使用react-redux的时候,如果写成下面这种形式,既不雅观也很麻烦

  1. class MyReactComponent extends React.Component {}
  2. export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);

通过装饰器就变得简洁多了

  1. @connect(mapStateToProps, mapDispatchToProps)
  2. export default class MyReactComponent extends React.Component {}

mixins,也可以写成装饰器,让使用更为简洁了

  1. function mixins(...list) {
  2. return function (target) {
  3. Object.assign(target.prototype, ...list);
  4. };
  5. }
  6. // 使用
  7. const Foo = {
  8. foo() { console.log('foo') }
  9. };
  10. @mixins(Foo)
  11. class MyClass {}
  12. let obj = new MyClass();
  13. obj.foo() // "foo"

下面再讲讲core-decorators.js几个常见的装饰器

@antobind

autobind装饰器使得方法中的this对象,绑定原始对象

  1. import { autobind } from 'core-decorators';
  2. class Person {
  3. @autobind
  4. getPerson() {
  5. return this;
  6. }
  7. }
  8. let person = new Person();
  9. let getPerson = person.getPerson;
  10. getPerson() === person;
  11. // true

@readonly

readonly装饰器使得属性或方法不可写

  1. import { readonly } from 'core-decorators';
  2. class Meal {
  3. @readonly
  4. entree = 'steak';
  5. }
  6. var dinner = new Meal();
  7. dinner.entree = 'salmon';
  8. // Cannot assign to read only property 'entree' of [object Object]

@deprecate

deprecatedeprecated装饰器在控制台显示一条警告,表示该方法将废除

  1. import { deprecate } from 'core-decorators';
  2. class Person {
  3. @deprecate
  4. facepalm() {}
  5. @deprecate('功能废除了')
  6. facepalmHard() {}
  7. }
  8. let person = new Person();
  9. person.facepalm();
  10. // DEPRECATION Person#facepalm: This function will be removed in future versions.
  11. person.facepalmHard();
  12. // DEPRECATION Person#facepalmHard: 功能废除了

参考文献