装饰器类型
装饰器的类型有:类装饰器、访问器装饰器、属性装饰器、方法装饰器、参数装饰器,但是没有函数装饰器(function)。
1.类装饰器
应用于类构造函数,其参数是类的构造函数。
注意class并不是像Java那种强类型语言中的类,而是JavaScript构造函数的语法糖。
function addAge(args: number) { return function (target: Function) { target.prototype.age = args; };} @addAge(18)class Hello { name: string; age: number; constructor() { console.log('hello'); this.name = 'yugo'; }} console.log(Hello.prototype.age);//18let hello = new Hello(); console.log(hello.age);//18
2.方法装饰器
它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。
方法装饰会在运行时传入下列3个参数:
- 1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 2、成员的名字。
- 3、成员的属性描述符{value: any, writable: boolean, enumerable: boolean, configurable: boolean}。
function addAge(constructor: Function) { constructor.prototype.age = 18;}function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) { console.log(target); console.log("prop " + propertyKey); console.log("desc " + JSON.stringify(descriptor) + "\n\n");};@addAgeclass Hello{ name: string; age: number; constructor() { console.log('hello'); this.name = 'yugo'; } @method hello(){ return 'instance method'; } @method static shello(){ return 'static method'; }}
我们得到的结果是
Hello { hello: [Function] }prop hellodesc {"writable":true,"enumerable":true,"configurable":true}{ [Function: Hello] shello: [Function] }prop shellodesc {"writable":true,"enumerable":true,"configurable":true}
假如我们修饰的是 hello 这个实例方法,第一个参数将是原型对象,也就是 Hello.prototype。
假如是 shello 这个静态方法,则第一个参数是构造器 constructor。
第二个参数分别是属性名,第三个参数是属性修饰对象。
注意:在vscode编辑时有时会报作为表达式调用时,无法解析方法修饰器的签名。错误,此时需要在tsconfig.json
中增加target
配置项:
{ "compilerOptions": { "target": "es6", "experimentalDecorators": true, }}
3. 访问器装饰器
访问器装饰器应用于访问器的属性描述符,可用于观察,修改或替换访问者的定义。 访问器装饰器不能在声明文件中使用,也不能在任何其他环境上下文中使用(例如在声明类中)。
注意: TypeScript不允许为单个成员装饰get和set访问器。相反,该成员的所有装饰器必须应用于按文档顺序指定的第一个访问器。这是因为装饰器适用于属性描述符,它结合了get和set访问器,而不是单独的每个声明。
访问器装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 成员的属性描述符。
注意 如果代码输出目标版本小于ES5,Property Descriptor将会是undefined。
如果访问器装饰器返回一个值,它会被用作方法的属性描述符。
注意 如果代码输出目标版本小于
ES5
返回值会被忽略。
下面是使用了访问器装饰器(@configurable
)的例子,应用于Point
类的成员上:
class Point { private _x: number; private _y: number; constructor(x: number, y: number) { this._x = x; this._y = y; } @configurable(false) get x() { return this._x; } @configurable(false) get y() { return this._y; }}
我们可以通过如下函数声明来定义@configurable
装饰器:
function configurable(value: boolean) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.configurable = value; };}
4. 方法参数装饰器
参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 2、参数的名字。
- 3、参数在函数参数列表中的索引。
const parseConf = [];class Modal { @parseFunc public addOne(@parse('number') num) { console.log('num:', num); return num + 1; }} // 在函数调用前执行格式化操作function parseFunc(target, name, descriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { for (let index = 0; index < parseConf.length; index++) { const type = parseConf[index]; console.log(type); switch (type) { case 'number': args[index] = Number(args[index]); break; case 'string': args[index] = String(args[index]); break; case 'boolean': args[index] = String(args[index]) === 'true'; break; } return originalMethod.apply(this, args); } }; return descriptor;} // 向全局对象中添加对应的格式化信息function parse(type) { return function (target, name, index) { parseConf[index] = type; console.log('parseConf[index]:', type); };}let modal = new Modal();console.log(modal.addOne('10')); // 11
5. 属性装饰器
属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
- 1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 2、成员的名字。
function log(target: any, propertyKey: string) { let value = target[propertyKey]; // 用来替换的getter const getter = function () { console.log(`Getter for ${propertyKey} returned ${value}`); return value; } // 用来替换的setter const setter = function (newVal) { console.log(`Set ${propertyKey} to ${newVal}`); value = newVal; }; // 替换属性,先删除原先的属性,再重新定义属性 if (delete this[propertyKey]) { Object.defineProperty(target, propertyKey, { get: getter, set: setter, enumerable: true, configurable: true }); }}class Calculator { @log public num: number; square() { return this.num * this.num; }}let cal = new Calculator();cal.num = 2;console.log(cal.square());// Set num to 2// Getter for num returned 2// Getter for num returned 2// 4
装饰器加载顺序

function ClassDecorator() { return function (target) { console.log("I am class decorator"); }}function MethodDecorator() { return function (target, methodName: string, descriptor: PropertyDescriptor) { console.log("I am method decorator"); }}function Param1Decorator() { return function (target, methodName: string, paramIndex: number) { console.log("I am parameter1 decorator"); }}function Param2Decorator() { return function (target, methodName: string, paramIndex: number) { console.log("I am parameter2 decorator"); }}function PropertyDecorator() { return function (target, propertyName: string) { console.log("I am property decorator"); }} @ClassDecorator()class Hello { @PropertyDecorator() greeting: string; @MethodDecorator() greet( @Param1Decorator() p1: string, @Param2Decorator() p2: string) { }}
输出结果:
I am property decoratorI am parameter2 decoratorI am parameter1 decoratorI am method decoratorI am class decorator
从上述例子得出如下结论:
-
有多个参数装饰器时:从最后一个参数依次向前执行
-
方法和方法参数中参数装饰器先执行。
-
类装饰器总是最后执行。
-
方法和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所以参数会一直紧紧挨着方法执行。
上述例子中属性和方法调换位置,输出如下结果:
I am parameter2 decoratorI am parameter1 decoratorI am method decoratorI am property decoratorI am class decorator
网友评论