解读 Babel 编译后的 decorator 代码

2,154 阅读3分钟

装饰器是一种与类相关的语法,用来注释或修改类和类方法。

装修类

装饰类 - @decorator

// target 就是构造函数 Foo
function print(target){
    console.log(target)
}

// 编译前,注意,在这里我们的使用方式是 `@print` 而不是 `@print()`。
@print
class Foo(){}

// 编译后
var Foo =
    print(
        (_class = function Foo() {})
    ) || _class;

由于 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);

可以发现首先执行 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

可以看到,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(); // 张三

我们可以看出 foo 的原型上有 print 方法。借鉴阮一峰老师的文章来说。有了装饰器,就可以把这样的代码

class MyReactComponent extends React.Component {}

export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);

改成这样的代码

@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}

是不是后一种更容易理解。

装饰属性

// 编译前
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);

可以看到编译后 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;
}

装饰方法

// 编译前
function print() {}
class Foo {
    @print
    getName() {
        return this.name;
    }
}

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);
    }
}

参考文献