本文基于自身理解進行輸出,目的在于交流學習,如有不對,還望各位看官指出,
DI
DI—Dependency Injection,即“依賴注入”:物件之間依賴關系由容器在運行期決定,形象的說,即由容器動態的將某個物件注入到物件屬性之中,依賴注入的目的并非為軟體系統帶來更多功能,而是為了提升物件重用的頻率,并為系統搭建一個靈活、可擴展的框架,
使用方式
首先看一下常用依賴注入 (DI)的方式:
function Inject(target: any, key: string){
target[key] = new (Reflect.getMetadata('design:type',target,key))()
}
class A {
sayHello(){
console.log('hello')
}
}
class B {
@Inject // 編譯后等同于執行了 @Reflect.metadata("design:type", A)
a: A
say(){
this.a.sayHello() // 不需要再對class A進行實體化
}
}
new B().say() // hello
原理分析
TS在編譯裝飾器的時候,會通過執行__metadata函式多回傳一個屬性裝飾器@Reflect.metadata,它的目的是將需要實體化的service以元資料'design:type'存入reflect.metadata,以便我們在需要依賴注入時,通過Reflect.getMetadata獲取到對應的service, 并進行實體化賦值給需要的屬性,
@Inject編譯后代碼:
var __metadata = https://www.cnblogs.com/o2team/archive/2021/08/26/(this && this.__metadata) || function (k, v) {
if (typeof Reflect ==="object" && typeof Reflect.metadata =https://www.cnblogs.com/o2team/archive/2021/08/26/=="function") return Reflect.metadata(k, v);
};
// 由于__decorate是從右到左執行,因此, defineMetaData 會優先執行,
__decorate([
Inject,
__metadata("design:type", A) // 作用等同于 Reflect.metadata("design:type", A)
], B.prototype, "a", void 0);
即默認執行了以下代碼:
Reflect.defineMetadata("design:type", A, B.prototype, 'a');
Inject函式需要做的就是從metadata中獲取對應的建構式并構造實體物件賦值給當前裝飾的屬性
function Inject(target: any, key: string){
target[key] = new (Reflect.getMetadata('design:type',target,key))()
}
不過該依賴注入方式存在一個問題:
- 由于
Inject函式在代碼編譯階段便會執行,將導致B.prototype在代碼編譯階段被修改,這違反了六大設計原則之開閉原則(避免直接修改類,而應該在類上進行擴展)
那么該如何解決這個問題呢,我們可以借鑒一下TypeDI的思想,
typedi
typedi 是一款支持TypeScript和JavaScript依賴注入工具
typedi 的依賴注入思想是類似的,不過多維護了一個container
1. metadata
在了解其container前,我們需要先了解 typedi 中定義的metadata,這里重點講述一下我所了解的比較重要的幾個屬性,
id: service的唯一標識type: 保存service建構式value: 快取service對應的實體化物件
const newMetadata: ServiceMetadata<T> = {
id: ((serviceOptions as any).id || (serviceOptions as any).type) as ServiceIdentifier, // service的唯一標識
type: (serviceOptions as ServiceMetadata<T>).type || null, // service 建構式
value: (serviceOptions as ServiceMetadata<T>).value || EMPTY_VALUE, // 快取service對應的實體化物件
};
2. container 作用
function ContainerInstance() {
this.metadataMap = new Map(); //保存metadata映射關系,作用類似于Refect.metadata
this.handlers = []; // 事件待處理佇列
get(){}; // 獲取依賴注入后的實體化物件
...
}
- this. metadataMap -
@service會將service建構式以metadata形式保存到this.metadataMap中,- 快取實體化物件,保證單例;
- this.handlers -
@inject會將依賴注入操作的物件、目標、行為以 object 形式 push 進 handlers 待處理陣列,- 保存
建構式與靜態型別及屬性間的映射關系,
- 保存
{
object: target, // 當前等待掛載的類的原型物件
propertyName: propertyName, // 目標屬性值
index: index,
value: function (containerInstance) { // 行為
var identifier = Reflect.getMetadata('design:type', target, propertyName)
return containerInstance.get(identifier);
}
}
@inject將該物件 push 進一個等待執行的 handlers 待處理陣列里,當需要用到對應 service 時執行 value函式 并修改 propertyName,
if (handler.propertyName) {
instance[handler.propertyName] = handler.value(this);
}
- get - 物件實體化操作及依賴注入操作
- 避免直接修改類,而是對其實體化物件的屬性進行拓展;
相關結論
typedi中的實體化操作不會立即執行, 而是在一個handlers待處理陣列,等待Container.get(B),先對B進行實體化,然后從handlers待處理陣列取出對應的value函式并執行修改實體化物件的屬性值,這樣不會影響Class B 自身- 實體的屬性值被修改后,將被快取到
metadata.value(typedi 的單例服務特性),
相關資料可查看:
https://stackoverflow.com/questions/55684776/typedi-inject-doesnt-work-but-container-get-does
new B().say() // 將會輸出sayHello is undefined
Container.get(B).say() // hello word
實作一個簡易版 DI Container
此處代碼依賴TS,不支持JS環境
interface Handles {
target: any
key: string,
value: any
}
interface Con {
handles: Handles [] // handlers待處理陣列
services: any[] // service陣列,保存已實體化的物件
get<T>(service: new () => T) : T // 依賴注入并回傳實體化物件
findService<T>(service: new () => T) : T // 檢查快取
has<T>(service: new () => T) : boolean // 判斷服務是否已經注冊
}
var container: Con = {
handles: [], // handlers待處理陣列
services: [], // service陣列,保存已實體化的物件
get(service){
let res: any = this.findService(service)
if(res){
return res
}
res = new service()
this.services.push(res)
this.handles.forEach(handle=>{
if(handle.target !== service.prototype){
return
}
res[handle.key] = handle.value
})
return res
},
findService(service){
return this.services.find(instance => instance instanceof service)
},
// service是否已被注冊
has(service){
return !!this.findService(service)
}
}
function Inject(target: any, key: string){
const service = Reflect.getMetadata('design:type',target,key)
// 將實體化賦值操作快取到handles陣列
container.handles.push({
target,
key,
value: new service()
})
// target[key] = new (Reflect.getMetadata('design:type',target,key))()
}
class A {
sayA(name: string){
console.log('i am '+ name)
}
}
class B {
@Inject
a: A
sayB(name: string){
this.a.sayA(name)
}
}
class C{
@Inject
c: A
sayC(name: string){
this.c.sayA(name)
}
}
// new B().sayB(). // Cannot read property 'sayA' of undefined
container.get(B).sayB('B')
container.get(C).sayC('C')
· 往期精彩 ·
【不懂物理的前端不是好的游戲開發者(一)—— 物理引擎基礎】
【3D性能優化 | 說一說glTF檔案壓縮】
【京東購物小程式 | Taro3 專案分包實踐】
歡迎關注凹凸實驗室博客:aotu.io
或者關注凹凸實驗室公眾號(AOTULabs),不定時推送文章:

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/296380.html
標籤:其他
