ES7的装饰器

准备写这篇博客,是因为年初在找工作的时候,面试被问到了装饰器,对于ES6的好多新特性和方法还一知半解的我来说太超纲了!因此决定好好研究一下这是个啥[捂脸]~

1、概念

装饰器是ES7的语法,本质是一个wrapper,可以动态增强类以及实例方法,同时,被装饰者对于装饰者是无感知的。

2、使用

1)示例1,给类添加装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
@testable
class MyTestableClass {
    // ...
}

function testable(target) {
    //这里的target是MyTestableClass类
    target.isTestable = true;
    return target;
}

console.log(MyTestableClass.isTestable); // true

等同于

1
    MyTestableClass = testable(function MyTestableClass(){...});

上面代码中,@testable就是一个装饰器,它修改了MyTestableClass这个类的行为,为其添加了属性isTestable。

上述只是个简单的例子,装饰器一般接受三个参数:

function(target, key, descriptor)

  • target: 需要定义属性的对象(MyTestableClass)

  • key: 需要定义或修改的属性的名字

  • descriptor: 将被定义或修改的属性的描述符(属性的描述对象)

这里可以看出其参数同Object.defineProperty是一样的,本质上来说,作用在类上的装饰器是通过Object.defineProperty来进行扩展和封装的。

2)示例2,给类的属性添加装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function readonly(target, name, descriptor){
     descriptor.writable = false;
     return descriptor;
}

class MyTestableClass{
    @readonly
    say = '翠花';
}

let instance = new MyTestableClass();

instance.say = '酸菜';
console.log(instance.say);//翠花

从上面例子可以看出,当装饰器作用域类本身的时候,我们操作的对象也是类本身,而当装饰器作用域某个具体的属性的时候,我们操作的对象既不是类本身,也不是类的属性,而是他的描述符,而描述符里记录着我们对这个属性的全部信息,所以,我们可以对它自由的进行扩展和封装。

3)装饰器特性:

  • 有多个装饰器时,先从外到内进入,再由内向外执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

上述代码中,dec装饰器又返回了一个装饰器,根据其特性,外层装饰器@dec(1)先进入,但是内层装饰器@dec(2)先执行。

  • 装饰器不能用于普通函数,因为普通函数存在变量提升。

3、装饰器集合

core-decorators.js中内置了很多装饰器,可以根据实际情况使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { autobind } from 'core-decorators';

class Person {
    @autobind
    getPerson() {
        return this;
    }
}

let person = new Person();
let getPerson = person.getPerson;

getPerson() === person;
// true

以上是一个绑定原始对象的装饰器,还有:

  • readonly:使属性或方法不可写;
  • override:检查子类是否覆盖父类的同名方法;
  • deprecate:在控制台显示一条警告,表示该方法将废除;
  • suppressWarnings:抑制deprecated装饰器导致的console.warn()调用。但是,异步代码发出的调用除外;
  • ……

4、编译装饰器

1
npm i babel-cli babel-preset-es2015 babel-preset-stage-1 babel-plugin-transform-decorators

.babelrc文件如下:

1
2
3
4
5
6
{
  "presets": ["es2015", "stage-1"],
  "plugins": [
    "babel-plugin-transform-decorators-legacy"
  ]
}

参考文档: ES6入门-装饰器

探寻 ECMAScript 中的装饰器 Decorator