前端发展的速度真的是太快了,ES2015(ES6)刚出来一大波特性,下一代的 ES7 又推出更多方便的特性。但是仔细看一下,会发现,JS的语法越来越在向 Python 靠拢了,ES7 中将出现的 decorator 就是其中的一个特性。

简单地来说,decorator 允许我们在不修改函数内部代码的情况下添加一些额外的行为。在以前的标准中,我们可能会需要通过类似于柯里化(Currying)的方法来实现这个行为:

1
2
3
4
5
6
7
8
9
10
11
function decorate(fn) {
return function () {
// 额外行为
fn.call(this, [].slice.call(arguments));
}
}
function foobar() {
// do something
}
newFoobar = decorate(foobar);
newfoobar(parameters);

作用于成员变量

但是,有了decorator,我们可以更加简洁地实现这一行为。与此同时,decorator还允许我们对一个class进行修饰,如很常用的就是添加某些属性、某些属性设为只读特性。按其语法,decorator函数接受三个参数:target, key, descriptor。还记得我们可以通过 Object.defineProperty 定义对象的成员吗:

1
2
3
4
5
6
Object.defineProperty(Class.Prototype, 'method', {
value: function () {},
enumerable: false,
configurable: true,
writable: true
});

decorator里的参数与defineProperty的参数一致。实现一个readonly的decorator可以简单地使用下面方式:

1
2
3
4
5
6
7
8
9
function readonly(target, key, descriptor) {
descriptor.writable = false;
return descriptor;
}
class Class {
@readonly
method () {}
}

当JS引擎执行到decorator的 @ 标志时,它实际执行的代码为:

1
2
3
4
5
6
7
8
let descriptor = {
value: method,
enumerable: false,
configurable: true,
writable: true
};
descriptor = readonly(Class.prototype, 'method', descriptor) || descriptor;
Object.defineProperty(Class.prototype, 'method', descriptor);

(显然,从这里来看,作用于成员变量也是没有问题的)。

作用于 Class

下面来看一下怎样对一个类进行decorate。很简单,我们只要操作 decorate 函数参数中的target即可:

1
2
3
4
5
6
7
8
9
10
function className(clsName) {
return function (target) {
target.className = clsName;
}
}
@className('ui.component')
class UIComponent {
//
}
console.log(UIComponent.className);

是不是非常地方便呢?赶紧开始使用吧!直接使用babel转码器即可开启这个特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// babel --optional es7.decorators decorator.js | iojs
import {autobind, readonly, deprecate, debounce, suppressWarnings} from 'core-decorators';
/* decorate a member method*/
class Dog {
@readonly
bark () {
console.log('wang! wang!');
}
}
var dog = new Dog();
// dog.bark = function () {}; // tbis will throw an exception
dog.bark();
/* decorate a class */
function className(name) {
return function (target) {
target.prototype.className = name;
target.prototype.sayName = function () {
console.log(this.className);
}
}
}
@className('ui.Widget')
class Widget {
@deprecate('use the Layout instead', {url: 'http://www.xxxx.com'})
@debounce(3000)
layout() {
console.log('I am triggered after about 3 seconds')
}
@suppressWarnings
doLayoutWithNoWarning() {
console.log('hello')
this.layout();
}
}
var widget = new Widget();
widget.sayName();
widget.layout();
widget.doLayoutWithNoWarning();
@autobind
class Person {
@autobind
getPerson() {
return this;
}
}
let person = new Person();
let getPerson = person.getPerson;
console.log(getPerson() === person);

而事实上,GITHUB 上已经有人实现了一系列常用的 decorator,如 readonly 用于设置方法的只读,debounce 用于延迟执行,deprecate 在调用的时候用于提醒用户方法已过时等等。传送门:https://github.com/jayphelps/core-decorators.js。

参考