Appearance
装饰器(Decorator)
reflect-metadata
基本概念
reflect-metadata 是实现元编程的库,提供元数据反射API,可以在运行时检查和操作对象的元数据。
元数据(Metadata)是描述数据的数据,在 TypeScript 中,元数据可以附加到类、方法、属性等上,用于在运行时获取类型信息、配置信息等。
安装和导入
bash
npm install reflect-metadatatypescript
import 'reflect-metadata'主要 API
1. 定义元数据
装饰器方式 - @Reflect.metadata
typescript
class PersonClass {
@Reflect.metadata('methodName', 'methodValue')
method() {
console.log('method');
}
}直接定义 - Reflect.defineMetadata
typescript
const person = new PersonClass('property');
// 语法:Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey?)
Reflect.defineMetadata('name', 'fan', person, 'property');💡
@Reflect.metadata是Reflect.defineMetadata的语法糖,用于简化元数据定义过程。
2. 检查元数据
判断是否存在元数据
typescript
// 语法:Reflect.hasMetadata(metadataKey, target, propertyKey?)
const hasMetadata = Reflect.hasMetadata('name', person, 'property');
console.log('判断是否有元数据:', hasMetadata); // true3. 获取元数据
获取元数据(包括继承的)
typescript
// 语法:Reflect.getMetadata(metadataKey, target, propertyKey?)
const metadata = Reflect.getMetadata('name', person, 'property');
console.log('获取元数据:', metadata); // 'fan'获取自有元数据(不包括继承的)
typescript
// 语法:Reflect.getOwnMetadata(metadataKey, target, propertyKey?)
const ownMetadata = Reflect.getOwnMetadata('name', person, 'property');
console.log('获取自有元数据:', ownMetadata); // 'fan'
// 获取方法上的元数据需要通过原型
const methodMetadata = Reflect.getOwnMetadata('methodName', PersonClass.prototype, 'method');
console.log('方法元数据:', methodMetadata); // 'methodValue'4. 删除元数据
typescript
// 语法:Reflect.deleteMetadata(metadataKey, target, propertyKey?)
Reflect.deleteMetadata('name', person, 'property');
const hasMetadataAfterDelete = Reflect.hasMetadata('name', person, 'property');
console.log('删除后是否存在:', hasMetadataAfterDelete); // false其他常用 API
获取元数据键列表
typescript
// 获取所有元数据键(包括继承的)
const metadataKeys = Reflect.getMetadataKeys(target, propertyKey);
// 获取自有元数据键(不包括继承的)
const ownMetadataKeys = Reflect.getOwnMetadataKeys(target, propertyKey);获取所有元数据
typescript
// 获取所有元数据键值对
const allMetadata = Reflect.getOwnMetadata(Symbol.for('custom:annotations'), target);实际应用场景
1. 依赖注入
typescript
@Injectable()
class UserService {
constructor(
@Inject('DATABASE') private db: Database
) {}
}2. 路由装饰器
typescript
@Controller('/users')
class UserController {
@Get('/:id')
@UseGuards(AuthGuard)
getUserById(@Param('id') id: string) {
// ...
}
}3. 验证装饰器
typescript
class CreateUserDto {
@IsString()
@Length(2, 20)
name: string;
@IsEmail()
email: string;
}注意事项
target 参数:
- 对于实例属性:使用实例对象
- 对于静态属性:使用类构造函数
- 对于方法:使用类的原型
Class.prototype
继承关系:
getMetadata会查找继承链上的元数据getOwnMetadata只查找对象自身的元数据
性能考虑:元数据操作有一定性能开销,避免在热点代码中频繁使用
在 NestJS 中的应用
NestJS 大量使用 reflect-metadata 来实现:
- 装饰器元数据存储
- 依赖注入容器
- 路由元数据管理
- 管道、守卫、拦截器的元数据
类装饰器
类装饰器是应用于类构造函数的特殊函数,可以用来监视、修改或替换类的定义。类装饰器在类声明之前被声明,紧挨着类声明。
基本语法
类装饰器是一个函数,接收一个参数:类的构造函数。
typescript
function logClass(constructor: Function) {
console.log(`Class Created`, constructor.name);
}
@logClass
class HttpClient {
constructor() {
console.log('HttpClient constructor');
}
}
new HttpClient();
// 输出:
// Class Created HttpClient
// HttpClient constructor装饰器工厂
装饰器工厂是返回装饰器函数的高阶函数,可以接收参数来控制装饰器的行为。
typescript
function logClassWithParams(params: string) {
return function (constructor: Function) {
console.log(`Class Created`, constructor.name, params);
}
}
@logClassWithParams('https://www.xyu.fan')
class HttpClient2 {
constructor() {
console.log('HttpClient2 constructor');
}
}
new HttpClient2();
// 输出:
// Class Created HttpClient2 https://www.xyu.fan
// HttpClient2 constructor扩展类功能
类装饰器可以向类的原型添加新的属性和方法。
typescript
function addProperty(target: any) {
target.prototype.url = 'https://www.xyu.fan';
}
// 使用接口声明合并来提供类型支持
interface HttpClient3 {
url: string;
}
@addProperty
class HttpClient3 {
constructor() {
console.log('HttpClient3 constructor');
}
}
const httpClient3 = new HttpClient3();
console.log(httpClient3.url); // 输出:https://www.xyu.fan重写类构造函数
类装饰器可以返回一个新的构造函数来替换原有的构造函数。
typescript
function rewriteClass<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
super(...args);
console.log('rewriteClass constructor');
}
}
}
@rewriteClass
class HttpClient4 {
constructor() {
console.log('HttpClient4 constructor');
}
}
new HttpClient4();
// 输出:
// HttpClient4 constructor
// rewriteClass constructor高级用法示例
1. 单例模式装饰器
typescript
function Singleton<T extends { new (...args: any[]): {} }>(constructor: T) {
let instance: T;
return class extends constructor {
constructor(...args: any[]) {
if (instance) {
return instance;
}
super(...args);
instance = this as any;
return instance;
}
} as T;
}
@Singleton
class DatabaseConnection {
constructor(public connectionString: string) {}
}2. 日志装饰器
typescript
function Logger(logLevel: 'info' | 'debug' | 'error' = 'info') {
return function <T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
console.log(`[${logLevel.toUpperCase()}] Creating instance of ${constructor.name}`);
super(...args);
console.log(`[${logLevel.toUpperCase()}] Instance of ${constructor.name} created`);
}
};
};
}
@Logger('debug')
class ApiService {
constructor(private baseUrl: string) {}
}3. 验证装饰器
typescript
function ValidateConstructor(validationRules: Record<string, (value: any) => boolean>) {
return function <T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
// 验证构造函数参数
Object.keys(validationRules).forEach((key, index) => {
const validator = validationRules[key];
if (!validator(args[index])) {
throw new Error(`Validation failed for parameter ${key}`);
}
});
super(...args);
}
};
};
}
@ValidateConstructor({
email: (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
age: (value: number) => value > 0 && value < 150
})
class User {
constructor(public email: string, public age: number) {}
}多个装饰器的执行顺序
当多个装饰器应用于同一个类时,它们的执行顺序是从下到上(最接近类声明的先执行)。
typescript
function first() {
console.log("first(): factory evaluated");
return function (target: any) {
console.log("first(): called");
};
}
function second() {
console.log("second(): factory evaluated");
return function (target: any) {
console.log("second(): called");
};
}
@first()
@second()
class ExampleClass {}
// 输出:
// first(): factory evaluated
// second(): factory evaluated
// second(): called
// first(): called方法装饰器
方法装饰器是应用于方法的装饰器,可以用来监视、修改或替换方法的定义。方法装饰器在方法声明之前被声明。
基本语法
方法装饰器是一个函数,接收三个参数:
target:类的原型对象(对于静态方法是类的构造函数)propertyKey:方法的名称descriptor:方法的属性描述符
typescript
function logMethod(target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Method ${propertyKey} called with arguments:`, args);
const result = originalMethod.apply(this, args);
console.log(`Method ${propertyKey} returned:`, result);
return result;
}
}
class Calculator {
@logMethod
add(a: number, b: number) {
return a + b;
}
}
const calculator = new Calculator();
calculator.add(1, 2);
// 输出:
// Method add called with arguments: [1, 2]
// Method add returned: 3权限控制装饰器
方法装饰器可以用来实现权限检查,在方法执行前进行权限验证。
typescript
function checkPermission(target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
if (user[args[0]].role.includes('admin')) {
return originalMethod.apply(this, args);
} else {
throw new Error('Permission denied');
}
}
return descriptor;
}
const user = {
'user1': { role: ['admin', 'user'] },
'user2': { role: ['user'] },
}
class WebSite {
@checkPermission
deleteUser(userId: string) {
console.log(`delete user ${userId}`);
}
}
const webSite = new WebSite();
webSite.deleteUser('user1'); // 成功执行
// webSite.deleteUser('user2'); // 抛出 Permission denied 错误缓存装饰器
方法装饰器可以实现方法结果的缓存,提高性能。
typescript
function cacheMethod(target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const cache = new Map();
descriptor.value = function (...args: any[]) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log(`Cache hit for ${key}`);
return cache.get(key);
}
const result = originalMethod.apply(this, args);
console.log(`Cache ${key} = ${result}`);
cache.set(key, result);
return result;
}
return descriptor;
}
class CacheCalculator {
@cacheMethod
add(a: number, b: number) {
return a + b;
}
}
const cacheCalculator = new CacheCalculator();
cacheCalculator.add(1, 2); // 首次调用,缓存结果
cacheCalculator.add(1, 2); // 第二次调用,从缓存返回高级用法示例
1. 性能监控装饰器
typescript
function performance(target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const end = performance.now();
console.log(`Method ${propertyKey} executed in ${end - start} milliseconds`);
return result;
};
return descriptor;
}
class DataProcessor {
@performance
processLargeData(data: any[]) {
// 模拟耗时操作
return data.map(item => item * 2);
}
}2. 重试装饰器
typescript
function retry(maxAttempts: number = 3, delay: number = 1000) {
return function (target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
let attempts = 0;
while (attempts < maxAttempts) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
attempts++;
if (attempts >= maxAttempts) {
throw error;
}
console.log(`Attempt ${attempts} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
};
return descriptor;
};
}
class ApiClient {
@retry(3, 2000)
async fetchData(url: string) {
// 模拟可能失败的 API 调用
if (Math.random() < 0.7) {
throw new Error('Network error');
}
return { data: 'success' };
}
}3. 参数验证装饰器
typescript
function validateArgs(validators: ((arg: any) => boolean)[]) {
return function (target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
validators.forEach((validator, index) => {
if (!validator(args[index])) {
throw new Error(`Invalid argument at position ${index} for method ${propertyKey}`);
}
});
return originalMethod.apply(this, args);
};
return descriptor;
};
}
class MathOperations {
@validateArgs([
(a: number) => typeof a === 'number' && !isNaN(a),
(b: number) => typeof b === 'number' && !isNaN(b) && b !== 0
])
divide(a: number, b: number) {
return a / b;
}
}4. 异步错误处理装饰器
typescript
function asyncErrorHandler(target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
console.error(`Error in method ${propertyKey}:`, error);
// 可以在这里添加错误上报、日志记录等逻辑
throw error;
}
};
return descriptor;
}
class UserService {
@asyncErrorHandler
async createUser(userData: any) {
// 可能抛出异常的异步操作
return await database.save(userData);
}
}在 NestJS 中的应用
1. HTTP 方法装饰器
typescript
@Controller('users')
export class UsersController {
@Get(':id')
findOne(@Param('id') id: string) {
return `This action returns user #${id}`;
}
@Post()
@HttpCode(201)
create(@Body() createUserDto: CreateUserDto) {
return 'This action adds a new user';
}
@Put(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return `This action updates user #${id}`;
}
@Delete(':id')
remove(@Param('id') id: string) {
return `This action removes user #${id}`;
}
}2. 守卫装饰器
typescript
@Controller('admin')
export class AdminController {
@Get('users')
@UseGuards(AuthGuard, RolesGuard)
@Roles('admin')
getAllUsers() {
return 'Admin only endpoint';
}
}3. 拦截器装饰器
typescript
@Controller('data')
export class DataController {
@Get()
@UseInterceptors(LoggingInterceptor, TransformInterceptor)
@Timeout(5000)
getData() {
return { message: 'Hello World' };
}
}4. 管道装饰器
typescript
@Controller('validation')
export class ValidationController {
@Post()
create(
@Body(ValidationPipe) createDto: CreateUserDto,
@Query('type', ParseIntPipe) type: number
) {
return 'Data validated and processed';
}
}PropertyDescriptor 详解
方法装饰器中的 descriptor 参数包含以下属性:
typescript
interface PropertyDescriptor {
value?: any; // 方法的实现
writable?: boolean; // 是否可写
enumerable?: boolean; // 是否可枚举
configurable?: boolean; // 是否可配置
get?(): any; // getter 方法
set?(v: any): void; // setter 方法
}装饰器组合使用
多个方法装饰器可以组合使用,执行顺序从下到上:
typescript
class ApiService {
@asyncErrorHandler
@performance
@retry(3)
@cacheMethod
async fetchUserData(userId: string) {
return await this.httpClient.get(`/users/${userId}`);
}
}常见应用模式
1. AOP(面向切面编程)
typescript
// 前置通知
function before(advice: Function) {
return function (target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
advice.apply(this, args);
return originalMethod.apply(this, args);
};
};
}
// 后置通知
function after(advice: Function) {
return function (target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const result = originalMethod.apply(this, args);
advice.call(this, result);
return result;
};
};
}2. 方法限流装饰器
typescript
function throttle(limit: number, window: number) {
const calls = new Map();
return function (target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const now = Date.now();
const key = `${target.constructor.name}.${propertyKey}`;
if (!calls.has(key)) {
calls.set(key, []);
}
const callTimes = calls.get(key);
const recentCalls = callTimes.filter((time: number) => now - time < window);
if (recentCalls.length >= limit) {
throw new Error(`Method ${propertyKey} called too frequently`);
}
recentCalls.push(now);
calls.set(key, recentCalls);
return originalMethod.apply(this, args);
};
};
}访问器装饰器和属性装饰器
访问器装饰器
访问器装饰器用来装饰类的 getter 和 setter 方法,可以监视、修改或替换访问器的定义。
基本语法
访问器装饰器接收三个参数:
target:类的原型对象(静态成员为类的构造函数)propertyKey:属性的名称descriptor:属性描述符
typescript
function LogAccessor(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalGet = descriptor.get;
descriptor.get = function () {
console.log(`Accessed property: ${propertyKey}`);
return originalGet?.call(this);
};
}
class User {
private _name = 'Alice';
@LogAccessor
get name() {
return this._name;
}
}
const u = new User();
console.log(u.name);
// 输出:
// Accessed property: name
// Alice高级访问器装饰器示例
1. 读写监控装饰器
typescript
function Monitor(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalGet = descriptor.get;
const originalSet = descriptor.set;
if (originalGet) {
descriptor.get = function () {
console.log(`Reading ${propertyKey}`);
const value = originalGet.call(this);
console.log(`Read ${propertyKey}: ${value}`);
return value;
};
}
if (originalSet) {
descriptor.set = function (value: any) {
console.log(`Writing ${propertyKey}: ${value}`);
return originalSet.call(this, value);
};
}
}
class Product {
private _price: number = 0;
@Monitor
get price() {
return this._price;
}
set price(value: number) {
this._price = value;
}
}2. 数据验证装饰器
typescript
function ValidateRange(min: number, max: number) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalSet = descriptor.set;
descriptor.set = function (value: number) {
if (value < min || value > max) {
throw new Error(`${propertyKey} must be between ${min} and ${max}`);
}
return originalSet?.call(this, value);
};
};
}
class Temperature {
private _celsius: number = 0;
@ValidateRange(-273.15, 1000)
get celsius() {
return this._celsius;
}
set celsius(value: number) {
this._celsius = value;
}
}属性装饰器
属性装饰器用来装饰类的属性,通常与 reflect-metadata 结合使用来存储元数据。
基本语法
属性装饰器接收两个参数:
target:类的原型对象(静态属性为类的构造函数)propertyKey:属性的名称
typescript
import 'reflect-metadata';
function Required(target: any, propertyKey: string) {
const requiredProps = Reflect.getMetadata('required:props', target) || [];
Reflect.defineMetadata(
'required:props',
[...requiredProps, propertyKey],
target
);
}
class User1 {
@Required
username: string;
email: string;
}
function validateRequired(obj: any) {
const requiredProps = Reflect.getMetadata('required:props', obj) || [];
for (const key of requiredProps) {
if (!obj[key]) {
console.log(`${key} is required`);
}
}
}
const u1 = new User1();
u1.username = ''; // 未赋值
validateRequired(u1); // 输出:username is required高级属性装饰器示例
1. 类型验证装饰器
typescript
function Type(type: 'string' | 'number' | 'boolean' | 'object') {
return function (target: any, propertyKey: string) {
Reflect.defineMetadata('validation:type', type, target, propertyKey);
};
}
function validate(obj: any) {
const properties = Object.getOwnPropertyNames(obj);
for (const prop of properties) {
const expectedType = Reflect.getMetadata('validation:type', obj, prop);
if (expectedType && typeof obj[prop] !== expectedType) {
throw new Error(`Property ${prop} must be of type ${expectedType}`);
}
}
}
class UserProfile {
@Type('string')
name: string;
@Type('number')
age: number;
@Type('boolean')
isActive: boolean;
}2. 格式化装饰器
typescript
function Format(formatter: (value: any) => any) {
return function (target: any, propertyKey: string) {
const formatters = Reflect.getMetadata('formatters', target) || new Map();
formatters.set(propertyKey, formatter);
Reflect.defineMetadata('formatters', formatters, target);
// 重新定义属性
let value: any;
Object.defineProperty(target, `_${propertyKey}`, {
writable: true,
enumerable: false
});
Object.defineProperty(target, propertyKey, {
get() {
return this[`_${propertyKey}`];
},
set(newValue: any) {
this[`_${propertyKey}`] = formatter(newValue);
},
enumerable: true,
configurable: true
});
};
}
class UserInfo {
@Format((value: string) => value.toLowerCase().trim())
email: string;
@Format((value: string) => value.charAt(0).toUpperCase() + value.slice(1).toLowerCase())
firstName: string;
}
const userInfo = new UserInfo();
userInfo.email = ' JOHN@EXAMPLE.COM ';
userInfo.firstName = 'jOhN';
console.log(userInfo.email); // john@example.com
console.log(userInfo.firstName); // John3. 默认值装饰器
typescript
function Default(defaultValue: any) {
return function (target: any, propertyKey: string) {
const defaults = Reflect.getMetadata('defaults', target) || new Map();
defaults.set(propertyKey, defaultValue);
Reflect.defineMetadata('defaults', defaults, target);
};
}
function applyDefaults(obj: any) {
const defaults = Reflect.getMetadata('defaults', obj) || new Map();
for (const [key, value] of defaults) {
if (obj[key] === undefined || obj[key] === null) {
obj[key] = value;
}
}
}
class Configuration {
@Default('localhost')
host: string;
@Default(3000)
port: number;
@Default(false)
debug: boolean;
}装饰器执行顺序
根据代码示例,不同类型装饰器的执行顺序如下:
typescript
function classDecorator1(target) { console.log('classDecorator1'); }
function classDecorator2(target) { console.log('classDecorator2'); }
function propertyDecorator1(target, propertyKey) { console.log('propertyDecorator1'); }
function propertyDecorator2(target, propertyKey) { console.log('propertyDecorator2'); }
function methodDecorator1(target, propertyKey) { console.log('methodDecorator1'); }
function methodDecorator2(target, propertyKey) { console.log('methodDecorator2'); }
function accessorDecorator1(target, propertyKey) { console.log('accessorDecorator1'); }
function accessorDecorator2(target, propertyKey) { console.log('accessorDecorator2'); }
function parameterDecorator1(target, propertyKey, parameterIndex: number) {
console.log('parameterDecorator1', propertyKey);
}
function parameterDecorator2(target, propertyKey, parameterIndex: number) {
console.log('parameterDecorator2', propertyKey);
}
@classDecorator1
@classDecorator2
class Example {
@accessorDecorator1
@accessorDecorator2
get myProp() {
return this.prop;
}
@propertyDecorator1
@propertyDecorator2
prop: string;
@methodDecorator1
@methodDecorator2
method(
@parameterDecorator2 @parameterDecorator1 param1: any,
@parameterDecorator2 @parameterDecorator1 param2: any
) {}
}
// 执行顺序:
// 1. 访问器装饰器:accessorDecorator2 → accessorDecorator1
// 2. 属性装饰器:propertyDecorator2 → propertyDecorator1
// 3. 参数装饰器:从右到左,从下到上
// 4. 方法装饰器:methodDecorator2 → methodDecorator1
// 5. 类装饰器:classDecorator2 → classDecorator1执行顺序规则总结
- 属性装饰器、方法装饰器、访问器装饰器:按照在类中出现的顺序,从上往下依次执行
- 类装饰器:最后执行
- 参数装饰器:先于方法装饰器执行
- 多个装饰器:从右向左,从下到上执行(最接近被装饰项的先执行)
在 NestJS 中的应用
1. 属性装饰器应用
typescript
// 依赖注入
class UserService {
@Inject('USER_REPOSITORY')
private userRepository: UserRepository;
@InjectRepository(User)
private userRepo: Repository<User>;
}
// 配置注入
class AppService {
@ConfigProperty('database.host')
private dbHost: string;
@ConfigProperty('database.port', 5432)
private dbPort: number;
}2. 访问器装饰器应用
typescript
// 数据转换
class UserDto {
private _email: string;
@Transform(({ value }) => value.toLowerCase())
get email() {
return this._email;
}
set email(value: string) {
this._email = value;
}
}
// API 属性文档
class CreateUserDto {
private _age: number;
@ApiProperty({ minimum: 0, maximum: 120 })
@IsNumber()
@Min(0)
@Max(120)
get age() {
return this._age;
}
set age(value: number) {
this._age = value;
}
}实用装饰器集合
1. 只读属性装饰器
typescript
function ReadOnly(target: any, propertyKey: string) {
Object.defineProperty(target, propertyKey, {
writable: false,
configurable: false
});
}
class Config {
@ReadOnly
readonly API_URL = 'https://api.example.com';
}2. 延迟加载装饰器
typescript
function Lazy(loader: () => any) {
return function (target: any, propertyKey: string) {
let loaded = false;
let value: any;
Object.defineProperty(target, propertyKey, {
get() {
if (!loaded) {
value = loader();
loaded = true;
}
return value;
},
enumerable: true,
configurable: true
});
};
}
class DataService {
@Lazy(() => new ExpensiveResource())
resource: ExpensiveResource;
}3. 观察者模式装饰器
typescript
function Observable(target: any, propertyKey: string) {
const observers = new Set<(value: any) => void>();
let value: any;
// 添加订阅方法
target[`subscribe${propertyKey.charAt(0).toUpperCase() + propertyKey.slice(1)}`] =
function (observer: (value: any) => void) {
observers.add(observer);
};
Object.defineProperty(target, propertyKey, {
get() {
return value;
},
set(newValue: any) {
const oldValue = value;
value = newValue;
observers.forEach(observer => observer({ oldValue, newValue, propertyKey }));
},
enumerable: true,
configurable: true
});
}
class Model {
@Observable
name: string;
}
const model = new Model();
model.subscribeName((change) => {
console.log(`Property ${change.propertyKey} changed from ${change.oldValue} to ${change.newValue}`);
});
model.name = 'John'; // 触发观察者参数装饰器
参数装饰器用来装饰函数参数,通常与 reflect-metadata 结合使用。
基本语法
参数装饰器接收三个参数:
target:类的原型对象propertyKey:方法的名称parameterIndex:参数在函数参数列表中的索引
typescript
function LogParam(target: any, propertyKey: string, parameterIndex: number) {
const existingParams = Reflect.getMetadata('log:params', target, propertyKey) || [];
existingParams.push(parameterIndex);
Reflect.defineMetadata('log:params', existingParams, target, propertyKey);
}
function logParams(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const loggedParams = Reflect.getMetadata('log:params', target, propertyKey) || [];
descriptor.value = function (...args: any[]) {
loggedParams.forEach((index: number) => {
console.log(`Parameter ${index} in ${propertyKey}:`, args[index]);
});
return originalMethod.apply(this, args);
};
}
class ApiService {
@logParams
createUser(@LogParam name: string, @LogParam email: string, age: number) {
return { name, email, age };
}
}装饰器执行顺序详解
基于代码示例,完整的执行顺序如下:
typescript
@classDecorator1
@classDecorator2
class Example {
@accessorDecorator1
@accessorDecorator2
get myProp() {
return this.prop;
}
@propertyDecorator1
@propertyDecorator2
prop: string;
@methodDecorator1
@methodDecorator2
method(
@parameterDecorator4 @parameterDecorator3 param1: any,
@parameterDecorator2 @parameterDecorator1 param2: any
) {}
}
// 实际执行顺序:
// 1. accessorDecorator2
// 2. accessorDecorator1
// 3. propertyDecorator2
// 4. propertyDecorator1
// 5. parameterDecorator3 (param1)
// 6. parameterDecorator4 (param1)
// 7. parameterDecorator1 (param2)
// 8. parameterDecorator2 (param2)
// 9. methodDecorator2
// 10. methodDecorator1
// 11. classDecorator2
// 12. classDecorator1执行规则总结
- 同类型多个装饰器:从下到上执行(最接近被装饰项的先执行)
- 不同类型装饰器:
- 访问器/属性/方法装饰器:按在类中出现的顺序执行
- 参数装饰器:先于对应的方法装饰器执行
- 类装饰器:最后执行
- 参数装饰器特殊规则:
- 多个参数:从右向左执行
- 单个参数多个装饰器:从右向左执行
