当前位置: 首页 > Web前端 > JavaScript

Angular依赖注入原理

时间:2023-03-26 20:10:48 JavaScript

依赖注入是Angular的一大特性,通过它你可以写出更易维护的代码。但是JavaScript语言本身并没有提供依赖注入功能,那么Angular是如何实现依赖注入功能的呢?阅读本文,您将能够找到答案。一个典型的Angular应用,从开发者编写的源代码到在浏览器中运行,主要有两个关键步骤:模板编译,即通过运行ngbuild等构建命令来调用编译器对我们编写的代码进行编译。运行时运行,模板编译的产物借助运行时代码在浏览器中运行。首先,让我们编写一个简单的Angular应用程序。AppComponent组件有一个依赖HeroService:import{Component}from'@angular/core';从'@angular/core'导入{Injectable};@Injectable({providedIn:'root'})exportclassHeroService{name='heroservice';constructor(){}}@Component({selector:'app-root',templateUrl:'./app.component.html',styleUrls:['./app.component.css']})exportclassAppComponent{title:细绳;构造函数(heroService:HeroService){this.title=heroService.name;}}上面Angular编译器编译打包的代码是这样的:从图中可以看出,编译后的产品主要分为两部分:HeroService和AppComponent。由于本文主要讲解依赖注入的实现原理,对于编译后的产品其他部分不做讲解。现在让我们关注依赖注入相关的代码,其中箭头所示的代码:AppComponent_Factory函数负责创建AppComponent,显然依赖HeroService是由i0.??directiveInject(HeroService)创建的。让我们继续看看i0.??directiveInject函数做了什么。function??directiveInject(token,flags=InjectFlags.Default){//省略无关代码......returngetOrCreateInjectable(tNode,lView,resolveForwardRef(token),flags);}这里我们直接定位getOrCreateInjectable函数即可。在继续分析这个函数之前,我们先看一下lView这个参数。在Angular内部,LView和[TView.data](http://TView.data)是两个非常重要的视图数据,Ivy(即Angular的编译渲染管线)就是根据这些内部数据进行模板渲染的。LView被设计为单个数组,其中包含模板渲染所需的所有数据。TView.data的数据可以被所有模板实例共享。现在让我们回到函数getOrCreateInjectable:functiongetOrCreateInjectable(tNode,lView,token,xxxxx){//省略不相关的代码...结果,从名字就可以大致理解为通过模块注入器找到对应的Token:&InjectFlags.Optional);}moduleInjector.get方法最终被R3Injector搜索到:this._r3Injector.get(token,notFoundValue,injectFlags);}这里引入一个新名词:R3Injector。R3Injector和NodeInjector是Angular中两种不同类型的注入器。前者是模块级注入器,后者是组件级注入器。我们继续看R3Injector的get方法做??了什么:if(record===undefined){constdef=couldBeInjectableType(token)&&getInjectableDef(token);如果(def&&this.injectableDefInScope(def)){record=makeRecord(injectableDefOrInjectorDefFactory(token),NOT_YET);}else{记录=null;}this.records.set(token,record);}//如果找到一条记录,获取它的实例并返回它。if(record!=null/*NOTnull||undefined*/){returnthis.hydrate(token,record);}通过上面的代码,我们大概可以理解R3Injector的get方法的大致流程。this.records是一个Map集合,key是token,value是token对应的实例。如果在Map集合中没有找到相应的实例,则创建一条记录。get方法返回的this.hydrate函数的执行结果。该函数最终执行了本文开头模板编译产品中的HeroService.?fac函数:HeroService.?fac=functionHeroService_Factory(t){returnnew(t||HeroService)();};至此,Angular的依赖注入流程就分析完了。本文分析的代码示例使用了模块注入器,那么组件级注入器背后的实现过程是怎样的呢?要使用组件级注入器,我们需要在@Component装饰器中显式声明提供者:@Component({selector:'app-root',templateUrl:'./app.component.html',styleUrls:['./app.component.css'],providers:[{provide:HeroService,useClass:HeroService}]})和模块注入器一样的流程这里不再赘述。getOrCreateInjectable函数中组件注入器的关键函数如下:如果(实例!==NOT_FOUND){返回实例;}}instanceisCreatedbythefunctionsearchTokensOnInjector:functionsearchTokensOnInjector(injectorIndex,lView,token,xxxx){//省略无关代码...returngetNodeInjectable(lView,currentTView,injectableIdx,tNode);}最后的getNodeInjectable函数解释了最后的结果:导出函数getNodeInjectable(lView:LView,tView:TView,index:number,tNode:TDirectiveHostNode):any{letvalue=lView[index];consttData=tView.data;//…..如果(isFactory(value)){常量工厂:NodeInjectorFactory=value;try{value=lView[index]=factory.factory(undefined,tData,lView,tNode);//.......returnvalue}也就是说我们一开始分析的i0.??directiveInject(HeroService)函数创建的value就是上面代码中的valuevalue是由factory.factory()函数,工厂函数还是本文开头模板编译产品中的HeroService。εfac函数。可以看出,R3Injector和NodeInjector的区别在于,一个是通过this.records来存储依赖注入的实例,而NodeInjector是通过LView来存储信息的。