在 Node.js 里,装饰器是一项极为实用的语法糖,它能够对类、方法、属性等进行元编程,从而在不改变原有代码结构的前提下,为其添加额外功能。
装饰器本质上是一种特殊的函数,其返回值是一个用于修改类、方法、属性等目标对象的函数。借助@装饰器名
这种语法,我们可以将其应用到目标对象上。
类装饰器
类装饰器用于修改类的定义,它接收构造函数作为参数。下面是一个类装饰器的示例:
javascript
function logClass(constructor: Function) {
console.log(`Class ${constructor.name} created`);
// 可以通过修改 prototype 来添加新的属性或方法
constructor.prototype.log = function() {
console.log(`Instance of ${constructor.name}`);
};
}
@logClass
class MyClass {
constructor() {}
}
const instance = new MyClass();
// 可以调用装饰器添加的方法
(instance as any).log(); // 输出 "Instance of MyClass"
方法装饰器
方法装饰器用于修改类的方法,它接收三个参数:目标对象、方法名和方法描述符。以下是方法装饰器的示例:
typescript
function logMethod(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling method ${propertyKey} with args: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`Method ${propertyKey} returned: ${result}`);
return result;
};
return descriptor;
}
class Calculator {
@logMethod
add(a: number, b: number) {
return a + b;
}
}
const calc = new Calculator();
calc.add(1, 2);
// 输出:
// Calling method add with args: [1,2]
// Method add returned: 3
在方法装饰器中,三个参数分别是目标对象(target) 、属性名(propertyKey)和属性描述符(descriptor) 。这三个参数构成了装饰器修改类方法的基础,下面详细解释它们的概念和用途。
1. 目标对象(Target)
-
概念 :
目标对象是类的原型(prototype) (对于实例方法)或类的构造函数本身(对于静态方法)。它是装饰器应用的对象,通过它可以访问和修改类的原型链。
-
作用:
- 修改类的原型,添加新属性或方法。
- 获取或设置类的现有属性。
- 实现依赖注入等功能。
示例:实例方法 vs 静态方法
less
class Example {
// 实例方法装饰器:target 是 Example.prototype
@log
instanceMethod() {}
// 静态方法装饰器:target 是 Example 构造函数
@log
static staticMethod() {}
}
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('Target:', target === Example.prototype ? 'Prototype' : 'Constructor');
}
2. 属性名(PropertyKey)
-
概念 :
属性名是一个字符串或符号(Symbol) ,表示被装饰方法的名称。通过它可以明确知道装饰器应用于哪个方法。
-
作用:
- 在运行时动态获取方法名称。
- 结合反射 API(如
Reflect.metadata
)存储或读取元数据。
示例:记录方法名称
typescript
function methodNameLogger(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(`Method "${propertyKey}" was decorated`);
// 输出:"Method "greet" was decorated"
}
class Greeter {
@methodNameLogger
greet(name: string) {
return `Hello, ${name}`;
}
}
3. 属性描述符(PropertyDescriptor)
-
概念 :
属性描述符是一个对象 ,定义了方法的配置信息,与
Object.defineProperty()
中的描述符格式一致。它包含以下属性:value
:方法的实现(函数体)。writable
:是否可修改方法实现。enumerable
:是否可枚举(如for...in
循环)。configurable
:是否可删除或重新定义。get/set
:存取器属性(如果是访问器方法)。
-
作用:
- 修改方法行为 :通过重写
descriptor.value
来拦截方法调用。 - 配置方法特性:控制方法的可写性、可枚举性等。
- 实现访问控制 :通过
getter/setter
实现属性的访问拦截。
- 修改方法行为 :通过重写
示例:修改方法描述符
typescript
function readonly(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// 禁止修改方法实现
descriptor.writable = false;
// 可选:修改方法行为
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Readonly method "${propertyKey}" called`);
return originalMethod.apply(this, args);
};
return descriptor;
}
class Calculator {
@readonly
add(a: number, b: number) {
return a + b;
}
}
const calc = new Calculator();
calc.add = () => 0; // TypeError: Cannot assign to read only property 'add'
属性装饰器
属性装饰器用于修改类的属性,它接收目标对象和属性名作为参数。示例如下:
ini
function uppercase(
target: any,
propertyKey: string
) {
let value = target[propertyKey];
const getter = () => value;
const setter = (newVal: string) => {
value = newVal.toUpperCase();
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
class Person {
@uppercase
name: string;
constructor(name: string) {
this.name = name;
}
}
const person = new Person("john");
console.log(person.name); // 输出 "JOHN"
person.name = "doe";
console.log(person.name); // 输出 "DOE"
参数装饰器
参数装饰器用于获取方法参数的元数据,它接收目标对象、方法名和参数索引作为参数。示例如下:
typescript
import 'reflect-metadata';
// 元数据键
const METADATA_KEY = 'validation:rules';
// 参数验证装饰器
function Validate(rule: (value: any) => boolean) {
return function(target: any, propertyKey: string, parameterIndex: number) {
// 获取现有规则或初始化空数组
const rules: { index: number; rule: (value: any) => boolean }[] =
Reflect.getMetadata(METADATA_KEY, target, propertyKey) || [];
// 添加新规则
rules.push({ index: parameterIndex, rule });
// 存储规则元数据
Reflect.defineMetadata(METADATA_KEY, rules, target, propertyKey);
};
}
// 方法拦截器:验证参数
function validateParams(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
// 获取参数验证规则
const rules: { index: number; rule: (value: any) => boolean }[] =
Reflect.getMetadata(METADATA_KEY, target, propertyKey) || [];
// 验证每个参数
for (const { index, rule } of rules) {
if (!rule(args[index])) {
throw new Error(`Validation failed for parameter at index ${index} of method "${propertyKey}"`);
}
}
// 执行原方法
return originalMethod.apply(this, args);
};
return descriptor;
}
class UserService {
@validateParams
createUser(
@Validate(name => typeof name === 'string' && name.length > 0) name: string,
@Validate(age => typeof age === 'number' && age > 0) age: number
) {
return { name, age };
}
}
// 使用示例
const service = new UserService();
service.createUser('Alice', 30); // 正常执行
service.createUser('', 0); // 抛出错误