JavaScript 中的装饰器(Decorators)高级应用:元数据反射与依赖注入
描述:装饰器是 JavaScript 中一种用于修改类、方法、属性或参数的语法,它基于装饰器提案,目前处于 Stage 3 阶段。装饰器允许开发者以声明式的方式增强或修改代码行为,在框架和库中广泛用于实现元数据反射、依赖注入、日志记录等功能。本知识点将深入讲解装饰器的高级应用,特别是如何结合元数据反射实现依赖注入。
解题过程:
-
装饰器基本语法与分类:
装饰器是一个函数,它接收目标对象的相关信息作为参数,并返回修改后的描述符或目标。装饰器分为:- 类装饰器:应用于类构造函数,可修改或替换类定义。
- 方法装饰器:应用于类的方法,可修改方法行为(如日志、验证)。
- 属性装饰器:应用于类的属性,常用于添加元数据。
- 参数装饰器:应用于方法的参数,常用于依赖注入场景。
示例:一个简单的类装饰器,用于添加版本信息:
function addVersion(version) { return function (target) { target.prototype.version = version; return target; }; } @addVersion("1.0.0") class MyClass {} console.log(new MyClass().version); // 输出:"1.0.0" -
元数据反射 API(Reflect Metadata):
元数据是附加到代码上的数据,用于描述代码的额外信息。JavaScript 通过reflect-metadata库支持反射元数据 API,提供:Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey?):定义元数据。Reflect.getMetadata(metadataKey, target, propertyKey?):获取元数据。
元数据常用于存储类型信息、依赖关系等,是实现高级装饰器功能的基础。
示例:为属性添加类型元数据:
import "reflect-metadata"; function Type(type) { return function (target, propertyKey) { Reflect.defineMetadata("design:type", type, target, propertyKey); }; } class User { @Type(String) name = ""; } const metadata = Reflect.getMetadata("design:type", User.prototype, "name"); console.log(metadata); // 输出:String -
依赖注入(Dependency Injection, DI)的实现:
依赖注入是一种设计模式,通过外部提供依赖对象,降低代码耦合度。结合装饰器和元数据,可构建简单的 DI 容器:- 使用类装饰器标记可注入的服务。
- 使用参数装饰器在构造函数中声明依赖。
- 通过反射元数据解析依赖类型,并自动实例化。
实现步骤:
a. 定义服务装饰器@Injectable(),用于标记类为可注入服务:const Injectable = () => (target) => { // 可在此处注册服务到容器 return target; };b. 定义注入装饰器
@Inject(),用于构造函数参数,声明依赖:function Inject() { return (target, propertyKey, parameterIndex) => { const paramTypes = Reflect.getMetadata("design:paramtypes", target); const dependencyType = paramTypes[parameterIndex]; // 存储依赖类型信息,供容器解析 Reflect.defineMetadata("inject:dependencies", dependencyType, target, parameterIndex); }; }c. 创建 DI 容器,负责解析依赖并实例化类:
class Container { services = new Map(); register(token, service) { this.services.set(token, service); } resolve(TargetClass) { const paramTypes = Reflect.getMetadata("design:paramtypes", TargetClass) || []; const dependencies = paramTypes.map((type) => { if (this.services.has(type)) { return this.services.get(type); } // 递归解析依赖 return this.resolve(type); }); return new TargetClass(...dependencies); } } -
完整示例:构建一个简易 DI 系统:
结合元数据装饰器,实现一个日志服务和用户服务的依赖注入:import "reflect-metadata"; // 日志服务 @Injectable() class Logger { log(message) { console.log(`[LOG]: ${message}`); } } // 用户服务,依赖 Logger @Injectable() class UserService { constructor(@Inject() logger) { this.logger = logger; } getUser(id) { this.logger.log(`Fetching user ${id}`); return { id, name: "Alice" }; } } // 解析并运行 const container = new Container(); container.register(Logger, new Logger()); const userService = container.resolve(UserService); userService.getUser(1); // 输出:[LOG]: Fetching user 1 -
高级应用:装饰器工厂与组合装饰器:
装饰器工厂是返回装饰器函数的函数,允许传递参数定制行为。多个装饰器可组合使用,执行顺序为从上到下(靠近目标的先执行):function log(message) { console.log(`Log setup: ${message}`); return function (target) { console.log(`Log applied: ${message}`); }; } function seal(target) { Object.seal(target); return target; } @log("Class Decorator") @seal class Example {} // 输出顺序: // Log setup: Class Decorator // Log applied: Class Decorator -
装饰器在框架中的应用:
现代框架如 Angular、Nest.js 大量使用装饰器实现依赖注入、路由定义、中间件等。理解装饰器与元数据反射的协作,有助于深入掌握这些框架的原理。
注意:装饰器目前需通过 Babel 或 TypeScript 编译使用,且浏览器原生支持有限。在实际项目中,应结合构建工具配置相应的插件(如 @babel/plugin-proposal-decorators)。