依赖注入容器的核心作用是自动管理类之间的依赖关系,减少手动创建依赖的冗余代码,而JavaScript的反射API提供了获取类构造函数参数类型的能力,正好可以作为实现依赖自动识别的基础。

反射API的核心能力铺垫
JavaScript的Reflect对象和Reflect.metadata相关的装饰器能力,可以让我们在类定义阶段为其添加元数据,后续运行时可以读取这些元数据获取依赖信息。首先需要了解两个关键API:
Reflect.defineMetadata:用于为类或者类的属性定义元数据Reflect.getMetadata:用于读取之前定义的元数据
如果要使用元数据相关的反射能力,需要先引入反射 polyfill,或者使用支持装饰器元数据提案的运行环境。
依赖注入容器的基本实现步骤
1. 定义依赖标识与元数据装饰器
我们首先需要一个装饰器,用来标记类的构造函数参数对应的依赖类型,方便后续反射读取。
// 定义依赖注入的装饰器,用于标记构造函数参数的依赖类型
function Inject(token) {
return function (target, propertyKey, parameterIndex) {
// 获取当前构造函数已有的参数元数据,没有则初始化为空数组
const existingParameters = Reflect.getMetadata('design:paramtypes', target) || [];
// 将当前参数的依赖标识存入元数据
Reflect.defineMetadata('inject:paramtypes', [...existingParameters, token], target);
};
}
// 定义类级别的装饰器,标记该类可以被容器管理
function Service(token) {
return function (target) {
// 为类定义服务标识元数据
Reflect.defineMetadata('service:token', token, target);
};
}
2. 实现容器核心逻辑
容器的核心功能是注册服务、解析服务的依赖并完成实例化,我们需要维护一个服务注册表,存储服务标识和对应的类或者实例。
class DIContainer {
constructor() {
// 存储服务标识到类定义的映射
this.serviceRegistry = new Map();
// 存储已经实例化的单例服务
this.instanceCache = new Map();
}
// 注册服务,token是服务标识,target是对应的类
register(token, target) {
this.serviceRegistry.set(token, target);
}
// 解析并获取服务实例
resolve(token) {
// 如果缓存中已有实例,直接返回
if (this.instanceCache.has(token)) {
return this.instanceCache.get(token);
}
// 获取对应的类定义
const target = this.serviceRegistry.get(token);
if (!target) {
throw new Error(`未找到标识为 ${token} 的服务`);
}
// 获取构造函数的依赖元数据
const paramTypes = Reflect.getMetadata('inject:paramtypes', target) || [];
// 递归解析所有依赖的实例
const dependencies = paramTypes.map(depToken => this.resolve(depToken));
// 创建实例,将依赖传入构造函数
const instance = new target(...dependencies);
// 存入缓存,实现单例
this.instanceCache.set(token, instance);
return instance;
}
}
3. 使用示例
我们定义几个存在依赖关系的服务,测试容器的效果。
// 定义数据库服务
class DatabaseService {
query(sql) {
return `执行SQL: ${sql}`;
}
}
// 为数据库服务定义标识
Reflect.defineMetadata('service:token', 'DatabaseService', DatabaseService);
// 定义用户服务,依赖数据库服务
@Service('UserService')
class UserService {
constructor(database) {
this.database = database;
}
getUser() {
return this.database.query('SELECT * FROM user');
}
}
// 标记构造函数的第一个参数依赖数据库服务
Inject('DatabaseService')(UserService, null, 0);
// 初始化容器并注册服务
const container = new DIContainer();
container.register('DatabaseService', DatabaseService);
container.register('UserService', UserService);
// 获取用户服务实例并调用方法
const userService = container.resolve('UserService');
console.log(userService.getUser()); // 输出:执行SQL: SELECT * FROM user
实现注意事项
- 上述示例是简化实现,实际生产环境中需要处理循环依赖、 transient 实例(非单例)、可选依赖等场景
- 反射元数据的支持依赖运行环境,如果环境不支持装饰器元数据,可以手动为每个类定义依赖列表,替代反射自动获取的方式
- 服务标识建议使用唯一的字符串或者Symbol,避免冲突
适用场景
这种基于反射API实现的依赖注入容器适合中大型前端项目或者Node.js后端项目,尤其是当项目中存在大量服务类、依赖关系复杂时,可以大幅减少手动管理依赖的代码,提升代码的可维护性。如果项目规模较小,依赖关系简单,手动注入或者简单的工厂模式可能更轻量。
JavaScript反射API依赖注入依赖注入容器修改时间:2026-07-08 07:42:22