本篇文章帶大家聊聊Angular,介紹一下依賴注入的基本概念,希望對(duì)大家有所幫助!
作為“為大型前端項(xiàng)目”而設(shè)計(jì)的前端框架,Angular 其實(shí)有許多值得參考和學(xué)習(xí)的設(shè)計(jì),本系列主要用于研究這些設(shè)計(jì)和功能的實(shí)現(xiàn)原理。本文主要圍繞 Angular 中的最大特點(diǎn)——依賴注入,首先來(lái)介紹一些 Angular 依賴注入體系中的基本概念。
依賴注入
既然要介紹 Angular 框架的依賴注入設(shè)計(jì),那么先鋪墊一下依賴注入的基本概念。我們常常會(huì)搞混依賴倒置原則(DIP)、控制反轉(zhuǎn)(IoC)、依賴注入(DI)這幾個(gè)概念,因此這里會(huì)先簡(jiǎn)單介紹一下。【相關(guān)教程推薦:《angular教程》】
依賴倒置原則、控制反轉(zhuǎn)、依賴注入
低耦合、高內(nèi)聚大概是每個(gè)系統(tǒng)的設(shè)計(jì)目標(biāo)之一,而為此產(chǎn)生了很多的設(shè)計(jì)模式和理念,其中便包括依賴倒置原則、控制反轉(zhuǎn)的設(shè)計(jì)思想。
(1) 依賴倒置原則(DIP)。
依賴倒置原則的原始定義為:
- 高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴其抽象;
- 抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象。
簡(jiǎn)單說(shuō)便是:模塊間不應(yīng)該直接依賴對(duì)方,應(yīng)該依賴一個(gè)抽象的規(guī)則(接口或者時(shí)抽象類)。
(2) 控制反轉(zhuǎn)(IoC)。
控制反轉(zhuǎn)的定義為:模塊間的依賴關(guān)系從程序內(nèi)部提到外部來(lái)實(shí)例化管理。即對(duì)象在被創(chuàng)建的時(shí)候,由一個(gè)調(diào)控系統(tǒng)內(nèi)所有對(duì)象的外界實(shí)體控制,并將其所依賴的對(duì)象的引用傳遞(注入)給它。
實(shí)現(xiàn)控制反轉(zhuǎn)主要有兩種方式:
- 依賴注入:被動(dòng)的接收依賴對(duì)象
- 依賴查找:主動(dòng)索取依賴的對(duì)象
(3) 依賴注入。
依賴注入,是控制反轉(zhuǎn)的最為常見(jiàn)的一種技術(shù)。
依賴倒置和控制反轉(zhuǎn)兩者相輔相成,常??梢砸黄鹗褂茫捎行У亟档湍K間的耦合。
Angular 中的依賴注入
在 Angular 中,同樣使用了依賴注入的技術(shù),DI 框架會(huì)在實(shí)例化某個(gè)類時(shí),向其提供這個(gè)類所聲明的依賴項(xiàng)(依賴項(xiàng):指當(dāng)類需要執(zhí)行其功能時(shí),所需要的服務(wù)或?qū)ο螅?/p>
Angular 中的依賴注入基本上是圍繞著組件或者是模塊展開(kāi)的,主要用于給新建的組件提供依賴。
Angular 中主要的依賴注入機(jī)制是注入器機(jī)制:
- 應(yīng)用中所需的任何依賴,都必須使用該應(yīng)用的注入器來(lái)注冊(cè)一個(gè)提供者,以便注入器可以使用這個(gè)提供者來(lái)創(chuàng)建新實(shí)例
- Angular 會(huì)在啟動(dòng)過(guò)程中,創(chuàng)建全應(yīng)用級(jí)注入器以及所需的其它注入器
這里面主要涉及兩個(gè)概念,分別是Injector 注入器和Provider 提供商,我們來(lái)看看。
Injector 注入器
Injector 注入器用于創(chuàng)建依賴,會(huì)維護(hù)一個(gè)容器來(lái)管理這些依賴,并盡可能地復(fù)用它們。注入器會(huì)提供依賴的一個(gè)單例,并把這個(gè)單例對(duì)象注入到多個(gè)組件中。
顯然,作為一個(gè)用來(lái)創(chuàng)建、管理、維護(hù)依賴的容器,注入器的功能很簡(jiǎn)單:創(chuàng)建依賴實(shí)例、獲取依賴實(shí)例、管理依賴實(shí)例。我們也可以從抽象類Injector
的源碼中看出來(lái):
export abstract class Injector { // 找不到依賴 static THROW_IF_NOT_FOUND = THROW_IF_NOT_FOUND; // NullInjector 是樹(shù)的頂部 // 如果你在樹(shù)中向上走了很遠(yuǎn),以至于要在 NullInjector 中尋找服務(wù),那么將收到錯(cuò)誤消息,或者對(duì)于 @Optional(),返回 null static NULL: Injector = new NullInjector(); // 根據(jù)提供的 Token 從 Injector 檢索實(shí)例 abstract get<T>( token: Type<T> | AbstractType<T> | InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags ): T; // 創(chuàng)建一個(gè)新的 Injector 實(shí)例,該實(shí)例提供一個(gè)或多個(gè)依賴項(xiàng) static create(options: { providers: StaticProvider[]; parent?: Injector; name?: string; }): Injector; // ??defineInjectable 用于構(gòu)造一個(gè) InjectableDef // 它定義 DI 系統(tǒng)將如何構(gòu)造 Token,并且在哪些 Injector 中可用 static ?prov = ??defineInjectable({ token: Injector, providedIn: "any" as any, // ??inject 生成的指令:從當(dāng)前活動(dòng)的 Injector 注入 Token factory: () => ??inject(INJECTOR), }); static __NG_ELEMENT_ID__ = InjectorMarkers.Injector; }
也就是說(shuō),我們可以將需要共享的依賴實(shí)例添加到注入器中,并通過(guò) Token 查詢和檢索注入器來(lái)獲取相應(yīng)的依賴實(shí)例。
需要注意的是,Angular 中的注入器是分層的,因此查找依賴的過(guò)程也是向上遍歷注入器樹(shù)的過(guò)程。
這是因?yàn)樵?Angular 中,應(yīng)用是以模塊的方式組織的,具體可以參考5.模塊化組織篇。一般來(lái)說(shuō),頁(yè)面的 DOM 是以html
作為根節(jié)點(diǎn)的樹(shù)狀結(jié)構(gòu),以此為基礎(chǔ),Angular 應(yīng)用中的組件和模塊也是與之相伴的樹(shù)狀結(jié)構(gòu)。
而注入器服務(wù)于組件和模塊,同樣是掛載與模塊和組織上的樹(shù)狀結(jié)構(gòu)。因此,Injector 也劃分為模塊和組件級(jí)別,可分別為組件和模塊提供依賴的具體實(shí)例。注入器是可繼承的,這意味著如果指定的注入器無(wú)法解析某個(gè)依賴,它就會(huì)請(qǐng)求父注入器來(lái)解析它,我們同樣可以從上面的創(chuàng)建注入器代碼中看到:
// 創(chuàng)建一個(gè)新的 Injector 實(shí)例,可傳入 parent 父注入器 static create(options: {providers: StaticProvider[], parent?: Injector, name?: string}): Injector;
在某個(gè)注入器的范圍內(nèi),服務(wù)是單例的。也就是說(shuō),在指定的注入器中最多只有某個(gè)服務(wù)的最多一個(gè)實(shí)例。如果不希望在所有地方都使用該服務(wù)的同一個(gè)實(shí)例,則可以通過(guò)注冊(cè)多個(gè)注入器、并按照需要關(guān)聯(lián)到組件和模塊中的方式,來(lái)按需共享某個(gè)服務(wù)依賴的實(shí)例。
我們可以看到創(chuàng)建一個(gè)新的Injector
實(shí)例時(shí),傳入的參數(shù)包括Provider
,這是因?yàn)?code>Injector不會(huì)直接創(chuàng)建依賴,而是通過(guò)Provider
來(lái)完成的。每個(gè)注入器會(huì)維護(hù)一個(gè)提供者的列表,并根據(jù)組件或其它服務(wù)的需要,用它們來(lái)提供服務(wù)的實(shí)例。
Provider 提供者
Provider 提供者用來(lái)告訴注入器應(yīng)該如何獲取或創(chuàng)建依賴,要想讓注入器能夠創(chuàng)建服務(wù)(或提供其它類型的依賴),必須使用某個(gè)提供者配置好注入器。
一個(gè)提供者對(duì)象定義了如何獲取與 DI 令牌(token) 相關(guān)聯(lián)的可注入依賴,而注入器會(huì)使用這個(gè)提供者來(lái)創(chuàng)建它所依賴的那些類的實(shí)例。
關(guān)于 DI 令牌:
- 當(dāng)使用提供者配置注入器時(shí),就會(huì)把提供者和一個(gè) DI 令牌關(guān)聯(lián)起來(lái);
- 注入器維護(hù)一個(gè)內(nèi)部令牌-提供者的映射表,當(dāng)請(qǐng)求一個(gè)依賴項(xiàng)時(shí)就會(huì)引用它,令牌就是這個(gè)映射表的鍵。
提供者的類型很多,從官方文檔中可以閱讀它們的具體定義:
export type Provider = | TypeProvider | ValueProvider | ClassProvider | ConstructorProvider | ExistingProvider | FactoryProvider | any[];
提供者的解析過(guò)程如下:
function resolveReflectiveFactory( provider: NormalizedProvider ): ResolvedReflectiveFactory { let factoryFn: Function; let resolvedDeps: ReflectiveDependency[]; if (provider.useClass) { // 使用類來(lái)提供依賴 const useClass = resolveForwardRef(provider.useClass); factoryFn = reflector.factory(useClass); resolvedDeps = _dependenciesFor(useClass); } else if (provider.useExisting) { // 使用已有依賴 factoryFn = (aliasInstance: any) => aliasInstance; // 從根據(jù) token 獲取具體的依賴 resolvedDeps = [ ReflectiveDependency.fromKey(ReflectiveKey.get(provider.useExisting)), ]; } else if (provider.useFactory) { // 使用工廠方法提供依賴 factoryFn = provider.useFactory; resolvedDeps = constructDependencies(provider.useFactory, provider.deps); } else { // 使用提供者具體的值作為依賴 factoryFn = () => provider.useValue; resolvedDeps = _EMPTY_LIST; } // return new ResolvedReflectiveFactory(factoryFn, resolvedDeps); }
根據(jù)不同類型的提供者,通過(guò)解析之后,得到由注入器 Injector 使用的提供者的內(nèi)部解析表示形式:
export interface ResolvedReflectiveProvider { // 鍵,包括系統(tǒng)范圍內(nèi)的唯一 id,以及一個(gè) token key: ReflectiveKey; // 可以返回由鍵表示的對(duì)象的實(shí)例的工廠函數(shù) resolvedFactories: ResolvedReflectiveFactory[]; // 指示提供者是多提供者,還是常規(guī)提供者 multiProvider: boolean; }
提供者可以是服務(wù)類ClassProvider
本身,如果把服務(wù)類指定為提供者令牌,那么注入器的默認(rèn)行為是用new
來(lái)實(shí)例化那個(gè)類。
Angular 中的依賴注入服務(wù)
在 Angular 中,服務(wù)就是一個(gè)帶有@Injectable
裝飾器的類,它封裝了可以在應(yīng)用程序中復(fù)用的非 UI 邏輯和代碼。Angular 把組件和服務(wù)分開(kāi),是為了增進(jìn)模塊化程度和可復(fù)用性。
用@Injectable
標(biāo)記一個(gè)類,以確保編譯器將在注入類時(shí)生成必要的元數(shù)據(jù)(元數(shù)據(jù)在 Angular 中也是很重要的一部分),以創(chuàng)建類的依賴項(xiàng)。
@Injectable
裝飾器的類會(huì)在編譯之后,得到 Angular 可注入對(duì)象:
// 根據(jù)其 Injectable 元數(shù)據(jù),編譯 Angular 可注入對(duì)象,并對(duì)結(jié)果進(jìn)行修補(bǔ) export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void { // 該編譯過(guò)程依賴 @angular/compiler // 可參考編譯器中的 compileFactoryFunction compileInjectable 實(shí)現(xiàn) }
Angular 中可注入對(duì)象(InjectableDef
)定義 DI 系統(tǒng)將如何構(gòu)造 token 令牌,以及在哪些注入器(如果有)中可用:
export interface ??InjectableDef<T> { // 指定給定類型屬于特定注入器,包括 root/platform/any/null 以及特定的 NgModule providedIn: InjectorType<any> | "root" | "platform" | "any" | null; // 此定義所屬的令牌 token: unknown; // 要執(zhí)行以創(chuàng)建可注入實(shí)例的工廠方法 factory: (t?: Type<any>) => T; // 在沒(méi)有顯式注入器的情況下,存儲(chǔ)可注入實(shí)例的位置 value: T | undefined; }
使用@Injectable()
的providedIn
時(shí),優(yōu)化工具可以進(jìn)行 Tree-shaking 優(yōu)化,從而刪除應(yīng)用程序中未使用的服務(wù),以減小捆綁包尺寸。
總結(jié)
本文簡(jiǎn)單介紹了在 Angular 依賴注入體系中比較關(guān)鍵的幾個(gè)概念,主要包括Injector
、Provider
和Injectable
。
對(duì)于注入器、提供者和可注入服務(wù),我們可以簡(jiǎn)單地這樣理解:
-
注入器用于創(chuàng)建依賴,會(huì)維護(hù)一個(gè)容器來(lái)管理這些依賴,并盡可能地復(fù)用它們。
-
一個(gè)注入器中的依賴服務(wù),只有一個(gè)實(shí)例。
-
注入器需要使用提供者來(lái)管理依賴,并通過(guò) token(DI 令牌)來(lái)進(jìn)行關(guān)聯(lián)。
-
提供者用于高速注入器應(yīng)該如何獲取或創(chuàng)建依賴。
-
可注入服務(wù)類會(huì)根據(jù)元數(shù)據(jù)編譯后,得到可注入對(duì)象,該對(duì)象可用于創(chuàng)建實(shí)例。