什么是装饰器
装饰器是一种特殊类型的声明,他可以用在类声明、方法、属性或者参数上。顾名思义,它是用来给附着的主体进行装饰,添加额外的行为。装饰器使用 @expression 这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息作为参数传入
装饰器的分类与执行顺序
@log2 // 类装饰器
@log1 // 类装饰器
class Test {
private _age: number = 0;
@nameDecorator // 属性装饰器
public name: string = "";
@sayDecorator // 方法装饰器
public say(
@paramWhoDecorator who: string, // 参数装饰器
@paramAnythingDecorator anything: string // 参数装饰器
) {
console.log(`${who}${anything}`);
}
@ageDecorator //访问器装饰器
get age() {
return this._age + 1;
}
set age(v: number) {
this._age = v;
}
@staticDecorator //方法装饰器
static getSomething() {
return "getSomething";
}
}
每种类型的装饰器调用顺序不同的,具体如下:
1.参数装饰器,然后依次是方法装饰器,访问器装饰器,或属性装饰器应用到每个实例成员(即类原型的成员)
2.参数装饰器,然后依次是方法装饰器,访问器装饰器,或属性装饰器应用到每一个静态成员
3.参数装饰器应用到构造函数(即类原型)
4.类装饰器应用到类
例如上述的 say 方法的装饰器:
@paramAnythingDecorator -> @paramWhoDecorator -> @sayDecorator -> @nameDecorator -> @log1
类装饰器
我们可以看一下类装饰器在 typescript 中的定义
declare type ClassDecorator = <TFunction extends Function>(
target: TFunction
) => TFunction | void;
从ts的底层定义上,类装饰器只有一个参数,而且这个参数是装饰类的构造函数
function log(constructor: any) {
// 重写类中的方法
constructor.prototype.getName = () => {
console.log("getNameOverride");
}; // 类新增方法
constructor.prototype.getAge = () => {
console.log("getAge");
}; // 类新增属性
constructor.prototype.age = "age";
constructor.prototype.name = "xx";
}
@log
class Test {
name: string;
constructor(name: string) {
this.name = name;
}
getName() {
console.log("getName");
}
}
const test = new Test("name");
test.getName(); // 可以打印出 getNameOverride
test.getAge();
console.log(test.age); // 可以打印出 age
console.log(test.name);
// 打印的是name,而不是xx,通过装饰器是装饰到类的原型上,所以此处test.__proto__.name打印的才是xx
简单实现一个混合的 mixin
class A {
getName() {
return "name";
}
}
class B {
getAge() {
return 18;
}
}
@mixin(A, B)
class C {}
function mixin(...args: any[]) {
// 调用函数返回装饰器实际应用的函数
return function (constructor: any) {
for (let arg of args) {
for (let key of Object.getOwnPropertyNames(arg.prototype)) {
if (key === "constructor") continue; // 跳过构造函数
Object.defineProperty(
constructor.prototype,
key, // @ts-ignore
Object.getOwnPropertyDescriptor(arg.prototype, key)
);
}
}
};
}
let c = new C(); // @ts-ignore
console.log(c.getName(), c.getAge()); // 1, 2
方法装饰器
我们可以看一下类装饰器在 ts 中的定义:
declare type MethodDecorator = <T>(
target: Object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;
方法装饰器的以下三个参数:
1.target:如果是普通方法,那么此时的 target 为类的原型。如果为静态方法,target 对应的是类的构造函数
2.propertyKey:为方法的名字
3.descript:PropertyDescriptor 类型
function getNameDescorator(
target: any,
key: string,
descriptor: PropertyDescriptor
) {
// 1、通过descriptor修改当前方法的属性,不能被重写
descriptor.writable = false;
}
class Test {
name: string;
constructor(name: string) {
this.name = name;
}
@getNameDescorator
getName() {
return this.name;
}
}
const test = new Test("name");
(test as any).getName = () => {
// 修改此方法就会报错 return 'xx'
};
简单实现一个异步捕获的逻辑,这样可以在多处使用
访问器装饰器
访问器主要用于 get 以及 set 前缀的函数,用于控制属性的肤质及取值操作,在使用上与函数装饰器没什么区别,甚至在返回值的处理上也没有什么区别。并且 ts不允许同时装饰一个成员的 get 和 set 访问器
function prefix(target: any, key: string, descorator: PropertyDescriptor) {
descorator.get = function () {
// @ts-ignore
return `prefix_${this._name}`;
};
}
class Test {
_name: string = "xx";
@prefix
get name() {
return this._name;
}
set name(name: string) {
this._name = name;
}
}
const test = new Test();
console.log(test.name); // 输出: prefix_xx
属性装饰器
declare type PropertyDecorator = (
target: Object,
propertyKey: string | symbol
) => void;
属性装饰器拥有两个参数:
1.target:当前对象的原型
2.propertyKey:属性的名称
参数装饰器
declare type ParameterDecorator = (
target: Object,
propertyKey: string | symbol,
parameterIndex: number
) => void;
参数装饰器有三个参数:
1.target 当前对象的原型
2.method 当前方法的名称
3.paramIndex 当前参数对于的索引