JavaScript装饰器从基础到实战教程
作者:Rysxt
装饰器(Decorator)是JavaScript中一种声明式的语法特性,用于在不修改原始代码的情况下,动态扩展类、方法、属性或参数的行为。它本质上是一个函数,通过@符号附加到目标上,让代码更优雅、可维护。本文将从基础概念入手,逐步讲解装饰器的类型、用法、进阶技巧及实战场景。
一、装饰器基础概念
1.1 什么是装饰器?
装饰器是一种特殊函数,接收目标对象(类、方法、属性等)作为参数,返回修改后的目标或新的描述符。其核心价值是分离关注点——将日志记录、权限验证、性能监控等功能从业务代码中剥离,通过装饰器“装饰”到目标上。
1.2 装饰器的语法
装饰器使用@符号,紧跟装饰器函数名,放置在目标声明前:
@decorator
class MyClass {} // 类装饰器
class MyClass {
@decorator
myMethod() {} // 方法装饰器
@decorator
myProperty; // 属性装饰器
}等价于:
class MyClass {}
MyClass = decorator(MyClass) || MyClass; // 类装饰器
MyClass.prototype.myMethod = decorator(MyClass.prototype, 'myMethod', Object.getOwnPropertyDescriptor(MyClass.prototype, 'myMethod')) || MyClass.prototype.myMethod; // 方法装饰器1.3 装饰器的执行时机
装饰器在编译阶段执行(ES6模块加载时),而非运行时。例如:
function logClass(target) {
console.log('类装饰器执行'); // 编译阶段输出
target.prototype.timestamp = new Date();
}
@logClass
class MyClass {}
// 实例化时不会再次执行装饰器
const instance = new MyClass();
console.log(instance.timestamp); // 输出编译阶段的日期二、装饰器的主要类型
装饰器可分为五大类,分别作用于不同的目标:
2.1 类装饰器
作用:修改类的构造函数或原型,添加静态属性/方法或实例属性/方法。
参数:类的构造函数(target)。
示例:为类添加静态属性和实例属性。
function addStaticProperty(staticProp, value) {
return function(target) {
target[staticProp] = value; // 添加静态属性
};
}
function addInstanceProperty(instanceProp, initialValue) {
return function(target) {
target.prototype[instanceProp] = initialValue; // 添加实例属性
};
}
@addStaticProperty('version', '1.0.0')
@addInstanceProperty('timestamp', new Date())
class MyClass {}
console.log(MyClass.version); // 输出: 1.0.0
const instance = new MyClass();
console.log(instance.timestamp); // 输出: 编译阶段的日期2.2 方法装饰器
作用:修改方法的描述符(value、writable等),实现日志、权限、性能监控等功能。
参数:
target:静态方法为类的构造函数,实例方法为类的原型;propertyKey:方法名;descriptor:方法描述符(包含value、writable等属性)。
示例:记录方法调用日志。
function logMethod(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
console.log(`调用方法 ${propertyKey},参数: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`方法 ${propertyKey} 返回: ${result}`);
return result;
};
return descriptor;
}
class Calculator {
@logMethod
add(a, b) {
return a + b;
}
}
const calc = new Calculator();
calc.add(2, 3);
// 输出:
// 调用方法 add,参数: [2,3]
// 方法 add 返回: 52.3 属性装饰器
作用:修改属性的描述符(如writable设为false实现只读),或通过Object.defineProperty定义属性的getter/setter。
参数:
target:静态属性为类的构造函数,实例属性为类的原型;propertyKey:属性名。
示例:实现属性只读。
function readOnly(target, propertyKey) {
Object.defineProperty(target, propertyKey, {
writable: false, // 设为不可写
configurable: false // 设为不可配置(防止后续修改)
});
}
class Person {
@readOnly
name = 'John';
}
const person = new Person();
person.name = 'Jane'; // 严格模式下报错:Cannot assign to read only property 'name'
console.log(person.name); // 输出: John2.4 访问器装饰器
作用:修改属性的getter或setter方法,实现数据格式化、验证等功能。
参数:与方法装饰器相同(target、propertyKey、descriptor)。
示例:将属性值转为大写。
function capitalize(target, propertyKey, descriptor) {
const originalGetter = descriptor.get;
descriptor.get = function() {
return originalGetter.call(this).toUpperCase(); // 转为大写
};
return descriptor;
}
class User {
constructor(name) {
this._name = name;
}
@capitalize
get name() {
return this._name;
}
}
const user = new User('john');
console.log(user.name); // 输出: JOHN2.5 参数装饰器
作用:为方法参数添加元数据(如参数名、验证规则),常用于框架中的依赖注入或参数校验。
参数:
target:类的原型(实例方法)或构造函数(静态方法);propertyKey:方法名;parameterIndex:参数的索引(从0开始)。
示例:记录参数索引。
function logParameter(target, propertyKey, parameterIndex) {
const existingParameters = target[propertyKey + 'Parameters'] || [];
existingParameters.push(parameterIndex);
target[propertyKey + 'Parameters'] = existingParameters;
}
class UserService {
getUser(@logParameter id, @logParameter name) {
return { id, name };
}
}
const userService = new UserService();
userService.getUser(1, 'John');
console.log(UserService.prototype.getUserParameters); // 输出: [0, 1]三、装饰器的高级用法
3.1 装饰器工厂:传递参数
装饰器本身是静态的,若需动态配置,可通过装饰器工厂(返回装饰器函数的函数)实现。
示例:带参数的权限装饰器。
function checkPermission(requiredRole) {
return function(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
const userRole = this.userRole; // 假设实例上有userRole属性
if (userRole === requiredRole) {
return originalMethod.apply(this, args);
} else {
throw new Error(`无权限:需要${requiredRole}角色`);
}
};
return descriptor;
};
}
class AdminPanel {
userRole = 'admin';
@checkPermission('admin')
deleteUser() {
console.log('用户已删除');
}
}
const panel = new AdminPanel();
panel.deleteUser(); // 输出: 用户已删除
const userPanel = new AdminPanel();
userPanel.userRole = 'user';
userPanel.deleteUser(); // 报错: 无权限:需要admin角色3.2 多个装饰器的执行顺序
多个装饰器按从外到内的顺序应用(声明顺序),但从内到外执行(执行顺序)。
示例:
function dec1(target, propertyKey, descriptor) {
console.log('装饰器1应用');
return descriptor;
}
function dec2(target, propertyKey, descriptor) {
console.log('装饰器2应用');
return descriptor;
}
class Example {
@dec1
@dec2
method() {}
}
// 输出:
// 装饰器2应用
// 装饰器1应用3.3 组合装饰器:简化重复代码
通过高阶函数组合多个装饰器,减少重复代码。
示例:组合日志和性能监控装饰器。
function composeDecorators(...decorators) {
return function(target, propertyKey, descriptor) {
return decorators.reduceRight((desc, decorator) => decorator(target, propertyKey, desc), descriptor);
};
}
function logMethod(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
console.log(`调用方法 ${propertyKey}`);
return originalMethod.apply(this, args);
};
return descriptor;
}
function measureTime(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
const start = Date.now();
const result = originalMethod.apply(this, args);
console.log(`方法 ${propertyKey} 执行时间: ${Date.now() - start}ms`);
return result;
};
return descriptor;
}
const logAndMeasure = composeDecorators(logMethod, measureTime);
class DataProcessor {
@logAndMeasure
processData(data) {
// 模拟耗时操作
for (let i = 0; i < 1e6; i++) {}
return data;
}
}
const processor = new DataProcessor();
processor.processData([1, 2, 3]);
// 输出:
// 调用方法 processData
// 方法 processData 执行时间: 5ms四、装饰器的实战场景
4.1 日志记录
通过装饰器自动记录方法的调用信息和返回值,无需手动添加console.log。
function logMethod(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
console.log(`[LOG] 调用 ${propertyKey},参数: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`[LOG] ${propertyKey} 返回: ${result}`);
return result;
};
return descriptor;
}
class LoggerExample {
@logMethod
greet(name) {
return `Hello, ${name}!`;
}
}
const logger = new LoggerExample();
logger.greet('Alice');
// 输出:
// [LOG] 调用 greet,参数: ["Alice"]
// [LOG] greet 返回: Hello, Alice!4.2 权限验证
通过装饰器在方法执行前检查用户权限,避免重复编写权限逻辑。
function hasPermission(requiredPermission) {
return function(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
const userPermissions = this.permissions || []; // 假设实例上有permissions属性
if (userPermissions.includes(requiredPermission)) {
return originalMethod.apply(this, args);
} else {
throw new Error(`无权限:需要${requiredPermission}`);
}
};
return descriptor;
};
}
class DocumentManager {
permissions = ['read'];
@hasPermission('write')
editDocument(content) {
console.log('文档已编辑');
}
}
const manager = new DocumentManager();
manager.editDocument('新内容'); // 报错: 无权限:需要write
manager.permissions.push('write');
manager.editDocument('新内容'); // 输出: 文档已编辑4.3 性能监控
通过装饰器监控方法的执行时间,快速定位性能瓶颈。
function measurePerformance(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const end = performance.now();
console.log(`[PERF] 方法 ${propertyKey} 执行时间: ${end - start}ms`);
return result;
};
return descriptor;
}
class HeavyCalculation {
@measurePerformance
calculateFactorial(n) {
return n <= 1 ? 1 : n * this.calculateFactorial(n - 1);
}
}
const calculator = new HeavyCalculation();
calculator.calculateFactorial(10); // 输出: [PERF] 方法 calculateFactorial 执行时间: 1ms4.4 自动绑定this
通过装饰器自动绑定方法的this,避免在回调中丢失this。
function autobind(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
return originalMethod.apply(this, args);
};
return descriptor;
}
class Button {
constructor(text) {
this.text = text;
this.handleClick = this.handleClick.bind(this); // 传统方式
}
@autobind
handleClick() {
console.log(`按钮文本: ${this.text}`);
}
}
const button = new Button('Click Me');
const clickHandler = button.handleClick;
clickHandler(); // 输出: 按钮文本: Click Me(无需手动绑定)五、注意事项
- 装饰器的执行顺序:多个装饰器按从外到内应用,从内到外执行。例如
@A @B等同于A(B(target)),但执行顺序是B先于A。 - 不能装饰函数:函数存在变量提升,装饰器可能在函数声明前执行,导致意外结果。建议使用类或箭头函数。
- 兼容性问题:装饰器是ES提案,需通过Babel(
@babel/plugin-proposal-decorators)或TypeScript(experimentalDecorators)转译。 - 保持单一职责:每个装饰器只做一件事(如日志、权限),避免复杂逻辑,提高可维护性。
六、总结
装饰器是JavaScript中强大的语法糖,通过声明式的方式扩展类、方法、属性的功能,让代码更清晰、可维护。本文从基础概念讲起,覆盖了类、方法、属性、访问器、参数五大类装饰器,以及装饰器工厂、组合装饰器等高级用法,最后结合实战场景演示了日志、权限、性能监控等常见应用。掌握装饰器,能让你写出更优雅、更模块化的代码。
到此这篇关于JavaScript装饰器从基础到实战教程的文章就介绍到这了,更多相关JavaScript装饰器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
