依赖注入是 Angular 的一大个性,通过它你可能写出更加易于保护的代码。但 JavaScript 语言自身并没有提供依赖注入的性能,那么 Angular 是如何实现依赖注入性能的呢?浏览本文,你就可能找到答案了。
一个典型的 Angular 应用程序,从开发者编写的源代码到在浏览器中运行,次要有 2 个关键步骤:
- 模板编译,即通过运行
ng build
等构建命令调用编译器编译咱们编写的代码。 - 运行时运行,模板编译的产物借助运行时代码在浏览器中运行。
首先咱们来编写一个简略的 Angular 利用,AppComponent 组件有个依赖项 HeroService:
import { Component } from '@angular/core';import { Injectable } from '@angular/core';@Injectable({ providedIn: 'root'})export class HeroService { name = 'hero service'; constructor() { }}@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']})export class AppComponent { title: string; constructor(heroService: HeroService) { this.title = heroService.name; }}
下面的代码通过 Angular 编译器编译打包后的产物大略是这样:
由图可知编译的产物次要分为 HeroService 和 AppComponent 两个局部,因为本文次要是解说依赖注入的实现原理,所以对于编译产物的其余局部不做开展解说。当初咱们来重点关注一下依赖注入相干的代码,其中箭头所示代码:
AppComponent.fac = function AppComponent_Factory(t) { return new (t || AppComponent)(i0.directiveInject(HeroService));};
AppComponent_Factory 函数负责创立 AppComponent,不言而喻依赖项 HeroService 是通过 i0.directiveInject(HeroService) 创立的。 咱们持续来看 i0.directiveInject 函数做了什么。
function directiveInject(token, flags = InjectFlags.Default) { // 省略无关代码 ...... return getOrCreateInjectable(tNode, lView, resolveForwardRef(token), flags);}
这里咱们间接定位到 getOrCreateInjectable 这个函数即可。在持续剖析这个函数之前,咱们来先看看 lView 这个参数。在 Angular 外部,LView
和 [TView.data](http://TView.data)
是两个很重要的视图数据,Ivy(即 Angular 编译和渲染管道)根据这些外部数据进行模板渲染。LView 被设计成一个单个的数组,通过这个数组蕴含了模板渲染须要的所有数据。TView.data
的数据能够被所有的模板实例共享。
当初咱们回到 getOrCreateInjectable 这个函数:
function getOrCreateInjectable(tNode, lView, token, xxxxx) { // 省略无关代码 ...... return lookupTokenUsingModuleInjector(lView, token, flags, notFoundValue);}
返回的是函数 lookupTokenUsingModuleInjector 执行的后果,通过名字大抵能够理解到是通过模块注入器来查找对应的 Token:
function lookupTokenUsingModuleInjector(lView, token, flags, notFoundValue) { // 省略无关代码 ...... return moduleInjector.get(token, notFoundValue, flags & InjectFlags.Optional);}
moduleInjector.get 办法最终是由 R3Injector 去执行查找:
this._r3Injector.get(token, notFoundValue, injectFlags);}
这里咱们又引入了一个新的名词:R3Injector。R3Injector 和 NodeInjector 是 Angular 中两种不同类型的注入器。前者是模块层级的注入器,后者则是组件层级的。咱们持续看 R3Injector 的 get 办法做了什么吧:
get(token, notFoundValue = THROW_IF_NOT_FOUND, flags = InjectFlags.Default) { // 省略无关代码 ...... let record = this.records.get(token); if (record === undefined) { const def = couldBeInjectableType(token) && getInjectableDef(token); if (def && this.injectableDefInScope(def)) { record = makeRecord(injectableDefOrInjectorDefFactory(token), NOT_YET); } else { record = null; } this.records.set(token, record); } // If a record was found, get the instance for it and return it. if (record != null /* NOT null || undefined */) { return this.hydrate(token, record);}
通过上述代码,咱们大略能够理解到 R3Injector 的 get 办法的大抵流程。this.records 是一个 Map 汇合,key 是 token, value 则是 token对应的实例。如果在 Map 汇合中没有找到对应的实例,就创立一条记录。get 办法返回的 this.hydrate 函数执行的后果,这个函数最终执行的是本文结尾模板编译产物中 HeroService.fac 函数:
HeroService.fac = function HeroService_Factory(t) { return new (t || HeroService)(); };
至此 Angular 依赖注入的流程就剖析完了。本文剖析的代码示例应用的是模块注入器,那么组件级别的注入器背地的实现流程是怎么的呢?要应用组件级别的注入器,咱们须要在@Component
装璜器中显式申明 provider:
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [{ provide: HeroService, useClass: HeroService }]})
和模块注入器雷同的流程就不再赘述,在 getOrCreateInjectable 函数中组件注入器要害函数如下:
function getOrCreateInjectable(tNode, lView, token, xxx) { // 省略无关代码 ...... const instance = searchTokensOnInjector(injectorIndex, lView, token, xxxx); if (instance !== NOT_FOUND) { return instance; }}
instance 是由函数 searchTokensOnInjector 创立的:
function searchTokensOnInjector(injectorIndex, lView, token, xxxx) { // 省略无关代码 ...... return getNodeInjectable(lView, currentTView, injectableIdx, tNode);}
最终 getNodeInjectable 函数解释了最终后果:
export function getNodeInjectable( lView: LView, tView: TView, index: number, tNode: TDirectiveHostNode): any { let value = lView[index]; const tData = tView.data; // ........ if (isFactory(value)) { const factory: NodeInjectorFactory = value; try { value = lView[index] = factory.factory(undefined, tData, lView, tNode); // ........... return value}
也就是说最开始咱们剖析的 i0.directiveInject(HeroService) 函数创立的值,就是下面代码中的value。value 是由 factory.factory() 函数创立的,而 factory 函数仍然是本文结尾模板编译产物中 HeroService.fac 函数。能够看到 R3Injector 和 NodeInjector 的区别在于,一个是通过 this.records 存储依赖注入的实例,而 NodeInjector 则是通过 LView 存储这些信息。
本文首发于集体公众号【朱玉洁的博客】,后续将会继续分享前端相干技术文章,欢送关注。