深入介绍Angular的依赖注入机制的实现原理';import{HttpModule}from'@angular/http';import{AppComponent}from'./app.component';exportconstROUTER_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],})导出类AppModule{}上面的示例代码定义了一个根组件和一些路由规则,通过这些规则,路由到不同的模块。Angular会生成VM(虚拟机)友好的代码以最大化其性能。Angular将为我们的每个模块生成一个注入器,因此在我们的例子中它将使用AppModule(我们的装饰器类)并创建一个名为AppModuleInjector的注入器。下面是编译器生成的AppModuleInjector代码:import{NgModuleInjector}from'@angular/core/src/linker/ng_module_factory';import{CommonModule}from'@angular/common/src/common_module';import{ApplicationModule,_localeFactory}从'@angular/core/src/application_module';从'@angular/platform-b??rowser/src/browser'导入{BrowserModule,errorHandler};从'@angular/router/src/router_module'导入{RouterModule,ROUTER_FORROOT_GUARD};从'@angular/common/src/localization'导入{NgLocaleLocalization,NgLocalization};从'@angular/core/src/application_init'导入{ApplicationInitStatus,APP_INITIALIZER};从'@angular/core/src导入{Testability,TestabilityRegistry}/testability/testability';从'@angular/http/src/http_module'导入{HttpModule};从'@angular/core/src/application_ref'导入{ApplicationRef,ApplicationRef_};从'@angular/platform导入{BrowserModule}-browser/src/browser';从'@angular/core导入{注入器}/src/di/injector';从'@angular/core/src/i18n/tokens'导入{LOCALE_ID};从'@angular/router/src/router_module'导入{RouterModule,provideForRootGuard};从'导入{路由器}@angular/router/src/router';从'@angular/core/src/zone/ng_zone'导入{NgZone};从'@angular/core/src/console'导入{控制台};从'导入{ROUTES}@angular/router/src/router_config_loader';从'@angular/core/src/error_handler'导入{ErrorHandler};从'./app.module'导入{AppModule};从'./app.component导入{AppComponentNgFactory}.ngfactory';classAppModuleInjectorextendsNgModuleInjector{_CommonModule_0:CommonModule;_ApplicationModule_1:应用模块;_BrowserModule_2:浏览器模块;_ROUTER_FORROOT_GUARD_3:任何;_RouterModule_4:路由器模块;_HttpModule_5:HttpModule;_AppModule_6:应用模块;_ErrorHandler_7:任何;_ApplicationInitStatus_8:应用程序初始化状态;_Testability_9:可测试性;_申请icationRef__10:ApplicationRef_;__ApplicationRef_11:任何;__ROUTES_12:任何[];constructor(parent:Injector){super(parent,[AppComponentNgFactory],[AppComponentNgFactory]);}get_ApplicationRef_11():any{if(this.__ApplicationRef_11==null){this.__ApplicationRef_11=this._ApplicationRef__10;}返回这个.__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'},{路径:'contact',loadChildren:'./contact/contact.module#ContactModule'}]];}返回这个.__ROUTES_12;}createInternal():AppModule{this._CommonModule_0=newCommonModule();this._ApplicationModule_1=newApplicationModule();this._BrowserModule_2=newBrowserModule(this.parent.get(BrowserModule,(nullasany)));this._ROUTER_FORROOT_GUARD_3=provideForRootGuard(this.parent.get(Router,(nullasany)));this._RouterModule_4=newRouterModule(this._ROUTER_FORROOT_GUARD_3);this._HttpModule_5=newHttpModule();this._AppModule_6=newAppModule();this._ErrorHandler_7=errorHandler();this._ApplicationInitStatus_8=newApplicationInitStatus(this.parent.get(APP_INITIALIZER,(nullasany)));this._Testability_9=newTestability(this.parent.get(NgZone));this._ApplicationRef__10=newApplicationRef_(this.parent.get(NgZone),this.parent.get(Console),this,this._ErrorHandler_7,this,this._ApplicationInitStatus_8,this.parent.get(TestabilityRegistry,(nullasany)),这个._Testability_9);返回这个._AppModule_6;}getInternal(token:any,notFoundResult:any):any{if(token===CommonModule){returnthis._CommonModule_0;}如果(令牌===ApplicationModule){returnthis._ApplicationModule_1;}if(token===BrowserModule){returnthis._BrowserModule_2;}if(token===ROUTER_FORROOT_GUARD){returnthis._ROUTER_FORROOT_GUARD_3;}if(token===RouterModule){returnthis._RouterModule_4;}if(token===HttpModule){returnthis._HttpModule_5;}if(token===AppModule){returnthis._AppModule_6;}if(token===ErrorHandler){returnthis._ErrorHandler_7;}if(token===ApplicationInitStatus){returnthis._ApplicationInitStatus_8;}if(token===Testability){returnthis._Testability_9;}if(token===ApplicationRef_){returnthis._ApplicationRef__10;}if(token===ApplicationRef){returnthis._ApplicationRef_11;}if(token===ROUTES){returnthis._ROUTES_12;}返回notFoundResult;}destroyInternal():void{this._ApplicationRef__10.ngOnDestroy();}}为了便于阅读,以上所有导入代码都已从手动导入更改为命名导入。在实际生成的代码中,每个模块都使用通配符导入,以避免命名冲突。例如,HttpModule会这样导入:import*asimport6from'@angular/http/src/http_module';然后使用import6.HttpModule而不是HttpModule来引用它。我们将从上面生成的代码中学习三个知识点:类属性、模块导入以及依赖注入机制是如何工作的。AppModuleInjector属性在AppModuleInjector上为每个提供者/依赖项创建属性://...classAppModuleInjectorextendsNgModuleInjector{_CommonModule_0:CommonModule;_ApplicationModule_1:应用模块;_BrowserModule_2:浏览器模块;//...}这是上面编译输出的一个片段-所以我们将关注类上定义的三个属性:CommonModuleApplicationModuleBrowserModule我们的模块只声明了BrowserModule,那么CommonModule和ApplicationModule从何而来?这些实际上是由BrowserModule为我们导出的,所以我们不需要自己导入它们。模块中每个属性的末尾也附加了一个数字。与通配符导入一样,这是为了避免提供商之间潜在的命名冲突。我们可以导入两个使用具有共享名称且没有递增编号的服务的模块,它们将被分配给相同的属性,这可能会导致更多错误。当Module导入compile时,Angular使用它导入的每个提供者的直接路径,例如,当我们编写以下代码时:import{CommonModule}from'@angular/common';编译后的代码实际上是:import*asimport5from'@angular/common/src/common_module';因此,当代码被编译并捆绑在一起时,我们可以利用tree-shaking的优势,只包含我们实际使用的每个模块的部分。依赖注入每个模块处理自己的依赖注入,如果它没有依赖项,它会转到父模块,直到找到或找不到,在后一种情况下我们会得到一个错误。重要的是要注意,所有依赖项都使用令牌来唯一标识它们,无论是在注册时还是在查找时。有两种不同的方式来启动我们的依赖关系,要么在createInternal中,要么作为属性的getter。例如,我们使用BrowserModule和HttpModule,它们是在这里创建的:classAppModuleInjectorextendsNgModuleInjector{_CommonModule_0:CommonModule;_ApplicationModule_1:应用模块;_BrowserModule_2:浏览器模块;_HttpModule_5:HttpModule;_AppModule_6:应用模块;{这个)._CommonModule_0=newCommonModule();this._ApplicationModule_1=newApplicationModule();this._BrowserModule_2=newBrowserModule(this.parent.get(BrowserModule,(nullasany)));this._HttpModule_5=newHttpModule();这个._AppModule_6=newAppModule();//...returnthis._AppModule_6;您可以看到BrowserModule的两个导出-CommonModule和ApplicationModule已经创建,以及我们其他导入的模块。我们的实际模块也被创建(AppModule),所以它可以被其他模块使用。对于所有其他提供者,它们是在需要时通过类中的getter创建的。这是为了避免在不需要时创建提供者的实例,同时提高初始渲染性能。每当我们谈论Angular中的注入器时,它指的是从我们的模块生成(编译)的代码。当Angular查找依赖项(例如我们通过构造函数注入的依赖项)时,它会在模块注入器中查找,如果找不到,它会向上查找父模块。如果不存在,将抛出错误。当我们在构造函数中使用类型定义时,Angular使用这些类型(即类)作为标记来查找依赖项。然后将此令牌传递给getInternal并返回依赖项的实例(如果存在)。再次通过源码学习:classAppModuleInjectorextendsNgModuleInjector{//newBrowserModule(this.parent.get(BrowserModule,(nullasany)));_BrowserModule_2:浏览器模块;//新的HttpModule()_HttpModule_5:HttpModule;//新的AppModule()_AppModule_6:AppModule;getInternal(token:any,notFoundResult:any):any{if(token===BrowserModule){returnthis._BrowserModule_2;}if(token===HttpModule){返回这个。_HttpModule_5;}if(token===AppModule){returnthis._AppModule_6;}返回notFoundResult;因此,在getInternal方法中,您可以看到Angular正在使用一个简单的if语句检查我们的令牌,并将返回提供的作者的关联属性-如果找到的话。否则,我们将使用返回的notFoundResult来挽救getInternal方法。当Angular遍历我们的模块以找到所需的依赖项时,这个notFoundResult将为空-直到它找到依赖项,或者到达根模块但仍然找不到它,此时将抛出错误。