关于前端:Angular-依赖注入机制实现原理的深入介绍

1次阅读

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

import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {Routes, RouterModule} from '@angular/router';
import {HttpModule} from '@angular/http';

import {AppComponent} from './app.component';

export const ROUTER_CONFIG: Routes = [{ path: '', loadChildren:'./home/home.module#HomeModule'},
  {path: 'about', loadChildren: './about/about.module#AboutModule'},
  {path: 'contact', loadChildren: './contact/contact.module#ContactModule'},
];

@NgModule({imports: [BrowserModule, HttpModule, RouterModule.forRoot(ROUTER_CONFIG)],
  bootstrap: [AppComponent],
  declarations: [AppComponent],
})
export class AppModule {}

下面的实例代码,定义了一个根组件和一些路由规定,通过这些规定,路由到不同的模块。

Angular 将生成对 VM(虚拟机)敌对的代码,以尽可能进步其高性能。

Angular 将为咱们的每个模块 (module) 生成一个注入器 Injector,因而在咱们的例子中,它将采纳 AppModule(咱们的装璜类)并创立一个名为 AppModuleInjector 的注入器。

上面是编译器生成的 AppModule Injector 代码:

import {NgModuleInjector} from '@angular/core/src/linker/ng_module_factory';
import {CommonModule} from '@angular/common/src/common_module';
import {ApplicationModule, _localeFactory} from '@angular/core/src/application_module';
import {BrowserModule, errorHandler} from '@angular/platform-browser/src/browser';
import {RouterModule, ROUTER_FORROOT_GUARD} from '@angular/router/src/router_module';
import {NgLocaleLocalization, NgLocalization} from '@angular/common/src/localization';
import {ApplicationInitStatus, APP_INITIALIZER} from '@angular/core/src/application_init';
import {Testability, TestabilityRegistry} from '@angular/core/src/testability/testability';
import {HttpModule} from '@angular/http/src/http_module';
import {ApplicationRef, ApplicationRef_} from '@angular/core/src/application_ref';
import {BrowserModule} from '@angular/platform-browser/src/browser';
import {Injector} from '@angular/core/src/di/injector';
import {LOCALE_ID} from '@angular/core/src/i18n/tokens';
import {RouterModule, provideForRootGuard} from '@angular/router/src/router_module';
import {Router} from '@angular/router/src/router';
import {NgZone} from '@angular/core/src/zone/ng_zone';
import {Console} from '@angular/core/src/console';
import {ROUTES} from '@angular/router/src/router_config_loader';
import {ErrorHandler} from '@angular/core/src/error_handler';

import {AppModule} from './app.module';
import {AppComponentNgFactory} from './app.component.ngfactory';

class AppModuleInjector extends NgModuleInjector<AppModule> {
  _CommonModule_0: CommonModule;
  _ApplicationModule_1: ApplicationModule;
  _BrowserModule_2: BrowserModule;
  _ROUTER_FORROOT_GUARD_3: any;
  _RouterModule_4: RouterModule;
  _HttpModule_5: HttpModule;
  _AppModule_6: AppModule;
  _ErrorHandler_7: any;
  _ApplicationInitStatus_8: ApplicationInitStatus;
  _Testability_9: Testability;
  _ApplicationRef__10: ApplicationRef_;
  __ApplicationRef_11: any;
  __ROUTES_12: any[];

  constructor(parent: Injector) {super(parent, [AppComponentNgFactory], [AppComponentNgFactory]);  
  }

  get _ApplicationRef_11(): any {if (this.__ApplicationRef_11 == null) {this.__ApplicationRef_11 = this._ApplicationRef__10;}
    return this.__ApplicationRef_11;
  }

  get _ROUTES_12(): any[] {if (this.__ROUTES_12 == null) {
      this.__ROUTES_12 = [[
        {path: '', loadChildren:'./home/home.module#HomeModule'},
        {path: 'about', loadChildren: './about/about.module#AboutModule'},
        {path: 'contact', loadChildren: './contact/contact.module#ContactModule'}
      ]];
    }
    return this.__ROUTES_12;
  }

  createInternal(): AppModule {this._CommonModule_0 = new CommonModule();
    this._ApplicationModule_1 = new ApplicationModule();
    this._BrowserModule_2 = new BrowserModule(this.parent.get(BrowserModule, (null as any)));
    this._ROUTER_FORROOT_GUARD_3 = provideForRootGuard(this.parent.get(Router, (null as any)));
    this._RouterModule_4 = new RouterModule(this._ROUTER_FORROOT_GUARD_3);
    this._HttpModule_5 = new HttpModule();
    this._AppModule_6 = new AppModule();
    this._ErrorHandler_7 = errorHandler();
    this._ApplicationInitStatus_8 = new ApplicationInitStatus(this.parent.get(APP_INITIALIZER, (null as any)));
    this._Testability_9 = new Testability(this.parent.get(NgZone));

    this._ApplicationRef__10 = new ApplicationRef_(this.parent.get(NgZone),
      this.parent.get(Console),
      this,
      this._ErrorHandler_7,
      this,
      this._ApplicationInitStatus_8,
      this.parent.get(TestabilityRegistry, (null as any)),
      this._Testability_9
    );
    return this._AppModule_6;
  }

  getInternal(token: any, notFoundResult: any): any {if (token === CommonModule) {return this._CommonModule_0;}
    if (token === ApplicationModule) {return this._ApplicationModule_1;}
    if (token === BrowserModule) {return this._BrowserModule_2;}
    if (token === ROUTER_FORROOT_GUARD) {return this._ROUTER_FORROOT_GUARD_3;}
    if (token === RouterModule) {return this._RouterModule_4;}
    if (token === HttpModule) {return this._HttpModule_5;}
    if (token === AppModule) {return this._AppModule_6;}
    if (token === ErrorHandler) {return this._ErrorHandler_7;}
    if (token === ApplicationInitStatus) {return this._ApplicationInitStatus_8;}
    if (token === Testability) {return this._Testability_9;}
    if (token === ApplicationRef_) {return this._ApplicationRef__10;}
    if (token === ApplicationRef) {return this._ApplicationRef_11;}
    if (token === ROUTES) {return this._ROUTES_12;}

    return notFoundResult;
  }

  destroyInternal(): void {this._ApplicationRef__10.ngOnDestroy();
  }
}

为了便于浏览,下面所有的 import 代码,曾经手动导入更改为命名导入。在理论生成的代码中,每个模块都应用通配符导入以防止命名抵触。

例如,HttpModule 会像这样被导入:

import * as import6 from '@angular/http/src/http_module';

而后应用 import6.HttpModule 而不是 HttpModule 来援用它。

咱们会从上述生成代码中学习三个知识点:类的属性、模块导入以及依赖注入机制的工作形式。

AppModuleInjector properties

在 AppModuleInjector 上为每个提供者 / 依赖项创立属性:

// ...
class AppModuleInjector extends NgModuleInjector<AppModule> {
  _CommonModule_0: CommonModule;
  _ApplicationModule_1: ApplicationModule;
  _BrowserModule_2: BrowserModule;
  // ...
}

这是下面编译输入的一个片段——所以咱们将关注定义在类上的三个属性:

  • CommonModule
  • ApplicationModule
  • BrowserModule

咱们的模块只申明了 BrowserModule,那么 CommonModule 和 ApplicationModule 是从哪里来的呢?这些实际上是由 BrowserModule 为咱们导出的,所以咱们不须要本人导入它们。

模块中每个属性的开端还附加了一个数字。就像应用通配符导入一样,这是为了防止提供者之间潜在的命名抵触。

咱们能够导入两个应用具备共享名称的服务的模块并且没有递增的数字,它们都将被调配给雷同的属性,这可能会导致进一步的谬误。

Module imports

编译时,Angular 应用它导入的每个提供程序的间接门路,例如,当咱们编写以下代码时:

import {CommonModule} from '@angular/common';

编译后的代码实际上为:

import * as import5 from '@angular/common/src/common_module';

因而,当代码被编译并捆绑在一起时,咱们能够利用 tree-shaking 并且只蕴含咱们理论应用的每个模块的局部。

Dependency Injection

每个模块解决本人的依赖注入,如果它没有依赖,则转到父模块,直到找到或未找到它,后者状况下咱们会失去一个谬误。

重要的是要留神,所有依赖项都应用令牌来惟一标识它们,无论是在注册时还是在查找时。

有两种不同的形式来启动咱们的依赖项,要么在 createInternal 中,要么作为属性的 getter。

例如,咱们应用 BrowserModule 和 HttpModule,它们是在这里创立的:

class AppModuleInjector extends NgModuleInjector<AppModule> {
  _CommonModule_0: CommonModule;
  _ApplicationModule_1: ApplicationModule;
  _BrowserModule_2: BrowserModule;
  _HttpModule_5: HttpModule;
  _AppModule_6: AppModule;

  createInternal(): AppModule {this._CommonModule_0 = new CommonModule();
    this._ApplicationModule_1 = new ApplicationModule();
    this._BrowserModule_2 = new BrowserModule(this.parent.get(BrowserModule, (null as any)));
    this._HttpModule_5 = new HttpModule();
    this._AppModule_6 = new AppModule();
    // ...
    return this._AppModule_6;
  }
}

您能够看到 BrowserModule 的两个导出 – CommonModule 和 ApplicationModule 已创立,以及咱们其余导入的模块。咱们的理论模块也被创立(AppModule),因而它能够被其余模块应用。

对于所有其余提供者,它们是在须要时通过类中的 getter 创立的。这是为了防止在不须要时创立提供程序的实例,同时进步初始渲染性能。

每当咱们谈到 Angular 中的注入器时,它指的是从咱们的模块中生成(编译)的代码。

当 Angular 查找依赖项(例如咱们通过构造函数注入的依赖项)时,它会在模块注入器中查找,如果找不到则向上遍历父模块。如果它不存在,将会抛出一个谬误。

当咱们在构造函数中应用类型定义时,Angular 应用这些类型(即类)作为查找依赖项的标记。而后将该令牌传递给 getInternal 并返回依赖项的实例(如果存在)。再次通过源代码来学习:

class AppModuleInjector extends NgModuleInjector<AppModule> {// new BrowserModule(this.parent.get(BrowserModule, (null as any)));
  _BrowserModule_2: BrowserModule;

  // new HttpModule()
  _HttpModule_5: HttpModule;

  // new AppModule()
  _AppModule_6: AppModule;

  getInternal(token: any, notFoundResult: any): any {if (token === BrowserModule) {return this._BrowserModule_2;}
    if (token === HttpModule) {return this._HttpModule_5;}
    if (token === AppModule) {return this._AppModule_6;}

    return notFoundResult;
  }
}

因而,在 getInternal 办法中,能够看到 Angular 正在应用简略的 if 语句查看咱们的令牌,并将返回提供者的相干属性 – 如果找到的话。

否则,咱们将应用返回的 notFoundResult 来援救 getInternal 办法。当 Angular 遍历咱们的模块以找到所需的依赖项时,这个 notFoundResult 将为空 – 直到它找到依赖项,或者达到根模块但依然找不到它,此时将会抛出一个谬误。

正文完
 0