25.深入装饰器 副本.md 来源:https://my.feishu.cn/file/WK38bgacUohIjLxZP5LcjUCInKf 采集状态:已完成正文采集;已从查看代码模式逐行提取,行号 1 至 173 连续,并核对滚动到底。 在 TypeScript 的高级特性中,**装饰器 (Decorators)** 无疑是最具“魔法”色彩的一个。它是一种特殊的声明,可以被附加到类、方法、访问器、属性或参数上,以一种**声明式**的语法来修改或注解它们。装饰器本质上是一种元编程(Metaprogramming)——即编写能够操作其他代码的代码。 它们在许多流行的框架(如 Angular、NestJS、TypeORM)中扮演着核心角色,用于实现依赖注入、路由映射、数据验证等功能。理解装饰器,是深入这些现代框架内部原理的钥匙。 ### **重要警告:这是一个实验性功能** 在开始之前,必须强调:**装饰器目前仍然是 TypeScript 中的一项实验性功能。** 这意味着: 1. 它基于一个尚未最终确定的 TC39(ECMAScript 标准委员会)提案。 2. 其未来的语法和行为可能会在 TypeScript 的新版本中发生变化。 3. 要使用它,你**必须**在你的 `tsconfig.json` 文件中显式地启用 `experimentalDecorators` 选项: ```json { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true // 通常与装饰器一同使用,用于元数据反射 } } ``` ### 装饰器的本质:一个特殊的函数 抛开所有魔法外衣,装饰器本质上就是一个**函数**。这个函数会在**类被定义时**(而不是实例化时)被立即调用,并接收到有关其所装饰的目标的信息。TypeScript 根据装饰器被附加到的位置(类、方法等),向这个函数传入不同的参数。 让我们来探索装饰器的五种主要类型。 #### 1. 类装饰器 (Class Decorator) * **作用**:应用于类的构造函数。 * **参数**:接收一个参数——被装饰的类的构造函数。 * **用途**:可以用来监视、修改或替换整个类的定义。 **示例:一个 `sealed` 装饰器,防止类被进一步扩展。** ```typescript function sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); console.log(`Class ${constructor.name} has been sealed.`); } @sealed class BugReport { type = "report"; title: string; constructor(t: string) { this.title = t; } } // 在类定义时,控制台就会输出: "Class BugReport has been sealed." ``` `@sealed` 语法就是将 `sealed` 函数应用到 `BugReport` 类上。 #### 2. 方法装饰器 (Method Decorator) * **作用**:应用于类的方法。 * **参数**:接收三个参数: 1. `target`: 对于静态方法是类的构造函数,对于实例方法是类的原型。 2. `propertyKey`: 方法的名称(一个字符串)。 3. `descriptor`: 该方法的属性描述符 (`PropertyDescriptor`)。 * **用途**:可以用来监视、修改甚至替换方法的实现。这是最常用的装饰器之一。 **示例:一个 `log` 装饰器,在方法调用前后打印日志。** ```typescript function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; // 保存原始方法 // 修改属性描述符,替换原始方法 descriptor.value = function (...args: any[]) { console.log(`Calling method: ${propertyKey} with args:`, args); const result = originalMethod.apply(this, args); console.log(`Method ${propertyKey} returned:`, result); return result; }; return descriptor; } class Calculator { @log add(a: number, b: number): number { return a + b; } } const calc = new Calculator(); calc.add(2, 3); // 控制台输出: // Calling method: add with args: [2, 3] // Method add returned: 5 ``` #### 3. 属性装饰器 (Property Decorator) 和 4. 访问器装饰器 (Accessor Decorator) * **作用**:分别应用于类的属性和 `get`/`set` 访问器。 * **参数**:接收两个参数 (`target` 和 `propertyKey`)。 * **用途**:主要用于**记录元数据**,因为它们不能直接修改属性的值(属性的值只在实例创建后才存在)。 #### 5. 参数装饰器 (Parameter Decorator) * **作用**:应用于构造函数或方法的参数。 * **参数**:接收三个参数: 1. `target`: 类的构造函数或原型。 2. `propertyKey`: 方法的名称(构造函数中为 `undefined`)。 3. `parameterIndex`: 参数在参数列表中的索引。 * **用途**:几乎完全用于**记录元数据**。例如,标记哪些参数需要通过依赖注入来提供。 **示例:依赖注入的元数据记录**(需要 `reflect-metadata` 库) ```typescript import "reflect-metadata"; function Inject(service: any) { return function (target: Object, propertyKey: string | symbol, parameterIndex: number) { const existingInjections = Reflect.getOwnMetadata("injections", target, propertyKey) || []; existingInjections.push({ index: parameterIndex, service }); Reflect.defineMetadata("injections", existingInjections, target, propertyKey); }; } class ApiService {} class MyComponent { constructor(@Inject(ApiService) private api: ApiService) {} } ``` 这个 `@Inject` 装饰器并没有改变 `api` 参数,它只是记录下:“第 0 个参数需要一个 `ApiService` 的实例”。然后,依赖注入框架会读取这些元数据并完成实际的注入工作。 ### 装饰器工厂与组合 * **装饰器工厂 (Decorator Factory)**:如果你想向装饰器传递参数,你需要创建一个“工厂函数”,它返回一个真正的装饰器函数。 ```typescript function log(message: string) { // 这是工厂,返回真正的装饰器 return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { // ... 装饰器逻辑 ... console.log(message); }; } class C { @log("Hello from decorator!") myMethod() {} } ``` * **装饰器组合**:可以对一个声明应用多个装饰器。它们的执行顺序是: 1. **评估**:自上而下。 2. **调用**:自下而上(像函数组合一样)。 ### 结论:何时使用装饰器? 装饰器是一把双刃剑。 **优势:** * **声明式**:代码意图清晰,将横切关注点(如日志、权限)与核心业务逻辑分离。 * **可组合**:可以轻松地组合多个装饰器来添加复杂行为。 * **框架利器**:为框架作者提供了强大的工具来减少模板代码,实现依赖注入、ORM 等。 **风险:** * **实验性**:API 可能会变。 * **调试困难**:它们增加了代码的“魔法”程度,可能让调试变得不那么直观。 **建议:** * 对于**应用程序开发者**:谨慎使用。在你真正需要一个清晰的横切关注点解决方案时,再考虑它。不要为了使用而使用。 * 对于**库或框架的开发者**:装饰器是你的强大盟友,值得深入研究。 * 对于**所有开发者**:即使你不亲自编写,也应该理解它们的工作原理,因为你很可能在你使用的框架中遇到它们。 装饰器代表了 TypeScript 与 JavaScript 语言演进的深度融合,它开启了元编程的大门,让我们能够以一种更优雅、更声明式的方式来构建复杂的软件系统。