AwesomeDevin / blog

Welcome to Devin's blog,I'm trying to be a fullstack developer and sticking with it !!!

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

【javascript】Typescript 装饰器 底层原理分析

AwesomeDevin opened this issue · comments

装饰器种类

// 方法装饰器 - 如果方法装饰器返回一个值,它会被用作方法的属性描述符。
function decorateMethod(target: any,key: string,value: any){
    return{
        value: function(...args: any[]){
            console.log('target',target) // 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
            console.log('target === C.prototype',target === C.prototype) // true
            console.log('key',key) // 方法名
            console.log('value',value)  // 成员的属性描述符 Object.getOwnPropertyDescriptor
            console.log('args',args)
            var a = args.map(a => JSON.stringify(a)).join();
            var result = value.value.apply(this, args);
            var r = JSON.stringify(result);
            console.log(`Call: ${key}(${a}) => ${r}`);
            return result;
        }
    }
}

// 类装饰器  - 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
function decorateClass(str: string){
    return function(constructor: Function){ 
        console.log(constructor) // 参数为类的构造函数
    }
}

// 访问器装饰器 - 如果访问器装饰器返回一个值,它会被用作方法的属性描述符。
function configurable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.configurable = value;
    };
}

// 装饰器工厂 - 闭包
function format(formatString: string) {
    return Reflect.metadata(formatMetadataKey, formatString);
}

// 属性装饰器 - 返回值也会被忽略。因此,属性描述符只能用来监视类中是否声明了某个名字的属性
function getFormat(target: any, propertyKey: string) {
     console.log('target') //对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
     console.log('propertyKey') // 属性名
    return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

// 参数装饰器 - 参数装饰器的返回值会被忽略。
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
     console.log('target') // //对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
     console.log('propertyKey') // 参数名
     console.log('parameterIndex')  // 参数在函数参数列表中的索引。
    let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
    existingRequiredParameters.push(parameterIndex);
    Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}


@decorateClass('decorateClass')
class C{

		@format("Hello, %s")
    great: string
    
    @decorateMethod('decorateMethod')
    sum(@required x: number,y: number){
        return x + y
    }

    @configurable(false)
    get x() { return this._x; }
}

方法日志装饰器底层实现 - __decorate()

首先通过ast将我们针对相同成员的装饰器处理为一个数组,ts规范规定装饰器工厂函数从上至下开始执行装饰器函数从下至上开始执行,__decorate() 会通过reduce.right从右到左依次执行这个数组中的函数。基于ts的规范不同的装饰器提供的参数不同,所以ts代码被编译成js代码后,__decorate 需要根据入参数长度进行处理,执行不同的装饰器函数时传入不同的参数。

方法装饰器函数以及访问器装饰器有返回值会将返回值通过defineProperty设置为函数本身的value,函数没有返回值的话,则通过 Object.getOwnPropertyDescriptor 获取函数自身的 value,并通过defineProperty设置属性value,之所以会有defineProperty的步骤,是为了方便某些场景我们在装饰器中需要与函数本身进行耦合,如:log及报错捕捉。通过defineProperty,装饰器的逻辑可以做到与函数本身的逻辑进行耦合。

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;
    console.log('r',r)
    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;
    console.log(c)
    return c > 3 && r && Object.defineProperty(target, key, r), r;
  };

var __metadata =
  (this && this.__metadata) ||
  function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
      return Reflect.metadata(k, v);
  };

var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
// You could think of it in three ways:
//
//  - A place to learn TypeScript in a place where nothing can break
//  - A place to experiment with TypeScript syntax, and share the URLs with others
//  - A sandbox to experiment with different compiler features of TypeScript
// To learn more about the language, click above in "Examples" or "What's New".
// Otherwise, get started by removing these comments and the world is your playground.
// 方法装饰器 - 如果方法装饰器返回一个值,它会被用作方法的属性描述符。
function decorateMethod(str) {
    return function (target, key, value) {
        return {
            value: function (...args) {
                console.log('decorateMethod', str); // decorateMethod
                console.log('target', target); // 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
                console.log('target === C.prototype', target === C.prototype); // true
                console.log('key', key); // 方法名
                console.log('value', value); // 成员的属性描述符 Object.getOwnPropertyDescriptor
                console.log('args', args);
                var a = args.map(a => JSON.stringify(a)).join();
                var result = value.value.apply(this, args);
                var r = JSON.stringify(result);
                console.log(`Call: ${key}(${a}) => ${r}`);
                return result;
            }
        };
    };
}
// 类装饰器  - 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
function decorateClass(str) {
    return function (constructor) {
        console.log(constructor); // 参数为类的构造函数
    };
}
// 访问器装饰器 - 如果访问器装饰器返回一个值,它会被用作方法的属性描述符。
function configurable(value) {
    return function (target, propertyKey, descriptor) {
        descriptor.configurable = value;
    };
}
// 装饰器工厂 - 闭包
function format(formatString) {
    return Reflect.metadata('formatMetadataKey', formatString);
}
// 属性装饰器 - 返回值也会被忽略。因此,属性描述符只能用来监视类中是否声明了某个名字的属性
function getFormat(target, propertyKey) {
    console.log('target'); //对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    console.log('propertyKey'); // 属性名
    return Reflect.getMetadata('formatMetadataKey', target, propertyKey);
}
// 参数装饰器 - 参数装饰器的返回值会被忽略。
function required(target, propertyKey, parameterIndex) {
    console.log('target'); // //对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
    console.log('propertyKey'); // 参数名
    console.log('parameterIndex'); // 参数在函数参数列表中的索引。
}
let C = class C {
    constructor() {
        this.str = 'string';
    }
    sum(x, y) {
        return x + y;
    }
    get x() { return this.str; }
};
__decorate([
    format("Hello, %s"),
    __metadata("design:type", String)
], C.prototype, "str", void 0);
__decorate([
    decorateMethod('decorateMethod'),
    __param(0, required),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number, Number]),
    __metadata("design:returntype", void 0)
], C.prototype, "sum", null);
__decorate([
    configurable(false),
    __metadata("design:type", Object),
    __metadata("design:paramtypes", [])
], C.prototype, "x", null);
C = __decorate([
    decorateClass('decorateClass'),
    __metadata("design:paramtypes", [])
], C);
console.log(new C().sum(1, 2)); // 3