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 不會自動提高,主要仍是爲了解決繼承父類時,父類不可用的問題。

相關文章
相關標籤/搜索