关于angular:Angular-依赖注入原理

44次阅读

共计 4040 个字符,预计需要花费 11 分钟才能阅读完成。

依赖注入是 Angular 的一大个性,通过它你可能写出更加易于保护的代码。但 JavaScript 语言自身并没有提供依赖注入的性能,那么 Angular 是如何实现依赖注入性能的呢?浏览本文,你就可能找到答案了。

一个典型的 Angular 应用程序,从开发者编写的源代码到在浏览器中运行,次要有 2 个关键步骤:

  1. 模板编译,即通过运行 ng build 等构建命令调用编译器编译咱们编写的代码。
  2. 运行时运行,模板编译的产物借助运行时代码在浏览器中运行。

首先咱们来编写一个简略的 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 存储这些信息。

本文首发于集体公众号【朱玉洁的博客】,后续将会继续分享前端相干技术文章,欢送关注。

正文完
 0