装饰器
环境
- ts包
npm i typescript
- 初始化tscofng.json配置
tsc --init
并开启装饰器实验功能:
"experimentalDecorators": true /* Enable experimental support for legacy experimental decorators. */,
"emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */,
基本使用
类的装饰器
基本使用形式
// 类装饰器
const moveDecorator: ClassDecorator = function (target: Function) {
console.log("target", target);
target.prototype.move = function (pos: { x: number; y: number }) {
console.log("move");
return pos;
};
};
@moveDecorator
class Tank {
// move() {}
}
const tank1 = new Tank();
console.log((<any>tank1).move({ x: 1, y: 2 })); // { x: 1, y: 2 }
对装饰器的剖析:
- 装饰器类:Dectorator
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
可以看出这个装饰器是一个函数,然后它是继承与Function的,参数接一个函数,而这个函数就类的构造函数
// 类装饰器
const moveDecorator: ClassDecorator = function (target: Function) {
console.log("target", target); // target的类型就是class Tank
};
@moveDecorator
class Tank {
}
- 本质
先看代码
const moveDecorator: ClassDecorator = function (target: Function) {
console.log("target", target);
};
class Tank {
}
moveDecorator(Tank);
这样执行的结果和上面使用**@形式**的结果是一摸一样的
因此装饰器其实就是一个语法糖,让我们更加便捷的来去修饰构造函数原型。并没有什么黑魔法
装饰器的叠加
没什么特殊写法,主需要在类名上面,一层层叠加装饰器函数就可以装饰。
// 类装饰器
const moveDecorator: ClassDecorator = function (target: Function) {
// console.log("target", target);
target.prototype.getPos = function (pos: { x: number; y: number }) {
console.log("move");
return pos;
};
};
const musicDecorator: ClassDecorator = function (target: Function) {
target.prototype.getMusic = function (music: string) {
console.log("paly music");
return music;
};
};
@moveDecorator
@musicDecorator
class Tank {
// move() {}
}
const tank1 = new Tank();
console.log((<any>tank1).getPos({ x: 1, y: 2 })); //{ x: 1, y: 2 }
(<any>tank1).getMusic("music"); //paly music
装饰器工厂
可以通过工厂函数,根据传递不同的类型参数,来返回不同的装饰器。
// 工厂装饰器
const MusicDecoratorFactory = function (type: string) {
switch (type) {
case "Tank":
return function (target: Function) {
target.prototype.getMusic = function (music: string) {
console.log("Tank-music");
return music;
};
};
default:
return function (target: Function) {
target.prototype.getMusic = function (music: string) {
console.log("default-music");
return music;
};
};
}
};
const musicDecorator: ClassDecorator = function (target: Function) {
target.prototype.getMusic = function (music: string) {
console.log("paly music");
return music;
};
};
@MusicDecoratorFactory("Tank")
class Tank {
// move() {}
}
const tank1 = new Tank();
(<any>tank1).getMusic("music"); //tank-music
@MusicDecoratorFactory("dadada")
class SU7 {
// move() {}
}
const su7 = new SU7();
(<any>su7).getMusic("music"); //default-music
方法装饰器
方法装饰器有三个参数
- target:方法所属类的原型
- PropertyKey:方法名称
- descriptor:和对象劫持得到的可配置参数一致
{
value: [Function: showsss],
writable: true,
enumerable: false,
configurable: true
}
- 示例
const showDecorator: MethodDecorator = (target, propertyKey, descriptor) => {
console.log(target); //方法所属类的原型
console.log(target.constructor === Person) //true
console.log(propertyKey); //方法名字 show
console.log(descriptor); //方法本体,也就是方法本身,是一个函数
//执行descriptor.value(),执行方法
(descriptor as any).value = () => {
console.log("showDecorator");
}; // 覆盖方法
(descriptor as any).value(); // 执行方法
};
class Person {
public static staticMethod() {
console.log("staticMethod");
}
@showDecorator
showsss() {
console.log("show");
}
}
const p = new Person();
p.showsss(); //showDecorator
静态方法装饰
与方法装饰器不同的是target是类即构造函数,不再试方法所属类的原型
const showDecorator: MethodDecorator = (
target,
propertyKey,
descriptor: PropertyDescriptor
) => {
console.log(target); //静态方法下target是构造函数
console.log(target === Person); //true
console.log(propertyKey); //方法名字 show
console.log(descriptor); //方法本体,也就是方法本身,是一个函数
//执行descriptor.value(),执行方法
(descriptor as any).value = () => {
console.log("showDecorator");
}; // 覆盖方法
(descriptor as any).value(); // 执行方法
descriptor.writable = false;
};
class Person {
@showDecorator
public static staticMethod() {
console.log("staticMethod");
}
}
Person.staticMethod();
Person.staticMethod = () => {
console.log("staticMethod999");
}; //报错 Cannot assign to read only property 'staticMethod' of function 'class Person
demo:输出高亮
const heightDecorator: MethodDecorator = (
target,
propertyKey,
descriptor: TypedPropertyDescriptor<any>
) => {
const method = descriptor.value;
descriptor.value = () => {
return `<div style="color: red">${method()}</div>`;
};
};
class Demo {
@heightDecorator
public say() {
return "hello";
}
}
console.log(new Demo().say());
延迟执行装饰器
const delayDecoratorFactory =
(delay: number): MethodDecorator =>
(...args: any[]) => {
const [, , descriptor] = args;
const method = descriptor.value;
descriptor.value = () => {
setTimeout(() => {
console.log("delayDecoratorFactory");
method();
}, delay);
};
};
class Test {
@delayDecoratorFactory(1000)
test() {
console.log("test");
}
}
new Test().test();
属性装饰器
和方法装饰器差不多,有两个参数
- 类型
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
- 示例
// 属性装饰器
const ProperDecorator: PropertyDecorator = (
target: Object,
key: string | symbol
) => {
console.log(target === Person.prototype); // true
console.log(target);
console.log(key); // name
};
class Person {
@ProperDecorator
name: string = "张三";
}
console.log(new Person().name);
export {};
demo:属性值自动变小写
// 属性装饰器
const ProperDecorator: PropertyDecorator = (
target: Object,
key: string | symbol
) => {
let name: string;
Object.defineProperty(target, key, {
get() {
return name.toLocaleLowerCase();
},
set(value: string) {
name = value;
},
});
console.log(target === Person.prototype); // true
console.log(target);
console.log(key); // name
};
class Person {
@ProperDecorator
name: string = "DONG";
}
console.log(new Person().name);
export {};
参数装饰器
元数据
- 安装
npm i reflect-metadata
- 使用
- 存储数据:Reflect.defineMetadata()
function defineMetadata(metadataKey: any, metadataValue: any, target: Object, propertyKey: string | symbol): void;
- 获取数据:Reflect.getMetadata()
function getMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): any;
- 检查元数据是否存在
Reflect.hasMetadata(metadataKey, target, propertyKey?)
function hasMetadata(metadataKey: any, target: Object): boolean;
- 获取目标对象自身的元数据:
Reflect.getOwnMetadata(metadataKey, target, propertyKey?)
function getOwnMetadata(metadataKey: any, target: Object): any;
- 检查目标对象自身是否具有元数据(不包括从原型链继承的元数据)
Reflect.hasOwnMetadata(metadataKey, target, propertyKey?)
function hasOwnMetadata(metadataKey: any, target: Object): boolean;
- demo
//存数据
Reflect.defineMetadata("required", requiredParams, target, propertyKey || "");
const requiredParams: number[] =
Reflect.getMetadata("required", target, propertyKey || "") || [];
参数装饰器
和方法装饰器差不多,看类型声明
参数:
- target:方法所属类的原型
- propertyKey:方法的key
- ParameterIndex:参数索引
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) => void;
修饰位置:参数前面
class User {
getUser(@requireDecorator id: number) {
console.log("-------------");
console.log(id);
console.log(this.getUser);
return id;
}
}
对参数传递不对时进行抛出错误
import "reflect-metadata";
// 参数装饰器:用来装饰函数的参数
const requireDecorator: ParameterDecorator = (
target: Object,
propertyKey: string | symbol | undefined,
parameterIndex: number
) => {
// console.log(target === User.prototype); //true
console.log(propertyKey); //getUser 调用的方法名
console.log(parameterIndex); //0 调用的参数索引
// 存放参数索引
const requiredParams: number[] = [];
requiredParams.push(parameterIndex);
// 把参数索引保存到元数据中
Reflect.defineMetadata("required", requiredParams, target, propertyKey || "");
};
// 方法装饰器:用来装饰类的方法
const validate: MethodDecorator = (
target: Object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) => {
// 获取参数索引,从元数据中获取
const requiredParams: number[] =
Reflect.getMetadata("required", target, propertyKey || "") || [];
const method = descriptor.value;
descriptor.value = function (...args: any[]) {
requiredParams.forEach((index) => {
// 当传递的参数为空时,抛出异常
if (args[index] === undefined) {
throw new Error("参数不能为空");
}
});
// 否则的话,正常执行函数,
return method.apply(target, args);
};
return descriptor;
};
class User {
@validate
getUser(@requireDecorator id: number) {
console.log("-------------");
console.log(id);
console.log(this.getUser);
return id;
}
}
const user = new User().getUser(5);