Angular 2 Forward Reference
Angular 2 通过引入 forwardRef
让我们可以在使用构造注入时,使用尚未定义的依赖对象类型。下面我们先看一下如果没有使用 forwardRef
,在开发中可能会遇到的问题:
@Injectable()classSocket{constructor(privatebuffer:Buffer){}}console.log(Buffer);//undefined@Injectable()classBuffer{constructor(@Inject(BUFFER_SIZE)privatesize:Number){}}console.log(Buffer);//[Function:Buffer]
若运行上面的例子,将会抛出以下异常:
Error:CannotresolveallparametersforSocket(undefined).Makesuretheyallhavevalidtypeorannotations
为什么会出现这个问题 ?在探究产生问题的具体原因时,我们要先明白一点。不管我们是使用开发语言是 ES6、ES7 还是 TypeScript,最终我们都得转换成 ES5
的代码。然而在 ES5
中是没有 Class
,只有 Function
对象。这样一来,我们的解决问题的思路就是先看一下 Socket
类转换后的 ES5
代码:
varBuffer=(function(){functionBuffer(size){this.size=size;}returnBuffer;}());
我们发现 Buffer
类最终转成 ES5
中的函数表达式。我们也知道,JavaScript VM 在执行 JS 代码时,会有两个步骤,首先会先进行编译,然后才开始执行。编译阶段,变量声明和函数声明会自动提升,而函数表达式不会自动提升。了解完这些后,问题原因一下子明朗了。
那么要解决上面的问题,最简单的处理方式是交换类定义的顺序。除此之外,我们还可以使用 Angular2 提供的 forward reference 特性来解决问题,具体如下:
import{forwardRef}from'@angular2/core';@Injectable()classSocket{constructor(@Inject(forwardRef(()=>Buffer))privatebuffer){}}@Injectable()classBuffer{constructor(@Inject(BUFFER_SIZE)privatesize:Number){}}
问题来了,出现上面的问题,我交互个顺序不就完了,为什么还要如此大费周章 ?话虽如此,但这样增加了开发者的负担,要时刻警惕类定义的顺序,特别当一个 ts
文件内包含多个内部类的时候。所以更好地方式还是通过 forwardRef
来解决问题,下面我们就来进一步揭开 forwardRef
的神秘面纱。
forwardRef 原理分析
//@angular/core/src/di/forward_ref.ts/***Allowstorefertoreferenceswhicharenotyetdefined.*/exportfunctionforwardRef(forwardRefFn:ForwardRefFn):Type<any>{//forwardRefFn:()=>Buffer(<any>forwardRefFn).__forward_ref__=forwardRef;(<any>forwardRefFn).toString=function(){returnstringify(this());};return(<Type<any>><any>forwardRefFn);}/***LazilyretrievesthereferencevaluefromaforwardRef.*/exportfunctionresolveForwardRef(type:any):any{if(typeoftype==='function'&&type.hasOwnProperty('__forward_ref__')&&type.__forward_ref__===forwardRef){return(<ForwardRefFn>type)();//CallforwardRefFngetBuffer}else{returntype;}}
通过源码可以看出,当调用 forwardRef
方法时,我们只是在 forwardRefFn
函数对象上,增加了一个私有属性__forward_ref__
,同时覆写了函数对象的 toString
方法。在上面代码中,我们还发现了resolveForwardRef
函数,通过函数名和注释信息,我们很清楚地了解到,该函数是用来解析通过 forwardRef
包装过的引用值。
那么 resolveForwardRef
这个函数是由谁负责调用,又是什么时候调用呢 ?其实 resolveForwardRef
这个函数由 Angular 2 的依赖注入系统调用,当解析 Provider
和创建依赖对象的时候,会自动调用该函数。
//@angular/core/src/di/reflective_provider.ts/***解析Provider*/functionresolveReflectiveFactory(provider:NormalizedProvider):ResolvedReflectiveFactory{letfactoryFn:Function;letresolvedDeps:ReflectiveDependency[];...if(provider.useClass){constuseClass=resolveForwardRef(provider.useClass);factoryFn=reflector.factory(useClass);resolvedDeps=_dependenciesFor(useClass);}}/*************************************************************************//***构造依赖对象*/exportfunctionconstructDependencies(typeOrFunc:any,dependencies:any[]):ReflectiveDependency[]{if(!dependencies){return_dependenciesFor(typeOrFunc);}else{constparams:any[][]=dependencies.map(t=>[t]);returndependencies.map(t=>_extractToken(typeOrFunc,t,params));}}/***抽取Token*/function_extractToken(typeOrFunc:any,metadata:any[]|any,params:any[][]):ReflectiveDependency{token=resolveForwardRef(token);if(token!=null){return_createDependency(token,optional,visibility);}else{thrownoAnnotationError(typeOrFunc,params);}}
我有话说
1.为什么 JavaScript 解释器不自动提升 class ?
因为当 class 使用 extends 关键字实现继承的时候,我们不能确保所继承父类的有效性,那么就可能导致一些无法预知的行为。
classDogextendsAnimal{}functionAnimal{this.move=function(){alert(defaultMove);}}letdefaultMove="moving";letdog=newDog();dog.move();
以上代码能够正常的输出 moving
,因为 JavaScript 解释器把会把代码转化为:
letdefaultMove,dog;functionAnimal{this.move=function(){alert(defaultMove);}}classDogextendsAnimal{}defaultMove="moving";dog=newDog();dog.move();
然而,当我们把 Animal 转化为函数表达式,而不是函数声明的时候:
classDogextendsAnimal{}letAnimal=function(){this.move=function(){alert(defaultMove);}}letdefaultMove="moving";letdog=newDog();dog.move();
此时以上代码将会转化为:
letAnimal,defaultMove,dog;classDogextendsAnimal{}Animal=function(){this.move=function(){alert(defaultMove);}}defaultMove="moving";dog=newDog();dog.move();
当 class Dog extends Animal
被解释执行的时候,此时 Animal
的值是 undefined,这样就会抛出异常。我们可以简单地通过调整 Animal 函数表达式的位置,来解决上述问题。
letAnimal=function(){this.move=function(){alert(defaultMove);}}classDogextendsAnimal{}letdefaultMove="moving";letdog=newDog();dog.move();
假设 class
也会自动提升的话,上面的代码将被转化为以下代码:
letAnimal,defaultMove,dog;classDogextendsAnimal{}Animal=function(){this.move=function(){alert(defaultMove);}}defaultMove="moving";dog=newDog();dog.move();
此时 Dog 被提升了,当解释器执行 extends Animal
语句的时候,此时的 Animal 仍然是 undefined,同样会抛出异常。所以 ES6 中的 Class
不会自动提升,主要还是为了解决继承父类时,父类不可用的问题。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。