书接上文:IOC 是容器根据依赖图帮你自动创建对象并自动组装
容器需要扫描依赖, 形成一个依赖图, 那么容器如何扫描依赖呢?
class A {
b
contructor(@Inject('B') b) {
this.b = b
}
}
需要根据上面的代码,我们需要扫描出 A构造函数的第一个参数是 B ,并记录下 类似的依赖图 A.construtor[0] => B
在用户 container.get('A') 的时候, 通过之前的依赖图可知, 需要先创建一个B, 并作为 A的构造函数传入
目前 js 世界里,大部分IOC的实现都需要依赖 reflect-metadata 这个库,主要是为Reflect API的 metadata-extensionECMAScript提案提供polyfill
这个提案目前处于 TC39 标准化过程的第 3 阶段.
那么具体这个提案是啥呢?
大致就是在一个对象或者其原型链上额外存点东西(存的东西被称之为元数据)
我们就可以利用这个特性,将我们的依赖关系存进去
提案的具体内容是:
所有常规对象都有一个内部槽[[Metadata]],用于存储元数据,其值为null或一个Map对象。
通过 Reflect.defineMetadata(metadataKey, metadataValue, target) 这样的方式来给 target 设置元数据
例如:
// 设置元数据, 记录依赖
// 记录下 A 有一个依赖, 位置在构造函数的第一个, 依赖的对象是B
Reflect.defineMetadata('contructorDependency', { index: 0 , key: 'B' }, A)
我们结合typescript的装饰器语法 尝试来实现 “Inject” 装饰器
// 实现 Inject 装饰器,用于构造函数的依赖记录
function Inject(moduleIdentifier) {
// 这里的 moduleIdentifier 是依赖的类的名字
return (targetClass, propKey, index?) => {
// 这里的 index 是构造函数参数的下标
Reflect.defineMetadata('contructorDependency', { index, key: moduleIdentifier }, targetClass)
// 通过 Reflect.defineMetadata 记录在 targetClass 的元数据中
}
}
// 使用 Inject 装饰器
class A {
b
// 使用 Inject 装饰器语法, 自动在 A 上记录元数据 —— 依赖第一个参数为 B
contructor(@Inject('B') b) {
this.b = b
}
}
// 通过 Reflect.getMetadata 获取 A 的元数据
Reflect.getMetadata('contructorDependency', A) // 这样就可以得到之前记录的信息 { index: 0, key: 'B' }
// 然后 ioc框架 就可以在 container.get(A) 的时候,就可以根据之前记录的依赖信息,
// 自动先构造B ,并作为第一个参数,传给A
const b = new B()
const a = new A(b)
return a
当然这里我们的数据结构都是非常不严谨的,例如需要多个参数怎么办,如下?
class A {
b
c
contructor(@Inject('B') b, @Inject('C') c) {
this.b = b
this.c = c
}
}
属性注入怎么办?
class A {
@Inject('D') d
contructor() {}
}
诸多细节问题我们以后再聊