装饰器(Decorators)提供一种简练的语法糖,这种语言特性 为 JavaScript
/ TypeScript
注入了新的活力。目前 Decorators
处于 Stage-2
阶段,即规范初稿阶段。
这种语言特性类似于 Python
中的 Decorators
、 Java
和 C#
中的注解特性。
需要区别不同的是,JS中的装饰器 只能 附加在类声明,方法,访问符,属性或参数上。我们先来看一个装饰器的例子:
1
2
3
4
5
6
7
8
| @frozen
class Foo {
@configurable(false)
@enumerable(true)
method() {
console.log('bar')
}
}
|
通过示例可以发现,Decorators 提供了一个非常简洁的方式去声明一个变量/方法。在此例子中,我们无需了解 @frozen
、@configurable
、@enumerable
的内部实现,就可猜出它们各自的作用:如该例子中,设置类方法 method
enumerable=true 和 configurable=false。
Decorators
最初是用来弥补 ECMAScript6 class
中声明的变量无法修改描述符的短板,但我们也可以利用该特性去做一些更高级的用法。
TypeScript 装饰器
Decorators
在 TypeScript 中为实验性特性,必须在 tsconfig.json
或者 命令行中启用 experimentalDecorators
编译选项:
1
2
3
4
5
6
| {
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
|
类装饰器
定义一个类装饰器很容易,它的第一个参数就是类构造器。
下面是一个简单的 serializable 例子说明如何定义和使用一个类装饰器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| function serializable(ctor: Function) {
ctor.prototype.toString = function () {
return JSON.stringify(this)
}
}
@serializable
class User {
// typescript shortcut
constructor(private message: string, private age: number) {
}
}
console.log(new User('John', 18))
// User { message: 'John', age: 18 }
|
方法装饰器
方法装饰器会传入三个参数:
- 如果是实例成员,传入类的原型
prototype
,如果是静态成员,传入类的构造函数; - 成员的名字;
- 成员的属性描述符。
以下以官方文档提供的 @enumerable
来作为示例:
1
2
3
4
5
| function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
|
用法:
1
2
3
4
5
6
7
8
9
10
11
| class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
|
实践
订阅事件
这个是从同事写的 Java EventBus 中获得的灵感。先上 Java 的代码:
1
2
3
4
5
6
7
8
9
10
11
| public class UserEventSubscriber {
@Subscribe
public void afterLogin(UserLoginEvent event) {
// 用户登录
}
@Subscribe
public void afterLogout(UserLogoutEvent event) {
// 用户注销
}
}
|
使用 Decorators
特性,我们也可以用这个来实现相同的写法:
1
2
3
4
5
| function subscribe(typeName: string) {
return function (target: any, propertyKey: string) {
event.on(typeName, target[propertyKey].bind(target));
};
}
|