共计 4040 个字符,预计需要花费 11 分钟才能阅读完成。
依赖注入是 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 存储这些信息。
本文首发于集体公众号【朱玉洁的博客】,后续将会继续分享前端相干技术文章,欢送关注。