依赖注入是 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 存储这些信息。
本文首发于个人公众号【朱玉洁的博客】,后续将会持续分享前端相关技术文章,欢迎关注。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。