依賴注入是一個很重要的設計模式。 它使用得很是普遍,以致於幾乎每一個人都把它簡稱爲 DI 。
Angular 有本身的依賴注入框架,離開它,你幾乎沒辦法構建出 Angular 應用。
下面介紹一種簡單的(只有100行左右代碼)實現相似Angular依賴注入的方式,先看一個例子git
// a.service.ts @Injectable() export class AService { constructor() { } public doSomething() { console.log('this is AService::doSomething'); } } // b.service.ts @Injectable() export class BService { constructor(private readonly a: AService) { } public doSomething() { this.a.doSomething(); console.log('this is BService::doSomething'); } } // some.module.ts @Module({ providers: [ AService, BService, ], }) export class SomeModule { }
在上面的例子中,咱們建立了兩個Service
,其中BService
依賴於AService
,那麼BService
能夠在其構造函數中聲明其依賴,咱們須要一種方法去自動將AService
的實例注入到BService
的私有隻讀變量a
中,接下來介紹實現的步驟。github
首先咱們應該對Typescript
進行配置,使其支持Javascript
的裝飾器(或者說是註解),下面是個人配置文件:typescript
{ "compilerOptions": { "lib": [ "dom", "es2015" ], "target": "es5", "module": "commonjs", "outDir": "./dist", "strict": true, "esModuleInterop": true, "experimentalDecorators": true, "emitDecoratorMetadata": true } }
其中,experimentalDecorators
表示爲ES裝飾器啓用實驗支持,emitDecoratorMetadata
表示在源代碼中爲裝飾聲明產生類型的元數據,配置好了這兩項以後,咱們才能使用Typescript
的裝飾器,接下來咱們須要安裝依賴reflect-metadata
,用於讀取和設置元數據。json
@Injectable
是一個裝飾器,它標識被裝飾的類是一個Provider
,它的聲明方式以下設計模式
export function Injectable(): ClassDecorator { return (target) => { }; }
咱們在此裝飾器中什麼都不作,他只起到一個標識的做用。框架
@Module
是一個裝飾器,它標識被裝飾的類是一個Module
,它的聲明方式以下dom
const DI_IMPORTS_SYMBOL = Symbol('di:imports') const DI_PROVIDERS_SYMBOL = Symbol('di:providers') export function Module(options: { imports?: Array<any>, providers?: Array<any> }): ClassDecorator { return (target) => { Reflect.defineMetadata(DI_IMPORTS_SYMBOL, new Set(options.imports || []), target); Reflect.defineMetadata(DI_PROVIDERS_SYMBOL, new Set(options.providers || []), target); } }
咱們使用Set
來存儲一個Module
做用域中它所聲明的Providers
和它所引入的其餘模塊。ide
咱們但願達到的目的是,在使用時能夠經過Factory.create(SomeModule)
來獲取一個Module
的實例,而後經過Module
實例來獲取一個Provider
,例如Factory.create(SomeModule).get(BService).doSomething()
,此時應該輸出函數
Factory.create(SomeModule).get(BService).doSomething(); // this is AService::doSomething // this is BService::doSomething
Talk is cheap. Show me the code:this
export namespace Factory { export function create(module: Type) { const imports: Set<Type> = Reflect.getMetadata(DI_IMPORTS_SYMBOL, module); const providers: Set<any> = Reflect.getMetadata(DI_PROVIDERS_SYMBOL, module); const providersMap = new Map(); const importModules = Array.from(imports).map((importModule) => { let moduleInstance: ModuleInstance = moduleInstances.get(importModule); if(!moduleInstance) { moduleInstance = create(importModule); moduleInstances.set(importModule, moduleInstance); } return moduleInstance; }); const moduleInstance = new ModuleInstance(importModules, providersMap); providers.forEach(provider => { createProvider(provider, providers, moduleInstance); }); return moduleInstance; } function createProvider(provider: any, providers: Set<any>, moduleInstance: ModuleInstance) { let providerInstance = moduleInstance.providers.get(provider); if(providerInstance) { return providerInstance; } const deps: Array<any> = Reflect.getMetadata('design:paramtypes', provider); if(!deps) { throw new Error(`No provider named ${ provider.name }, do yout add @Injectable() to this provider?`); } const args = deps.map(dep => { let depInstance = moduleInstance.providers.get(dep); if(!depInstance) { if(providers.has(dep)) { depInstance = createProvider(dep, providers, moduleInstance); } else { moduleInstance.imports.some(imp => { depInstance = createProvider(dep, new Set(), imp); return !!depInstance; }); } } if(!depInstance) { throw new Error(`can not found provider ${ dep.name }`); } return depInstance; }); providerInstance = new provider(...args); moduleInstance.providers.set(provider, providerInstance); return providerInstance; } export class ModuleInstance { constructor( public imports: Array<ModuleInstance>, public providers: Map<any, any>) { } get<T>(provider: Type<T>) { let instance: T = this.providers.get(provider); if(!instance) { this.imports.some(imp => { instance = imp.get(provider); return !!instance; }); } if(!instance) { throw new Error(`No provider named: ${ provider.name }`); } return instance; } } }
以上就是整個依賴注入的實現了,感興趣的朋友能夠到個人Github上面查看源代碼,核心文件就是lib/di.ts
,地址是
https://github.com/hungtcs/li...