一、核心概念与本质
1. 定义
装饰器(Decorator)是一种函数语法糖,用于在不修改原函数/类的前提下,为其添加额外功能。本质是一个高阶函数,接收目标对象作为参数,返回增强后的对象。
2. 应用场景
- 日志记录、权限校验、性能监控等横切关注点
- 框架层面的功能扩展(如Vue3组合式API、NestJS路由)
- 类属性的元数据管理(如TS的反射机制)
二、装饰器的类型与语法
1. 四种装饰器类型
| 类型 | 作用对象 | 参数说明 |
|---|---|---|
| 类装饰器 | class | 接收类构造函数作为参数 |
| 方法装饰器 | 类的方法 | 接收类原型、方法名、描述符对象 |
| 属性装饰器 | 类的属性 | 接收类原型、属性名 |
| 参数装饰器 | 方法的参数 | 接收类原型、方法名、参数索引 |
2. 基本语法(以类装饰器为例)
// 装饰器工厂(可传参的装饰器)
function Logger(target: Function) {
console.log(`[装饰器] 类 ${target.name} 被装饰`);
// 扩展目标类的行为
target.prototype.log = function() {
console.log(`实例 ${this.name} 日志`);
};
}
@Logger // 应用装饰器
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
const user = new User('张三');
(user as any).log(); // 输出:实例 张三 日志
三、装饰器的执行时机与参数详解
1. 执行顺序
- 类装饰器:在类定义时立即执行(编译阶段)
- 方法/属性装饰器:在类初始化时执行
- 示例:
// 装饰器执行顺序演示 function ClassDecorator() { console.log('类装饰器执行'); return (target: any) => target; } function MethodDecorator() { console.log('方法装饰器工厂执行'); return ( target: any, key: string, descriptor: PropertyDescriptor ) => descriptor; } @ClassDecorator() // 输出:类装饰器执行 class Demo { @MethodDecorator() // 输出:方法装饰器工厂执行(类定义时执行) method() {} }
2. 方法装饰器参数解析
function Validator(target: Object, key: string, descriptor: PropertyDescriptor) {
// target: 类原型(如 User.prototype)
// key: 方法名(如 'save')
// descriptor: 包含 value(原方法)、writable 等属性的对象
// 示例:重写方法实现参数校验
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
if (args[0] === undefined) throw new Error('参数不能为空');
return originalMethod.apply(this, args);
};
return descriptor;
}
四、装饰器在框架中的实际应用
1. Vue3组合式API中的装饰器(需搭配Vue Decorators库)
import { Component, Prop, Watch } from 'vue-property-decorator';
@Component
export default class UserComponent {
@Prop() userId!: number; // 声明props
name = '张三';
@Watch('userId')
onUserIdChange(newVal: number) {
console.log('用户ID变更:', newVal);
}
@Method('log') // 自定义装饰器示例
logMessage() {
console.log('组件日志');
}
}
2. NestJS中的路由装饰器
import { Controller, Get, Post, Param } from '@nestjs/common';
@Controller('users') // 类装饰器定义路由前缀
export class UserController {
@Get() // 方法装饰器定义GET请求
findAll() { return '所有用户'; }
@Post(':id') // 带参数的路由
findOne(@Param('id') id: string) { return `用户 ${id}`; }
}
五、装饰器的实现原理与性能考量
1. 核心原理
- 装饰器本质是元编程(Meta Programming) 的一种实现,通过操作类的元数据(Metadata)修改行为
- 编译阶段会被转换为普通函数调用,例如:
@Logger class User {} // 等价于 class User {} Logger(User);
2. 性能注意事项
- 装饰器执行于编译期,对运行时性能影响较小
- 避免在装饰器中执行复杂逻辑(如API请求),可能导致加载延迟
- 过多装饰器嵌套可能降低代码可维护性
六、问题
1. 问:装饰器与Mixin(混入)的区别?
- 答:
- 装饰器是行为增强,通过函数包装目标对象;
- Mixin是代码复用,通过合并多个类的属性和方法生成新类。
- 场景对比:
- 装饰器适合“横切功能”(如日志、权限);
- Mixin适合“垂直功能”(如多个组件共享表单逻辑)。
2. 问:如何实现一个带参数的装饰器?
- 答:
- 装饰器本身返回一个接收目标对象的函数(即“装饰器工厂”):
function Permission(role: string) { return function(target: any, key: string, descriptor: PropertyDescriptor) { // 保存权限配置到元数据 Reflect.defineMetadata('requiredRole', role, target, key); // 重写方法实现权限校验 const originalMethod = descriptor.value; descriptor.value = function() { const userRole = getCurrentUserRole(); if (userRole !== role) throw new Error('无权限'); return originalMethod.apply(this, arguments); }; return descriptor; }; } // 使用示例 class AdminPanel { @Permission('admin') deleteUser() {} }
- 装饰器本身返回一个接收目标对象的函数(即“装饰器工厂”):
3. 问:装饰器在TS与JS中的支持差异?
- 答:
- TS完全支持装饰器(需在tsconfig中开启
experimentalDecorators和emitDecoratorMetadata); - JS仅在ES7提案中支持,需通过Babel插件(如
@babel/plugin-proposal-decorators)转换; - 两者核心语法一致,但TS通过
Reflect.metadata提供更完善的元数据操作能力。
- TS完全支持装饰器(需在tsconfig中开启
七、总结
“装饰器是一种通过高阶函数实现的代码增强模式,能在不修改原对象的前提下添加功能。它在框架中广泛用于路由定义、权限校验等场景,本质是编译期执行的元编程技术。使用时需注意装饰器类型(类/方法/属性/参数)的参数差异,以及TS与JS的语法兼容问题。在实际项目中,装饰器能有效减少代码耦合,提升可维护性,但需避免过度使用导致逻辑复杂。”