typescript中的decorator

最近在做一些node上的中间件,不可避免的用到了typescript中的注解,而在ts中,注解有另外一个叫法:decorator,于是花了一点时间搞明白了在ts中注解是如何运行的,下面做一个简单的记录。

为何decorator

作为ts中的注解,decorator可以分为四类:

  • Class Decorator
  • Method Decorator
  • Accessor Decorator
  • Property Decorator
  • Parameter Decorator

分别对应以下五种被注解的类型:

  • 方法
  • 存取器
  • 变量
  • 参数

具体的大家可以参考这份文档

decorator是如何运行的

下面我们通过一个简单的例子,来了解一下ts中的decorator是如何运行的。我们以上文中提到的Method Decorator为例,其他几个都是大同小异的。

先定义一个简单的注解:

1
2
3
4
5
6
7
8
9
10
export function annotation(...param) {
return function test(target: Function, key: string, value: any) {
console.log(param);
return {
value: function (...args: any[]) {
return value.value.apply(this, args);
}
};
}
}

再使用一下该注解:

1
2
3
4
5
6
7
8
class Clz {
@annotation("test")
foo(n: number) {
return n * 2;
}
}

console.log(new Clz().foo(10));

下面是运行程序之后的输出:

1
2
test
20

我们来看一下编译好之后的js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
var annotation_1 = require("./annotation");
var Clz = /** @class */ (function () {
function Clz() {
}
Clz.prototype.foo = function (n) {
return n * 2;
};
__decorate([
annotation_1.annotation("test")
], Clz.prototype, "foo", null);
return Clz;
}());
console.log(new Clz().foo(10));

我们关注下面这个最关键的函数:

1
2
3
__decorate([
annotation_1.annotation("test")
], Clz.prototype, "foo", null);

有次我们知道,当我们使用Decorator来注解一个方法的时候,ts会将其通过一个__decorate函数去进行执行,其中包括4个参数:

  • 一个注解方法数组,在例子中就是annotation方法
  • 类的原型
  • 被注解的方法
  • 描述

接下去我们就看一下__decorate具体的逻辑,我们将其格式化一下以便于阅读:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
r = Reflect.decorate(decorators, target, key, desc);
} else {
for (var i = decorators.length - 1; i >= 0; i--) {
if (d = decorators[i]) {
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
}
}
}
return c > 3 && r && Object.defineProperty(target, key, r), r;
};

首先会有一个判断,用以保证该方法不会被初始化多次:

1
2
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
}

接着会去判断改方法的入参长度,我们以文中的例子为例,长度为4,并且desc为null,所以我们会为desc进行赋值:

1
desc = Object.getOwnPropertyDescriptor(target, key)

接着我们会走到else的代码块里,通过for循环取出我们的注解,并且还是判断入参的长度去给r这个变量赋值,带入我们的入参为4,我们可以知道赋值代码如下:

1
r = d(target, key, r);

而这个d就是我们的注解方法。最后将其赋值给target的key方法。带入实参我们可以知道,target就是类的原型,而key就是被注解的方法,所以我们由此可以知道,整个__decorate就是将被注解的方法进行重新的赋值,指向我们的注解。

通过上面的源码分析我们可以知道,一旦一个方法被注解了,那么它的逻辑将不会按照原有的代码运行了,而是会被重新赋值到注解方法中。带入文中的例子就是说:Clz的foo方法会被重新赋值给anntation方法的返回值。

下面我们按顺序来执行一遍整个逻辑:

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
//test.js
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
//step2 给desc赋值
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
r = Reflect.decorate(decorators, target, key, desc);
} else {
//step3遍历取出注解方法
for (var i = decorators.length - 1; i >= 0; i--) {
if (d = decorators[i]) {
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
}
}
}
//step5给被注解的方法重新赋值,值为注解的方法
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
var annotation_1 = require("./annotation");
var Clz = /** @class */ (function () {
function Clz() {
}
Clz.prototype.foo = function (n) {
return n * 2;
};
//step1 执行__decorate方法
__decorate([
annotation_1.annotation("test")
], Clz.prototype, "foo", null);
return Clz;
}());
//step6 执行外部调用方法
console.log(new Clz().foo(10));


//annotation.js
Object.defineProperty(exports, "__esModule", { value: true });
function annotation(param) {
return function test(target, key, value) {
//step4 value为注解的具体执行方法
return {
value: function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return value.value.apply(this, args);
}
};
};
}
exports.annotation = annotation;

注解的使用场景

在ts中,注解的使用场景还是十分多见的,比如依赖注入,aop等等。如果你有过java-web的开发经验,那么这个特性会对你起很大的帮助~