Angular 2 Forward Reference
admin
2023-07-31 13:03:41
0次

Angular 2 通过引入 forwardRef 让我们可以在使用构造注入时,使用尚未定义的依赖对象类型。下面我们先看一下如果没有使用 forwardRef ,在开发中可能会遇到的问题:

@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]

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

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

为什么会出现这个问题 ?在探究产生问题的具体原因时,我们要先明白一点。不管我们是使用开发语言是 ES6、ES7 还是 TypeScript,最终我们都得转换成 ES5 的代码。然而在 ES5 中是没有 Class ,只有 Function 对象。这样一来,我们的解决问题的思路就是先看一下 Socket 类转换后的 ES5 代码:

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

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

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

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

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

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

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

forwardRef 原理分析

// @angular/core/src/di/forward_ref.ts
/** * Allows to refer to references which are not yet defined. */

export function forwardRef(forwardRefFn: ForwardRefFn): Type {  
    // forwardRefFn: () => Buffer  
    (forwardRefFn).__forward_ref__ = forwardRef; 
    (forwardRefFn).toString = function() { 
        return stringify(this()); 
    };  
    return (>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 (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 ?

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

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

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...