一、装饰器简介
装饰器是一种特殊类型的声明,用于注释类、方法、访问器、属性或参数。装饰器使用 @expression
这种形式,其中 expression
必须求值为一个函数,该函数会在运行时被调用。
装饰器本质上是一个函数,它可以在类声明、方法、访问器、属性或参数上使用。装饰器的主要作用是扩展或修改被装饰的目标的行为。
二、类装饰器
类装饰器是一个函数,它接收一个类的构造函数作为参数,并可以返回一个新的类构造函数来替换原来的类。
// 定义一个类装饰器函数,接收类的构造函数作为参数
function Demo(target: Function) {
console.log(target); // 输出被装饰的类构造函数
}
// 使用 @Demo 装饰器
@Demo
class Person {
}
// 当 Person 类被定义时,Demo 函数会被立即执行
应用举例:重写 toString
方法并封闭原型对象。
// 定义一个类装饰器,用于重写 toString 方法并封闭原型对象
function CustomString(target: Function) {
// 向类的原型上添加自定义的 toString 方法
target.prototype.toString = function () {
return JSON.stringify(this); // 返回对象的 JSON 字符串表示
};
// 封闭原型对象,禁止随意修改
Object.seal(target.prototype);
}
// 使用 @CustomString 装饰器
@CustomString
class Person {
constructor(public name: string, public age: number) {
}
}
// 测试代码
const p1 = new Person('张三', 18);
console.log(p1.toString()); // 输出:{"name":"张三","age":18}
关于返回值:类装饰器可以返回一个新的类来替换原类。
// 定义一个类装饰器,返回一个新类
function demo(target: Function) {
// 返回一个新类,替换原类
return class {
test() {
console.log(200); // 新类的方法
}
};
}
// 使用 @demo 装饰器
@demo
class Person {
test() {
console.log(100); // 原类的方法
}
}
// 测试代码
console.log(new Person().test()); // 输出:200
三、装饰器工厂
装饰器工厂是一个返回装饰器函数的函数,允许为装饰器传递参数。
// 定义一个装饰器工厂,接收一个参数 n
function LogInfo(n: number) {
// 返回一个类装饰器函数
return function (target: Function) {
// 修改类的原型,添加 introduce 方法
target.prototype.introduce = function () {
for (let i = 0; i < n; i++) {
console.log(`我的名字:${
this.name},我的年龄:${
this.age}`); // 循环输出 n 次
}
};
};
}
// 使用 @LogInfo 装饰器,传递参数 3
@LogInfo(3)
class Person {
constructor(public name: string, public age: number) {
}
}
// 测试代码
const p1 = new Person('张三', 18);
p1.introduce(); // 输出三次:我的名字:张三,我的年龄:18
四、装饰器组合
装饰器可以组合使用,执行顺序为:先由上到下执行装饰器工厂,再由下到上执行装饰器。
// 装饰器函数
function test1(target: Function) {
console.log('test1');
}
// 装饰器工厂
function test2() {
console.log('test2工厂');
return function (target: Function) {
console.log('test2');
};
}
// 装饰器工厂
function test3() {
console.log('test3工厂');
return function (target: Function) {
console.log('test3');
};
}
// 装饰器函数
function test4(target: Function) {
console.log('test4');
}
// 组合使用装饰器
@test1
@test2()
@test3()
@test4
class Person {
}
// 输出顺序:
// test2工厂
// test3工厂
// test4
// test3
// test2
// test1
五、属性装饰器
属性装饰器用于装饰类的属性,可以监视属性的修改。
// 定义一个属性装饰器,用于监视属性的修改
function State(target: object, propertyKey: string) {
// 存储属性的内部值
const key = `__${
propertyKey}`;
// 使用 Object.defineProperty 替换类的原始属性
Object.defineProperty(target, propertyKey, {
get() {
return this[key]; // 获取属性值
},
set(newVal: string) {
console.log(`${
propertyKey}的最新值为:${
newVal}`); // 输出属性的新值
this[key] = newVal; // 设置属性值
},
enumerable: true, // 允许枚举
configurable: true, // 允许配置
});
}
// 使用 @State 装饰器
class Person {
@State age: number; // 被装饰的属性
constructor(public name: string, age: number) {
this.name = name;
this.age = age; // 触发 setter 方法
}
}
// 测试代码
const p1 = new Person('张三', 18);
p1.age = 20; // 输出:age的最新值为:20
六、方法装饰器
方法装饰器用于装饰类的方法,可以在方法执行前后添加额外逻辑。
// 定义一个方法装饰器,用于在方法执行前后添加日志
function Logger(target: object, propertyKey: string, descriptor: PropertyDescriptor) {
// 保存原始方法
const original = descriptor.value;
// 替换原始方法
descriptor.value = function (...args: any[]) {
console.log(`${
propertyKey}开始执行......`); // 执行前日志
const result = original.call(this, ...args); // 调用原始方法
console.log(`${
propertyKey}执行完毕......`); // 执行后日志
return result;
};
}
// 使用 @Logger 装饰器
class Person {
@Logger
speak() {
console.log(`你好,我的名字:${
this.name},我的年龄:${
this.age}`); // 原始方法逻辑
}
}
// 测试代码
const p1 = new Person('张三', 18);
p1.speak();
七、访问器装饰器
访问器装饰器用于装饰类的访问器(getter 和 setter),可以限制设置的值范围。
// 定义一个访问器装饰器,用于限制设置的值范围
function RangeValidate(min: number, max: number) {
return function (target: object, propertyKey: string, descriptor: PropertyDescriptor) {
// 保存原始的 setter 方法
const originalSetter = descriptor.set;
// 替换 setter 方法,加入范围验证逻辑
descriptor.set = function (value: number) {
if (value < min || value > max) {
throw new Error(`${
propertyKey}的值应该在 ${
min} 到 ${
max}之间!`); // 验证失败抛出错误
}
if (originalSetter) {
originalSetter.call(this, value); // 调用原始 setter 方法
}
};
};
}
// 使用 @RangeValidate 装饰器
class Weather {
private _temp: number;
constructor(temp: number) {
this._temp = temp;
}
@RangeValidate(-50, 50)
set temp(value: number) {
this._temp = value;
}
get temp() {
return this._temp;
}
}
// 测试代码
const w1 = new Weather(25);
w1.temp = 67; // 抛出错误:temp的值应该在 -50 到 50之间!
八、参数装饰器
参数装饰器用于装饰方法的参数,可以限制参数的类型。
// 定义一个参数装饰器,用于标记参数不能为数字
function NotNumber(target: any, propertyKey: string, parameterIndex: number) {
// 初始化或获取当前方法的参数索引列表
let notNumberArr: number[] = target[`__notNumber_${
propertyKey}`] || [];
notNumberArr.push(parameterIndex); // 添加当前参数索引
target[`__notNumber_${
propertyKey}`] = notNumberArr; // 存储回目标对象
}
// 定义一个方法装饰器,用于验证参数类型
function Validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
descriptor.value = function (...args: any[]) {
// 获取被标记为不能为数字的参数索引列表
const notNumberArr: number[] = target[`__notNumber_${
propertyKey}`] || [];
// 检查参数类型
for (const index of notNumberArr) {
if (typeof args[index] === 'number') {
throw new Error(`方法 ${
propertyKey} 中索引为 ${
index} 的参数不能是数字!`); // 验证失败抛出错误
}
}
// 调用原始方法
return method.apply(this, args);
};
}
// 使用 @Validate 和 @NotNumber 装饰器
class Student {
name: string;
constructor(name: string) {
this.name = name;
}
@Validate
speak(@NotNumber message1: any, message2: any) {
console.log(`${
this.name}想对说:${
message1},${
message2}`); // 原始方法逻辑
}
}
// 测试代码
const s1 = new Student("张三");
s1.speak(100, 200); // 抛出错误:方法 speak 中索引为 0 的参数不能是数字!