前端学 IOC(2)- 如何扫描依赖?

259 阅读2分钟

书接上文: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() {}
}

诸多细节问题我们以后再聊