装饰器
Decorators 是ES7中添加的JavaScript新特性。
在JavaScript中,一共有4类装饰器:
- Method Decorator 函数装饰器
- Property Decorators 熟悉装饰器
- Class Decorator 类装饰器
- Parameter Decorator 参数装饰器
方法装饰器实例
下面我们通过方法装饰器来修改一个函数的输入和输出。
function leDecorator(target, propertyKey: string, descriptor: PropertyDescriptor): any {
var oldValue = descriptor.value;
descriptor.value = function() {
console.log(`Calling "${propertyKey}" with`, arguments,target);
// Executing the original function interchanging the arguments
let value = oldValue.apply(null, [arguments[1], arguments[0]]);
//returning a modified value
return value + "; This is awesome";
};
return descriptor;
}
class JSMeetup {
speaker = "Ruban";
//@leDecorator
welcome(arg1, arg2) {
console.log(`Arguments Received are ${arg1}, ${arg2}`);
return `${arg1} ${arg2}`;
}
}
const meetup = new JSMeetup();
console.log(meetup.welcome("World", "Hello"));
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
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
在不使用装饰器的时候,输出值为:
Arguments Received are World, Hello
World Hello
1
2
2
启用装饰器后,输出值为:
Calling "welcome" with { '0': 'World', '1': 'Hello' } JSMeetup {}
Arguments Received are Hello, World
Hello World; This is awesome
1
2
3
2
3
通过使用装饰器,我们实现了对原函数的包装,可以修改方法的输入和输出,这意味着我们可以应用各种想要的魔法效果到目标方法上。
这里有几点需要注意的地方:
- 装饰器在class被声明的时候被执行,而不是class实例化的时候。
- 方法装饰器返回一个值
- 存储原有的描述符并且返回一个新的描述符是我们推荐的做法. 这在多描述符应用的场景下非常有用。
- 设置描述符的value的时候,不要使用箭头函数。
属性装饰器
属性装饰器和方法装饰器很类似,通过属性装饰器,我们可以用来重新定义getters、setters,修改enumerable, configurable等属性
function realName(target, key: string): any {
// property value
var _val = target[key];
// property getter
var getter = function () {
return "Ragularuban(" + _val + ")";
};
// property setter
var setter = function (newVal) {
_val = newVal;
};
// Create new property with getter and setter
Object.defineProperty(target, key, {
get: getter,
set: setter
});
}
class JSMeetup {
//@realName
public myName = "Ruban";
constructor() {
}
greet() {
return "Hi, I'm " + this.myName;
}
}
const meetup = new JSMeetup();
console.log(meetup.greet());
meetup.myName = "Ragul";
console.log(meetup.greet());
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
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
在不适用装饰器时,输出结果为:
Hi, I'm Ruban
Hi, I'm Ragul
1
2
2
启用装饰器之后,结果为:
Hi, I'm Ragularuban(Ruban)
Hi, I'm Ragularuban(Ragul)
1
2
2
Class 装饰器
Class装饰器是通过操作Class的构造函数,来实现对Class的相关属性和方法的动态添加和修改。
function AwesomeMeetup<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor implements extra {
speaker: string = "Ragularuban";
extra = "Tadah!";
}
}
//@AwesomeMeetup
class JSMeetup {
public speaker = "Ruban";
constructor() {
}
greet() {
return "Hi, I'm " + this.speaker;
}
}
interface extra {
extra: string;
}
const meetup = new JSMeetup() as JSMeetup & extra;
console.log(meetup.greet());
console.log(meetup.extra);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
在不启用装饰器的情况下输出值为:
Hi, I'm Ruban
undefined
1
2
2
在启用装饰器的情况下,输出结果为:
Hi, I'm Ragularuban
Tadah!
1
2
2
参数装饰器
如果通过上面讲过的装饰器来推论参数装饰器的作用,可能会是修改参数,但事实上并非如此。参数装饰器往往用来对特殊的参数进行标记,然后在方法装饰器中读取对应的标记,执行进一步的操作。例如:
function logParameter(target: any, key: string, index: number) {
var metadataKey = `myMetaData`;
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(index);
}
else {
target[metadataKey] = [index];
}
}
function logMethod(target, key: string, descriptor: any): any {
var originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
var metadataKey = `myMetaData`;
var indices = target[metadataKey];
console.log('indices', indices);
for (var i = 0; i < args.length; i++) {
if (indices.indexOf(i) !== -1) {
console.log("Found a marked parameter at index" + i);
args[i] = "Abrakadabra";
}
}
var result = originalMethod.apply(this, args);
return result;
}
return descriptor;
}
class JSMeetup {
//@logMethod
public saySomething(something: string, @logParameter somethingElse: string): string {
return something + " : " + somethingElse;
}
}
let meetup = new JSMeetup();
console.log(meetup.saySomething("something", "Something Else"));
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
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
上面的代码中,我们定义了一个参数装饰器,该装饰器将被装饰的参数放到一个指定的数组中。在方法装饰器中,查找被标记的参数,做进一步的处理 不启用装饰器的情况下,输出结果如下:
something:Something Else
1
启用装饰器的情况下,输出结果如下:
Found a marked parameter at index 1
something:Abrakadabra
1
2
2
小结
现在我们已经学习了所有装饰器的使用,下面总结一下关键用法:
- 方法装饰器的核心是 方法描述符
- 属性装饰器的核心是 Object.defineProperty
- Class装饰器的核心是 构造函数
- 参数装饰器的主要作用是标记,要结合方法装饰器来使用
参考文档:
https://juejin.im/post/5ac85f1d6fb9a028bf0590ee
https://juejin.im/post/5b6156e4e51d45355d51f819
https://juejin.im/post/5b41f76be51d4518f140f9e4