解读 Babel 编译后的 decorator 代码
装饰器是一种与类相关的语法,用来注释或修改类和类方法。
装修类
装饰类 - @decorator
// target 就是构造函数 Foo
function print(target){
console.log(target)
}
// 编译前,注意,在这里我们的使用方式是 `@print` 而不是 `@print()`。
@print
class Foo(){}
// 编译后
var Foo =
print(
(_class = function Foo() {})
) || _class;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
由于 print
函数返回 undefined
,所以 Foo
在这里仍是 Foo
。那当我们装饰器写成 @print()
时,又会发生什么?
装饰类 - @decorator()
// 编译后
var Foo = ((_dec = print()),
_dec(
(_class = ((_temp = function Foo() {
_classCallCheck(this, Foo);
this.a = 1;
this.b = 2;
}),
_temp))
) || _class);
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
可以发现首先执行 print
函数,但是由于 print
返回 undefined
,所以当执行 dec()
直接报错。
从这里,可以发现装饰器会在编译阶段就开始执行,你可以在装饰器对应的函数中对原型做些修改。
思考
如果装饰器函数又返回一个函数,那这时怎么处理?应用场景又会是什么?
// 编译前
function print(mixin){
return function(name){
this.name = name;
}
}
@print
class Foo{}
new Foo('张三')
// 编译后
function print(target) {
return function(name) {
this.name = name;
};
}
var Foo =
print(
(_class = function Foo() {
_classCallCheck(this, Foo);
})
) || _class;
var foo = new Foo('张三');
console.log(foo.name); // 张三
console.log(foo.hasOwnProperty('name')); // true
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
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
可以看到,Foo
的构造函数变成 print
函数内所返回的函数,且实例上有 name
属性。
看了 @decorator
方式,接下来看下 @decorator()
方式。
function mixin(...listFn){
return function(target){
Object.assign(target.prototype, { ...listFn });
}
}
function print(){
console.log(this.name)
}
@mixin(print)
class Foo{
name = '张三';
}
var foo = new Foo();
foo.print(); // 张三
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
我们可以看出 foo
的原型上有 print
方法。借鉴阮一峰老师的文章来说。有了装饰器,就可以把这样的代码
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
1
2
3
2
3
改成这样的代码
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
1
2
2
是不是后一种更容易理解。
装饰属性
// 编译前
function print(target){
console.log(target)
}
class Foo{
@print name;
}
new Foo();
// 编译后部分代码
var Foo = ((_class = ((_temp = function Foo() {
_initializerDefineProperty(this, 'name', _descriptor, this);
}),
_temp)),
(_descriptor = _applyDecoratedDescriptor(_class.prototype, 'name', [print], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
})),
_class);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
可以看到编译后 decorator
会作为 _applyDecoratedDescriptor
第三个参数传递进去,_applyDecoratedDescriptor
内部会先拷贝原有属性的属性描述符,紧接着调用 decorator
对应的函数,最终返回属性描述符。
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
// 拷贝原有的属性描述符
var desc = {};
Object.keys(descriptor).forEach(function(key) {
desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;
if ('value' in desc || desc.initializer) {
desc.writable = true;
}
// 由内往外执行 decorator 对应函数
desc = decorators.slice().reverse().reduce(function(desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
// 装饰方法所用
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
desc.initializer = undefined;
}
if (desc.initializer === void 0) {
Object.defineProperty(target, property, desc);
desc = null;
}
return desc;
}
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
装饰方法
// 编译前
function print() {}
class Foo {
@print
getName() {
return this.name;
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
decorator
装饰方法与装饰属性和类编译后的代码有很大差异。其一, 把构造函数的原型对象作为参数 context
传入_applyDecoratedDescriptor
函数中。其二,属性描述符是通过 Object.getOwnPropertyDescriptor
方法获取。其三,默认调用 _createClass
为构造函数 Foo
创建属性,其属性描述符默认为不可枚举。
// 编译后 部分代码
var Foo = (
(_class = (function() {
function Foo() {}
_createClass(Foo, [
{
key: 'getName',
value: function getName() {
console.log('getName');
}
}
]);
return Foo;
})()),
...,
_class
);
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
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
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