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 不会自动提升,主要还是为了解决继承父类时,父类不可用的问题。