TS | 装饰器原理

23 阅读4分钟

我们这里通过一个类装饰器和参数装饰器编译之后的结果简单看看装饰器的原理

装饰器

function log(message: string) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const originalMethod = descriptor.value;
        descriptor.value = function (...args: any[]) {
            console.log(`[${message}] 调用方法: ${String(propertyKey)}`);
            console.log(`[${message}] 参数:`, args);
            const result = originalMethod.apply(this, args);
            console.log(`[${message}] 返回值:`, result);
            return result;
        };
        return descriptor;
    };
}

function LogClass(constructor: Function) {
    console.log(`类 ${constructor.name} 被定义了`);
}

@LogClass
class User {
    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    @log('用户')
    sayHello(word: string) {
        console.log(`${word}, 我是 ${this.name}, 今年 ${this.age} 岁`);
    }
}

// 使用 User 类
const user = new User('张三', 25);
user.sayHello('你好');
//类 User 被定义了
//[用户] 调用方法: sayHello
//[用户] 参数: [ '你好' ]
//你好, 我是 张三, 今年 25 岁
//[用户] 返回值: undefined

装饰器编译后

// 运行初始化器,initializers是初始化器数组,value是初始化器的值
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
    var useValue = arguments.length > 2;
    for (var i = 0; i < initializers.length; i++) {
        // 如果useValue为true,则调用初始化器并传入value,否则调用初始化器并传入undefined
        value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
    }
    return useValue ? value : void 0;
};


/**
 * // ES 装饰器实现
 * @param {Object} ctor - 类构造函数,判断返回的是实例对象还是类原型
 * @param {Object} descriptorIn - 描述符
 * @param {Array} decorators - 装饰器数组
 * @param {Object} contextIn - 上下文
 * @param {Array} initializers - 初始化器数组
 * @param {Array} extraInitializers - 初始化器数组的引用
 */
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    // 接受一个函数,如果函数不是undefined且不是函数,则抛出TypeError,装饰器需要返回一个函数
    function accept(f) {
        if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
        return f;
    }
    // 获取装饰器的类型
    var kind = contextIn.kind;
    // 获取装饰器的键
    var key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    // 获取装饰器的目标,判断返回的是实例对象还是类原型
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    // 获取装饰器的描述符
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;

    // 遍历装饰器,从后往前遍历,因为装饰器是按顺序应用的,所以需要从后往前遍历
    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        // 遍历装饰器的上下文,如果装饰器的上下文是access,则设置为空对象,否则设置为装饰器的上下文
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        // 给上下文添加addInitializer方法
        context.addInitializer = function (f) {
            // 如果装饰器已经完成,则抛出TypeError
            if (done) throw new TypeError("Cannot add initializers after decoration has completed");
            // 将装饰器添加到extraInitializers中,在新定义的class中会调用这些初始化器(this.name = __runInitializers...)
            extraInitializers.push(accept(f || null));
        };
        // 如果是访问器装饰器,则设置为描述符的get和set,否则设置为描述符的键
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _; // 设置描述符的get
            if (_ = accept(result.set)) descriptor.set = _; // 设置描述符的set
            if (_ = accept(result.init)) initializers.unshift(_); // 设置描述符的init
            if (_ = accept(result.init)) initializers.unshift(_); // 设置描述符的field
        }
        // 声明result的时候已经做了判断,如果是属性装饰器,result是描述符的键
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }
    // 如果目标存在,则设置描述符的键
    if (target) Object.defineProperty(target, contextIn.name, descriptor);
    done = true;
};

// 设置函数名称,函数名会出现在错误堆栈中,或者对于使用到Function.name的代码,能够正确显示函数名
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
    if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
    return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
};

function log(message) {
    return function (target, propertyKey, descriptor) {
        var originalMethod = descriptor.value;
        descriptor.value = function () {
            var args = [];
            for (var _i = 0; _i < arguments.length; _i++) {
                args[_i] = arguments[_i];
            }
            console.log("[".concat(message, "] 调用方法: ").concat(String(propertyKey)));
            console.log("[".concat(message, "] 参数:"), args);
            var result = originalMethod.apply(this, args);
            console.log("[".concat(message, "] 返回值:"), result);
            return result;
        };
        return descriptor;
    };
}

function LogClass(constructor) {
    console.log("\u7C7B ".concat(constructor.name, " \u88AB\u5B9A\u4E49\u4E86"));
}

var User = function () {
    // 类装饰器
    var _classDecorators = [LogClass];
    var _classDescriptor;
    var _classExtraInitializers = [];
    var _classThis;
    var _instanceExtraInitializers = [];
    var _sayHello_decorators;

    // 定义class定义,一个IIFE闭包
    var User = _classThis = /** @class */ (function () {
        function User_1(name, age) {
            // 运行初始化器,(this.name = )操作感觉是多余的,或是一种通用写法,如果有类的属性装饰器,这里就会直接赋值
            this.name = __runInitializers(this, _instanceExtraInitializers);
            this.name = name;
            this.age = age;
        }

        // 将sayHello方法添加到原型上,是因为在__esDecorate内会进行初始化器的注入,所以需要添加到原型上
        User_1.prototype.sayHello = function (word) {
            console.log("".concat(word, ", \u6211\u662F ").concat(this.name, ", \u4ECA\u5E74 ").concat(this.age, " \u5C81"));
        };

        return User_1;
    }());

    // 将构造函数的 name 属性从 "User_1" 改为 "User",对外暴露的时候返回的就是User
    // 比如我们在原类装饰器代码打印constructor.name,如果不重新命名就会变成User_1
    __setFunctionName(_classThis, "User");

    // 应用装饰器
    (function () {
        var _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
        // sayHello方法装饰器数组
        _sayHello_decorators = [log('用户')];

        // 应用方法装饰器,注入初始化器到_instanceExtraInitializers中,在new User的时候会调用这些初始化器(this.name = __runInitializers...),给实例注入成员方法
        __esDecorate(_classThis, null, _sayHello_decorators, {
            kind: "method",
            name: "sayHello",
            static: false,
            private: false,
            access: {
                has: function (obj) { return "sayHello" in obj; },
                get: function (obj) { return obj.sayHello; }
            },
            metadata: _metadata
        }, null, _instanceExtraInitializers);

        // 应用类装饰器
        __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, {
            kind: "class",
            name: _classThis.name,
            metadata: _metadata
        }, null, _classExtraInitializers);

        User = _classThis = _classDescriptor.value;
        // 设置metadata,metadata是装饰器的元数据,用于存储装饰器的元数据
        if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, {
            enumerable: true,
            configurable: true,
            writable: true,
            value: _metadata
        });

        // 运行类装饰器的初始化器
        __runInitializers(_classThis, _classExtraInitializers);
    })();

    // 返回新定义的class
    return User = _classThis;
}();

var user = new User('张三', 25);
user.sayHello('你好');