在 JavaScript 里,装饰器(Decorator)是一种特殊的声明,它可以被附加到类声明、方法、属性或访问器上,用于修改类或其成员的行为。装饰器本质上是一个函数,它接收目标对象、属性名和属性描述符作为参数,并返回一个新的属性描述符或修改后的目标对象。装饰器在 ES2016 时以提案的形式提出,在 TypeScript 中得到了广泛支持,并且在现代 JavaScript 项目中也逐渐被应用。下面详细介绍其使用方法和应用场景。
基本语法
在 JavaScript 中使用装饰器,需要使用 @ 符号,后跟装饰器函数名。以下是一个简单的类装饰器示例:
function myDecorator(target) {
// 对目标类进行修改
target.prototype.newProperty = 'This is a new property added by decorator';
return target;
}
@myDecorator
class MyClass {
constructor() {}
}
const instance = new MyClass();
console.log(instance.newProperty); // 输出: This is a new property added by decorator
在上述代码中,myDecorator 是一个装饰器函数,它接收一个目标类作为参数,并在该类的原型上添加了一个新属性。通过 @myDecorator 将该装饰器应用到 MyClass 类上。
装饰器的参数
在 JavaScript 中,装饰器本质上是一个函数,根据其应用的目标不同(类、方法、属性等),接收的参数也有所不同。下面详细介绍不同类型装饰器函数所接收的参数。
类装饰器
类装饰器是应用在类声明上的装饰器,它接收一个参数,即被装饰的类的构造函数。
function classDecorator(target) {
// target 是被装饰的类的构造函数
console.log('类构造函数:', target);
// 可以对类进行修改,例如添加静态属性
target.newStaticProperty = 'This is a new static property';
return target;
}
@classDecorator
class MyClass {
constructor() {}
}
console.log(MyClass.newStaticProperty); // 输出: This is a new static property
在上述代码中,classDecorator 是一个类装饰器,它接收 target 参数,该参数就是 MyClass 类的构造函数。通过这个参数,可以对类进行修改,如添加静态属性。
方法装饰器
方法装饰器应用在类的方法上,它接收三个参数:
target:对于静态方法,它是类的构造函数;对于实例方法,它是类的原型对象。propertyKey:被装饰方法的名称,是一个字符串或符号(Symbol)类型。descriptor:方法的属性描述符对象,包含value(方法的实际实现)、writable、enumerable和configurable等属性。
function methodDecorator(target, propertyKey, descriptor) {
console.log('目标对象:', target);
console.log('方法名称:', propertyKey);
console.log('属性描述符:', descriptor);
const originalMethod = descriptor.value;
descriptor.value = function (...args) {
console.log(`调用方法 ${propertyKey} 前`);
const result = originalMethod.apply(this, args);
console.log(`调用方法 ${propertyKey} 后`);
return result;
};
return descriptor;
}
class MyClass {
@methodDecorator
myMethod() {
console.log('方法执行中');
}
}
const instance = new MyClass();
instance.myMethod();
在这个例子中,methodDecorator 是一个方法装饰器,通过修改 descriptor.value 来包裹原始方法,实现了在方法调用前后添加日志的功能。
属性装饰器
属性装饰器应用在类的属性上,它接收两个参数:
target:对于静态属性,它是类的构造函数;对于实例属性,它是类的原型对象。propertyKey:被装饰属性的名称,是一个字符串或符号(Symbol)类型。
function propertyDecorator(target, propertyKey) {
console.log('目标对象:', target);
console.log('属性名称:', propertyKey);
// 可以在这里对属性进行一些操作,例如设置默认值等
}
class MyClass {
@propertyDecorator
myProperty;
}
const instance = new MyClass();
在上述代码中,propertyDecorator 是一个属性装饰器,它接收 target 和 propertyKey 参数,可用于对属性进行一些预处理操作。
访问器装饰器
访问器装饰器应用在类的 getter 或 setter 方法上,其参数与方法装饰器相同,即 target、propertyKey 和 descriptor。
function accessorDecorator(target, propertyKey, descriptor) {
console.log('目标对象:', target);
console.log('访问器名称:', propertyKey);
console.log('属性描述符:', descriptor);
const originalGetter = descriptor.get;
descriptor.get = function () {
console.log(`获取 ${propertyKey} 属性值前`);
const result = originalGetter ? originalGetter.call(this) : undefined;
console.log(`获取 ${propertyKey} 属性值后`);
return result;
};
return descriptor;
}
class MyClass {
private _myValue = 0;
@accessorDecorator
get myValue() {
return this._myValue;
}
set myValue(value: number) {
this._myValue = value;
}
}
const instance = new MyClass();
console.log(instance.myValue);
在这个例子中,accessorDecorator 是一个访问器装饰器,通过修改 descriptor.get 来包裹原始的 getter 方法,实现了在获取属性值前后添加日志的功能。
传递参数
若要在装饰器里传递参数,可通过创建一个高阶函数来实现。高阶函数会返回一个装饰器函数,这样就能把参数传递给返回的装饰器函数。下面分别从类装饰器、方法装饰器和属性装饰器的角度,介绍如何在装饰器中传递参数。
类装饰器传递参数
类装饰器用于修改类的定义。要给类装饰器传递参数,可创建一个返回装饰器函数的函数。
// 定义一个接收参数的高阶函数,返回一个类装饰器
function classDecoratorWithParams(param1, param2) {
return function (target) {
// 打印传入的参数
console.log(`Received parameters: ${param1}, ${param2}`);
// 可以对目标类进行修改,这里只是简单添加一个静态属性
target.newStaticProperty = `Combined: ${param1} - ${param2}`;
return target;
};
}
// 使用带参数的类装饰器
@classDecoratorWithParams('value1', 'value2')
class MyClass {
constructor() {}
}
// 访问添加的静态属性
console.log(MyClass.newStaticProperty);
在上述代码中,classDecoratorWithParams 是一个高阶函数,它接收 param1 和 param2 作为参数,并返回一个类装饰器函数。这个装饰器函数会对目标类 MyClass 进行修改,添加一个静态属性。
方法装饰器传递参数
方法装饰器用于修改类的方法。要给方法装饰器传递参数,同样可以创建一个返回装饰器函数的高阶函数。
// 定义一个接收参数的高阶函数,返回一个方法装饰器
function methodDecoratorWithParams(message) {
return function (target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args) {
// 打印传入的消息
console.log(`Message from decorator: ${message}`);
// 调用原始方法
return originalMethod.apply(this, args);
};
return descriptor;
};
}
class MyClass {
// 使用带参数的方法装饰器
@methodDecoratorWithParams('This is a custom message')
myMethod() {
console.log('Method is executing');
}
}
const instance = new MyClass();
instance.myMethod();
在这段代码中,methodDecoratorWithParams 是一个高阶函数,它接收 message 作为参数,并返回一个方法装饰器函数。这个装饰器函数会在调用原始方法之前打印传入的消息。
属性装饰器传递参数
属性装饰器用于修改类的属性。要给属性装饰器传递参数,也采用创建高阶函数返回装饰器函数的方式。
// 定义一个接收参数的高阶函数,返回一个属性装饰器
function propertyDecoratorWithParams(defaultValue) {
return function (target, propertyKey) {
let value = defaultValue;
const getter = function () {
return value;
};
const setter = function (newValue) {
value = newValue;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
class MyClass {
// 使用带参数的属性装饰器
@propertyDecoratorWithParams('default value')
myProperty;
}
const instance = new MyClass();
console.log(instance.myProperty);
instance.myProperty = 'new value';
console.log(instance.myProperty);
在这个例子中,propertyDecoratorWithParams 是一个高阶函数,它接收 defaultValue 作为参数,并返回一个属性装饰器函数。这个装饰器函数会为属性设置默认值,并定义其 getter 和 setter 方法。
应用场景
1. 日志记录
可以使用装饰器在方法执行前后记录日志,方便调试和监控。
function logMethod(target, name, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args) {
console.log(`Calling method ${name} with arguments:`, args);
const result = originalMethod.apply(this, args);
console.log(`Method ${name} returned:`, result);
return result;
};
return descriptor;
}
class Calculator {
@logMethod
add(a, b) {
return a + b;
}
}
const calculator = new Calculator();
const result = calculator.add(2, 3);
在这个例子中,logMethod 装饰器用于记录 add 方法的调用参数和返回值。
2. 权限验证
在方法执行前进行权限验证,只有具备相应权限的用户才能调用该方法。
function checkPermission(requiredRole) {
return function (target, name, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args) {
const userRole = this.userRole; // 假设 this 上有 userRole 属性
if (userRole === requiredRole) {
return originalMethod.apply(this, args);
} else {
console.log('You do not have permission to call this method.');
}
};
return descriptor;
};
}
class UserService {
userRole = 'admin';
@checkPermission('admin')
deleteUser() {
console.log('User deleted.');
}
}
const userService = new UserService();
userService.deleteUser();
这里的 checkPermission 装饰器会检查调用者的角色是否符合要求,如果符合则执行原方法,否则给出提示。
3. 自动绑定 this
在 JavaScript 中,类方法的 this 指向可能会出现问题。可以使用装饰器自动绑定 this。
function autobind(target, name, descriptor) {
const originalMethod = descriptor.value;
return {
configurable: true,
get() {
const boundMethod = originalMethod.bind(this);
Object.defineProperty(this, name, {
value: boundMethod,
configurable: true,
writable: true
});
return boundMethod;
}
};
}
class Button {
constructor() {
this.text = 'Click me';
}
@autobind
handleClick() {
console.log(`Button text: ${this.text}`);
}
}
const button = new Button();
const clickHandler = button.handleClick;
clickHandler();
autobind 装饰器确保 handleClick 方法在被调用时,this 始终指向 Button 实例。
4. 性能监控
使用装饰器来监控方法的执行时间,帮助分析性能瓶颈。
function performanceMonitor(target, name, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args) {
const start = performance.now();
const result = await originalMethod.apply(this, args);
const end = performance.now();
console.log(`Method ${name} took ${end - start} milliseconds to execute.`);
return result;
};
return descriptor;
}
class DataFetcher {
@performanceMonitor
async fetchData() {
// 模拟异步数据获取
await new Promise(resolve => setTimeout(resolve, 1000));
return { data: 'Some data' };
}
}
const fetcher = new DataFetcher();
fetcher.fetchData();
performanceMonitor 装饰器记录了 fetchData 方法的执行时间。
总之,装饰器提供了一种简洁而强大的方式来修改类和其成员的行为,在很多场景下都能提高代码的可维护性和复用性。