Angular 2 Forward Reference

Angular 2 经过引入 forwardRef 让咱们能够在使用构造注入时,使用还没有定义的依赖对象类型。下面咱们先看一下若是没有使用 forwardRef ,在开发中可能会遇到的问题:angular2

@Injectable()
class Socket {
  constructor(private buffer: Buffer) { }
}

console.log(Buffer); // undefined

@Injectable()
class Buffer {
  constructor(@Inject(BUFFER_SIZE) private size: Number) { }
}

console.log(Buffer); // [Function: Buffer]

若运行上面的例子,将会抛出如下异常:ide

Error: Cannot resolve all parameters for Socket(undefined).
Make sure they all have valid type or annotations

为何会出现这个问题 ?在探究产生问题的具体缘由时,咱们要先明白一点。无论咱们是使用开发语言是 ES六、ES7 仍是 TypeScript,最终咱们都得转换成 ES5 的代码。然而在 ES5 中是没有 Class ,只有 Function 对象。这样一来,咱们的解决问题的思路就是先看一下 Socket 类转换后的 ES5 代码:函数

var Buffer = (function () {
    function Buffer(size) {
        this.size = size;
    }
    return Buffer;
}());

咱们发现 Buffer 类最终转成 ES5 中的函数表达式。咱们也知道,JavaScript VM 在执行 JS 代码时,会有两个步骤,首先会先进行编译,而后才开始执行。编译阶段,变量声明和函数声明会自动提高,而函数表达式不会自动提高。了解完这些后,问题缘由一会儿明朗了。this

那么要解决上面的问题,最简单的处理方式是交换类定义的顺序。除此以外,咱们还可使用 Angular2 提供的 forward reference 特性来解决问题,具体以下:code

import { forwardRef } from'@angular2/core';

@Injectable()
class Socket {
  constructor(@Inject(forwardRef(() => Buffer)) 
      private buffer) { }
}

class Buffer {
  constructor(@Inject(BUFFER_SIZE) private size: Number) { }
}

问题来了,出现上面的问题,我交互个顺序不就完了,为何还要如此大费周章 ?话虽如此,但这样增长了开发者的负担,要时刻警戒类定义的顺序,特别当一个 ts 文件内包含多个内部类的时候。因此更好地方式仍是经过 forwardRef 来解决问题,下面咱们就来进一步揭开 forwardRef 的神秘面纱。orm

forwardRef 原理分析

// @angular/core/src/di/forward_ref.ts

/**
 * Allows to refer to references which are not yet defined.
 */
export function forwardRef(forwardRefFn: ForwardRefFn): Type<any> {
  // forwardRefFn: () => Buffer
  (<any>forwardRefFn).__forward_ref__ = forwardRef;
  (<any>forwardRefFn).toString = function() { return stringify(this()); };
  return (<Type<any>><any>forwardRefFn);
}

/**
 * Lazily retrieves the reference value from a forwardRef.
 */
export function resolveForwardRef(type: any): any {
  if (typeof type === 'function' && type.hasOwnProperty('__forward_ref__') &&
      type.__forward_ref__ === forwardRef) {
    return (<ForwardRefFn>type)(); // Call forwardRefFn get Buffer 
  } else {
    return type;
  }
}

经过源码能够看出,当调用 forwardRef 方法时,咱们只是在 forwardRefFn 函数对象上,增长了一个私有属性__forward_ref__,同时覆写了函数对象的 toString 方法。在上面代码中,咱们还发现了resolveForwardRef 函数,经过函数名和注释信息,咱们很清楚地了解到,该函数是用来解析经过 forwardRef 包装过的引用值。对象

那么 resolveForwardRef 这个函数是由谁负责调用,又是何时调用呢 ?其实 resolveForwardRef 这个函数由 Angular 2 的依赖注入系统调用,当解析 Provider 和建立依赖对象的时候,会自动调用该函数。继承

// @angular/core/src/di/reflective_provider.ts

/**
 * 解析Provider
 */
function resolveReflectiveFactory(provider: NormalizedProvider): ResolvedReflectiveFactory {
  let factoryFn: Function;
  let resolvedDeps: ReflectiveDependency[];
  ...
  if (provider.useClass) {
    const useClass = resolveForwardRef(provider.useClass);
    factoryFn = reflector.factory(useClass);
    resolvedDeps = _dependenciesFor(useClass);
  }
}

/***************************************************************************************/

/**
 * 构造依赖对象
 */
export function constructDependencies(
    typeOrFunc: any, dependencies: any[]): ReflectiveDependency[] {
  if (!dependencies) {
    return _dependenciesFor(typeOrFunc);
  } else {
    const params: any[][] = dependencies.map(t => [t]);
    return dependencies.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 {
    throw noAnnotationError(typeOrFunc, params);
  }
}

我有话说

1.为何 JavaScript 解释器不自动提高 class ?token

由于当 class 使用 extends 关键字实现继承的时候,咱们不能确保所继承父类的有效性,那么就可能致使一些没法预知的行为。ip

class Dog extends Animal {}

function Animal {
  this.move = function() {
    alert(defaultMove);
  }
}

let defaultMove = "moving";

let dog = new Dog();
dog.move();

以上代码可以正常的输出 moving,由于 JavaScript 解释器把会把代码转化为:

let defaultMove,dog;

function Animal {
  this.move = function() {
    alert(defaultMove);
  }
}

class Dog extends Animal { }

defaultMove = "moving";

dog = new Dog();
dog.move();

然而,当咱们把 Animal 转化为函数表达式,而不是函数声明的时候:

class Dog extends Animal {}

let Animal = function () {
  this.move = function () {
    alert(defaultMove);
  }
}

let defaultMove = "moving";

let dog = new Dog();
dog.move();

此时以上代码将会转化为:

let Animal, defaultMove, dog;

class Dog extends Animal { }

Animal = function () {
  this.move = function () {
    alert(defaultMove);
  }
}

defaultMove = "moving";

dog = new Dog();
dog.move();

当 class Dog extends Animal 被解释执行的时候,此时 Animal 的值是 undefined,这样就会抛出异常。咱们能够简单地经过调整 Animal 函数表达式的位置,来解决上述问题。

let Animal = function () {
  this.move = function () {
    alert(defaultMove);
  }
}

class Dog extends Animal{

}

let defaultMove = "moving";

let dog = new Dog();
dog.move();

假设 class 也会自动提高的话,上面的代码将被转化为如下代码:

let Animal, defaultMove, dog;

class Dog extends Animal{ }

Animal = function () {
  this.move = function () {
    alert(defaultMove);
  }
}

defaultMove = "moving";

dog = new Dog();
dog.move();

此时 Dog 被提高了,当解释器执行 extends Animal 语句的时候,此时的 Animal 仍然是 undefined,一样会抛出异常。因此 ES6 中的 Class 不会自动提高,主要仍是为了解决继承父类时,父类不可用的问题。