InversityJS 是一個 IoC 框架。IoC(Inversion of Control) 包括依賴注入(Dependency Injection) 和依賴查詢(Dependency Lookup)。
相比於類繼承的方式,控制反轉解耦了父類和子類的聯繫。
案例解析
import 'reflect-metadata'
import { inject, injectable, Container } from 'inversify'
const container = new Container()
@injectable()
class PopMusic {
getName() {
return '流行音樂'
}
}
container.bind('request1').to(PopMusic)
@injectable()
class ClassicalMusic {
getName() {
return '古典音樂'
}
}
container.bind('request2').to(ClassicalMusic)
@injectable()
class Music {
pm: any
cm: any
constructor(
@inject('request1') popMusic: any,
@inject('request2') classicalMusic: any) {
this.pm = popMusic
this.cm = classicalMusic
}
getName() {
const result = this.pm.getName() + this.cm.getName()
return result
}
}
container.bind('Plan').to(Music)
const music: any = container.get('Plan')
console.log(music.getName()) // 流行音樂古典音樂
上述案例可以抽象爲下圖:
虛線表示可以注入,但在代碼中沒有表現出來。
代碼流程可概括如下:
1.將所有相關類(這裏指 Music、popMusic、classicMusic) 通過 @injectable
聲明進 container
容器;
2.通過 container.get()
獲取 container.bind().to(target)
中的目標對象(這裏指 Music);
3.如果目標對象中的 constructor() 裏有 @inject()
, 則將相應的實例(這裏指 PopMusic 與 classicalMusic 的實例)當作構造函數的參數'注入';
inject/injectable 相關源碼
inject 源碼簡化如下:
// 這是一個屬性裝飾器
function inject(serviceIdentifier) {
return function (target, targetKey) {
const metadataValue = { [targetKey]: [Metadata { key: 'inject', value: serviceIdentifier })] }
Reflect.defineMetadata('inversify:tagged_props', metadataValue, target.constructor);
}
}
injectable 源碼簡化如下:
// 這是一個類裝飾器
function injectable() {
return function (target) {
const metadataValue = []
Reflect.defineMetadata('inversify:paramtypes', metadataValue, target)
return target
}
}
從簡化版源碼中可以看到 inject/injectable 最終是對 Reflect.defineMetadata()
的一個使用。可以將 metadata 看成是一種相對高效的數據結構。
reflect-metadata
InversityJS 深度結合了 reflect-metadata, reflect-metadata 在 Reflect 基礎上對其 api 進行了擴展。
metadata 本質上是一個 WeakMap
對象。擴展:Map 和 WeakMap 的區別
Reflect.defineMetadata(metadataKey, metadataValue, target[, propertyKey])
簡化版實現如下:
const Metadata = new WeakMap()
function defineMetadata(metadataKey, metadataValue, target, propertyKey) {
metadataMap = new Map()
metadataMap.set(metadataKey, metadataValue)
targetMetadata = new Map()
targetMetadata.set(propertyKey, metadataMap)
Metadata.set(target, targetMetadata)
}
Reflect.getOwnMetadata(metadataKey, target[, propertyKey])
簡化版實現如下:
function getOwnMetadata(metadataKey, target, propertyKey) {
var targetMetadata = Metadata.get(target)
var metadataMap = targetMetadata.get(propertyKey)
return metadataMap.get(metadataKey)
}
其數據結構可表示如下:
WeakMap {
target: Map {
propertyKey: Map {
metadataKey: metadataValue
}
}
}