@H_
403_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]
@H_
403_0@若运行上面的例子,将会抛出以下异常:
Error: Cannot resolve all parameters for Socket(undefined).
Make sure they all have valid type or annotations
@H_
403_0@为什么会出现这个问题 ?在探究产生问题的具体原因时,我们要先明白一点。不管我们是使用开发语言是 ES6、ES7 还是 TypeScript,最终我们都得转换成 ES5 的
代码。然而在 ES5 中是没有 Class ,只有 Function 对象。这样一来,我们的
解决问题的思路就是先看一下 Socket 类转换后的 ES5
代码:
var Buffer = (function () {
function Buffer(size) {
this.size = size;
}
return Buffer;
}());
@H_
403_0@我们发现 Buffer 类最终转成 ES5 中的
函数表达式。我们也知道,JavaScript VM 在执行 JS
代码时,会有两个步骤,首先会先进行编译,然后才开始执行。编译阶段,变量声明和
函数声明会
自动提升,而
函数表达式不会
自动提升。了解完这些后,问题原因一下子明朗了。
@H_
403_0@那么要
解决上面的问题,最简单的处理方式是交换类定义的顺序。除此之外,我们还可以使用 Angular2 提供的 forward reference 特性来
解决问题,具体如下:
import { forwardRef } from'@angular2/core';
@Injectable()
class Socket {
constructor(@Inject(forwardRef(() => Buffer))
private buffer) { }
}
class Buffer {
constructor(@Inject(BUFFER_SIZE) private size: Number) { }
}
@H_
403_0@问题来了,出现上面的问题,我交互个顺序不就完了,为什么还要如此大费周章 ?话虽如此,但这样
增加了开发者的负担,要时刻警惕类定义的顺序,特别当一个 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<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;
}
}
@H_
403_0@通过源码可以看出,当
调用 forwardRef
方法时,我们只是在 forwardRefFn
函数对象上,
增加了一个私有
属性__forward_ref__,同时覆写了
函数对象的 toString
方法。在上面
代码中,我们还发现了resolveForwardRef
函数,通过
函数名和注释信息,我们很清楚地了解到,该
函数是用来解析通过 forwardRef 包装过的引用值。
@H_
403_0@那么 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);
}
}
我有话说
@H_
403_0@1.为什么 JavaScript 解释器不
自动提升 class ?
@H_
403_0@因为当 class 使用 extends 关键字实现继承的时候,我们不能确保所继承
父类的有效性,那么就可能导致一些无法预知的行为。
class Dog extends Animal {}
function Animal {
this.move = function() {
alert(defaultMove);
}
}
let defaultMove = "moving";
let dog = new Dog();
dog.move();
@H_
403_0@以上
代码能够正常的
输出 moving,因为 JavaScript 解释器把会把
代码转化为:
let defaultMove,dog;
function Animal {
this.move = function() {
alert(defaultMove);
}
}
class Dog extends Animal { }
defaultMove = "moving";
dog = new Dog();
dog.move();
@H_
403_0@然而,当我们把 Animal 转化为
函数表达式,而不是
函数声明的时候:
class Dog extends Animal {}
let Animal = function () {
this.move = function () {
alert(defaultMove);
}
}
let defaultMove = "moving";
let dog = new Dog();
dog.move();
@H_
403_0@此时以上
代码将会转化为:
let Animal,defaultMove,dog;
class Dog extends Animal { }
Animal = function () {
this.move = function () {
alert(defaultMove);
}
}
defaultMove = "moving";
dog = new Dog();
dog.move();
@H_
403_0@当 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();
@H_
403_0@假设 class 也会
自动提升的话,上面的
代码将被转化为以下
代码:
let Animal,dog;
class Dog extends Animal{ }
Animal = function () {
this.move = function () {
alert(defaultMove);
}
}
defaultMove = "moving";
dog = new Dog();
dog.move();
@H_
403_0@此时 Dog 被提升了,当解释器执行 extends Animal 语句的时候,此时的 Animal 仍然是 undefined,同样会抛出异常。所以 ES6 中的 Class 不会
自动提升,主要还是为了
解决继承
父类时,
父类不可用的问题。