ECMAScript 6-修饰器Decorator

修饰器(Decorator)是一个函数,用来修改类的行为。修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码

ECMAScript 6-修饰器Decorator

类的修饰

//修饰器函数1:为类加上静态属性isTestable
//修饰器函数的第一个参数,就是所要修饰的目标类
function testable(target) {
  target.isTestable = true;
}

//修饰类的行为。写在类上方,表示修饰器的目标是这整个类
@testable
class MyTestableClass {}

console.log(MyTestableClass.isTestable) // true
//修饰器函数2:添加实例属性
function testable(target) {
  target.prototype.isTestable = true;
}

@testable
class MyTestableClass {}

let obj = new MyTestableClass();
obj.isTestable // true

修饰器的实际行为

@decorator
class A {}

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

类的方法的修饰

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

修饰器的参数

修饰器函数一共可以接受三个参数:

  • 所要修饰的目标对象
  • 所要修饰的属性名
  • 该属性的描述对象
function nonenumerable(target, name, descriptor) {
  descriptor.enumerable = false; //修改了描述对象的属性enumerable,使得不可遍历
  return descriptor;
}

class Person {
  @nonenumerable //修饰该属性
  get kidCount() { return this.children.length; }
}

@log修饰器例子:输出日志

function log(target, name, descriptor) {
  var oldValue = descriptor.value;
  descriptor.value = function() {
    console.log(`Calling "${name}" with`, arguments);
    return oldValue.apply(null, arguments);
  };
  return descriptor;
}

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


const math = new Math();
// passed parameters should get logged now
math.add(2, 4);

修饰器有注释作用

@testable
class Person {
  @readonly
  @nonenumerable
  name() { return `${this.first} ${this.last}` }
}
//可看出Person类是可测试的,而name方法是只读和不可枚举的

core-decorators.js

core-decorators.js是一个第三方模块,提供了几个常见的修饰器,通过它可以更好地理解修饰器

  • @autobind:使得方法中的this对象,绑定原始对象

    import { autobind } from 'core-decorators';
    
    class Person {
      @autobind
      getPerson() {
        return this;
      }
    }
    
    let person = new Person(); 
    let getPerson = person.getPerson;
    
    getPerson() === person; 	// true
    
  • @readonly:使得属性或方法不可写

    import { readonly } from 'core-decorators';
    
    class Meal {
      @readonly
      entree = 'steak';
    }
    
    var dinner = new Meal();
    dinner.entree = 'salmon';
    // Cannot assign to read only property 'entree' of [object Object]
    
  • @override:检查子类的方法是否正确覆盖了父类的同名方法,如果不正确会报错

    import { override } from 'core-decorators';
    
    class Parent {
      speak(first, second) {}
    }
    
    class Child extends Parent {
      @override
      speak() {}// SyntaxError: Child#speak() does not properly override Parent#speak(first, second)
    }
    
  • @deprecate (别名@deprecated):在控制台显示一条警告,表示该方法将废除

    import { deprecate } from 'core-decorators';
    
    class Person {
      @deprecate
      facepalm() {}
    
      @deprecate('We stopped facepalming')
      facepalmHard() {}
    
      @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
      facepalmHarder() {}
    }
    
    let person = new Person();
    
    person.facepalm();
    // DEPRECATION Person#facepalm: This function will be removed in future versions.
    
    person.facepalmHard();
    // DEPRECATION Person#facepalmHard: We stopped facepalming
    
    person.facepalmHarder();
    // DEPRECATION Person#facepalmHarder: We stopped facepalming
    //
    //     See http://knowyourmeme.com/memes/facepalm for more details.
    //
    

Mixin

在修饰器的基础上,可以实现Mixin模式。所谓Mixin模式,就是对象继承的一种替代方案,中文译为“混入”(mixin),意为在一个对象之中混入另外一个对象的方法

以往写法:

//一个对象,里面有个foo方法
const Foo = {
  foo() { console.log('foo') }
};

//一个类
class MyClass {}

//通过Object.assign在类的原型对象上添加Foo对象上的方法添加进去,
Object.assign(MyClass.prototype, Foo);

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

现在:部署一个通用脚本mixins.js,将mixin写成一个修饰器

export function mixins(...list) {
  return function (target) {
    Object.assign(target.prototype, ...list);
  };
}
//原理:传入参数(这里是对象类型),然后将该参数中的方法添加在类的原型对象中

使用

import { mixins } from './mixins';

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

@mixins(Foo) //传入Foo对象,把Foo对象中foo方法的添加在MyClass的原型对象中
class MyClass {}

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

Trait修饰器

效果与Mixin类似,但是提供更多功能,比如防止同名方法的冲突、排除混入某些方法、为混入的方法起别名等等

下面采用traits-decorator这个第三方模块作为例子。这个模块提供的traits修饰器,不仅可以接受对象,还可以接受ES6类作为参数

import { traits } from 'traits-decorator';

//TFoo类中有foo方法
class TFoo {
  foo() { console.log('foo') }
}

//TBar对象中有bar方法
const TBar = {
  bar() { console.log('bar') }
};

//把TFoo类中有foo方法和TBar对象中有bar方法加进MyClass类的原型对象中
@traits(TFoo, TBar)
class MyClass { }

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

另外,Trait不允许“混入”同名方法

Babel转码器的支持

目前,Babel转码器已经支持Decorator。

首先,安装babel-corebabel-plugin-transform-decorators。由于后者包括在babel-preset-stage-0之中,所以改为安装babel-preset-stage-0亦可

$ npm install babel-core babel-plugin-transform-decorators

然后,设置配置文件.babelrc

{
  "plugins": ["transform-decorators"]
}

这时,Babel就可以对Decorator转码了

脚本中打开的命令如下

babel.transform("code", {plugins: ["transform-decorators"]})
上一篇:Error: The 'decorators' plugin requires a 'decoratorsBeforeExport' option, whose


下一篇:29. 用libgdx 写一个管理临时数组类