文章目录
Decorator 装饰器就是一个函数,前缀是 @ ,后跟函数表达式(或执行后返回一个函数),是一种特殊类型的声明,它能够被附加类声明、方法、属性、或者参数上也可以去修改类的行为。
装饰器会在代码加载阶段执行,而不是在运行时执行,而且只会执行一次。所以装饰器对类的行为的改变,实际上发生在编译阶段。TypeScript 装饰器能在编译阶段运行代码,其本质上就是编译时执行的函数。
使用装饰器需要在配置文件中配置 "experimentalDecorators": true
类装饰器
类装饰器应用于类(class),但实际上是应用于类的构造方法。
类装饰器有唯一参数,就是构造方法,可以在装饰器内部,对构造方法进行各种改造。
如果类装饰器有返回值,就会替换掉原来的构造方法。
例1:下面示例中,装饰器 @ClassDecorators 返回一个新的类,替代了原来的类。
// 类装饰器
function ClassDecorators(constructor: Function) {
console.log(constructor);
// class User {
// constructor() {
// this.name = '小七';
// this.age = 20;
// }
// }
return class extends User { // 替换类
name: string = '小九';
age: number = 18;
}
}
@ClassDecorators class User{
name: string;
age: number;
constructor() {
this.name = '小七';
this.age = 20;
}
}
let a = new User();
console.log(a.name); // 小九
console.log(User); // 替换后的类
// class extends User {
// constructor() {
// super(...arguments);
// this.name = '小九';
// this.age = 18;
// }
// }
例2:下面示例中,装饰器 @sealed (没有返回值) 会锁定 User 这个类,使得它无法新增或删除静态成员和实例成员。
// 类装饰器
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed class User {
name: string;
age: number = 18;
constructor(name: string) {
this.name = name;
}
}
【工厂装饰器】
如果除了构造方法,类装饰器还需要其他参数,可以采取“工厂模式”,即把装饰器写在一个函数里面,该函数可以接受其他参数,执行后返回装饰器。
例2:下面示例中,函数 Factory() 的返回值才是装饰器函数,所以加载装饰器函数的时候,要先执行一次 Factory('小七') ,才能得到装饰器函数。
// 工厂装饰器
function Factory(params: string) {
console.log(params); // 小七
return function (target: any) { // 返回装饰器
console.log(target); // 替换前的类
return class extends target { // 替换类
value = params;
};
}
}
@Factory('小七') class A {
value:string = 'abc';
}
let a = new A();
console.log(a.value); // 小七
属性装饰器
属性装饰器接收两个参数:
- target:装饰属性所在的类;
- propertyName:装饰属性的属性名。
属性装饰器不需要返回值,如果有的话,会被忽略。
下面示例中,propertyDecorators 的返回值才是装饰器函数。
// 属性装饰器
function propertyDecorators(params: any) {
return (target: any, propertyName: string | symbol) => {
// 装饰器传递的参数
console.log(params);
// 装饰器装饰属性所在的类
console.log(target);
// 装饰器的属性名
console.log(propertyName);
target[propertyName] = params;
}
}
class User {
// 属性装饰
@propertyDecorators('小九')
name: string | undefined; // 实例化时没有值为undefined
@propertyDecorators(18)
age: number | undefined; // 实例化时没有值为undefined
}
let b = new User();
console.log(b.name, b.age); // 小九 18
方法装饰器
方法装饰器接收三个参数:
- target:装饰方法所在的类;
- methodName:装饰方法的方法名;
- descriptor:装饰方法的描述对象。
如果方法装饰器有返回值,会作为该方法新的描述对象。
// 方法装饰器
function functionDecorators(params: any) {
return (target: any, methodName: string | symbol, desc: any) => {
// 装饰器传递的参数
console.log(params);
// 装饰器装饰方法所在的类
console.log(target);
// 装饰器的方法名
console.log(methodName);
// 装饰器装饰方法的描述对象
console.log(desc);
desc.value = function () {
console.log(params);
}
}
}
class User {
@functionDecorators('装饰器')
hobby(): void {
console.log("学习");
}
}
let u = new User();
u.hobby(); // 装饰器
存取装饰器
存取器装饰器接收三个参数:
- target:装饰器所在的类;
- propertyKey:存取器的属性名;
- descriptor:存取器的属性描述对象。
如果存取器装饰器有返回值,会作为该属性新的描述对象。
装饰器不能同时用于同一个属性的存值器和取值器,原因是装饰器可以从属性描述对象上面,同时拿到取值器和存值器
下面示例中,存取装饰器会实现 x 在设置值的基础上加一。
// 存取装饰器
function setDecorators() {
return (target: any, propertyKey: string, descriptor: any) => {
// 存取器所在的类
console.log(target);
// 存取器的属性名
console.log(propertyKey); // x
// 存取器的描述对象
console.log(descriptor);
const originalSet = descriptor.set;
descriptor.set = function (val: number) { // 重写set方法
console.log(this); // 对象方法中的this指向当前对象 Point {}
originalSet.call(this, val+1); // 设置的值加1
}
return descriptor; // 该属性新的描述对象
};
}
class Point {
#x!: number;
@setDecorators()
set x(val: number) {
this.#x = val;
}
get x(){
return this.#x
}
}
let p = new Point();
p.x = 1;
console.log(p.x); // 2
参数装饰器
参数装饰器接受三个参数:
- target:装饰器所在的类;
- propertyKey:装饰方法的名字;
- parameterIndex:当前参数在方法的参数序列的位置(从0开始)。
参数装饰器不需要返回值,如果有的话,会被忽略。
跟其他装饰器不同,参数装饰器主要用于输出信息,没有办法修改类的行为。
下面示例中,参数装饰器会输出参数的位置序号,后面的参数会先输出。
// 参数装饰器
function paramDecorators(target: any, propertyKey: string | symbol, paramIndex: number) {
console.log(propertyKey, paramIndex); // position 1 // position 2
}
class Point {
position(@paramDecorators x: number, @paramDecorators y: number) {
console.log(x, y); // 2 4
}
}
const p = new Point();
p.position(2, 4);