性能问题分析
- 函数调用开销:每次使用装饰器包装函数时,会额外增加函数调用的开销。因为装饰器本质上是一个函数,它会返回一个新的函数,这个新函数在每次调用时,除了执行原函数逻辑,还需要执行装饰器内部的逻辑,如日志记录逻辑。
- 元数据操作开销:如果在装饰器中使用 Reflect Metadata 来读取或写入元数据,这也会带来一定的性能开销。读取和写入元数据需要操作 JavaScript 的元数据存储机制,尤其是在高并发场景下,频繁的元数据操作可能会影响性能。
优化方案
- 缓存装饰器结果:
- 思路:对于相同的装饰器应用在不同函数上,如果装饰器逻辑不依赖于函数的具体实现(例如只记录函数名称和调用时间),可以缓存装饰器返回的新函数。这样在后续相同装饰器应用时,直接返回缓存的函数,避免重复创建新函数带来的开销。
- 代码示例:
const decoratorCache: Map<Function, Function> = new Map();
function logDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
if (decoratorCache.has(descriptor.value)) {
return decoratorCache.get(descriptor.value);
}
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with args:`, args);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} returned:`, result);
return result;
};
decoratorCache.set(originalMethod, descriptor.value);
return descriptor;
}
- 异步日志记录:
- 思路:将日志记录操作异步化,避免在函数调用路径中同步执行日志记录。可以使用
setTimeout
或 Promise
等方式将日志记录操作放到事件循环的下一个周期执行,从而减少对原函数调用性能的影响。
- 代码示例:
function asyncLogDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const startTime = Date.now();
const result = originalMethod.apply(this, args);
setTimeout(() => {
console.log(`Async log - Calling ${propertyKey} at ${startTime} with args:`, args);
console.log(`${propertyKey} returned:`, result);
}, 0);
return result;
};
return descriptor;
}
利用装饰器元数据增强日志记录功能
- 具体实现思路:
- 使用
Reflect.defineMetadata
在装饰器中定义元数据,例如定义一个 shouldLog
的元数据来决定是否记录日志。
- 在装饰器逻辑中,使用
Reflect.getMetadata
获取元数据,并根据元数据的值来决定是否执行日志记录操作。
- 代码示例:
import 'reflect-metadata';
const SHOULD_LOG_KEY ='shouldLog';
function conditionalLogDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const shouldLog = Reflect.getMetadata(SHOULD_LOG_KEY, target, propertyKey);
descriptor.value = function (...args: any[]) {
if (shouldLog) {
console.log(`Calling ${propertyKey} with args:`, args);
}
const result = originalMethod.apply(this, args);
if (shouldLog) {
console.log(`${propertyKey} returned:`, result);
}
return result;
};
return descriptor;
}
function setShouldLog(shouldLog: boolean) {
return function (target: any, propertyKey: string) {
Reflect.defineMetadata(SHOULD_LOG_KEY, shouldLog, target, propertyKey);
};
}
class MyClass {
@setShouldLog(true)
@conditionalLogDecorator
myMethod(a: number, b: number) {
return a + b;
}
}
const myObj = new MyClass();
myObj.myMethod(1, 2);