您好,登錄后才能下訂單哦!
Angular 2 通過引入 forwardRef
讓我們可以在使用構(gòu)造注入時(shí),使用尚未定義的依賴對(duì)象類型。下面我們先看一下如果沒有使用 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]
若運(yùn)行上面的例子,將會(huì)拋出以下異常:
Error: Cannot resolve all parameters for Socket(undefined).Make sure they all have valid type or annotations
為什么會(huì)出現(xiàn)這個(gè)問題 ?在探究產(chǎn)生問題的具體原因時(shí),我們要先明白一點(diǎn)。不管我們是使用開發(fā)語言是 ES6、ES7 還是 TypeScript,最終我們都得轉(zhuǎn)換成 ES5
的代碼。然而在 ES5
中是沒有 Class
,只有 Function
對(duì)象。這樣一來,我們的解決問題的思路就是先看一下 Socket
類轉(zhuǎn)換后的 ES5
代碼:
var Buffer = (function () { function Buffer(size) { this.size = size; } return Buffer; }());
我們發(fā)現(xiàn) Buffer
類最終轉(zhuǎn)成 ES5
中的函數(shù)表達(dá)式。我們也知道,JavaScript VM 在執(zhí)行 JS 代碼時(shí),會(huì)有兩個(gè)步驟,首先會(huì)先進(jìn)行編譯,然后才開始執(zhí)行。編譯階段,變量聲明和函數(shù)聲明會(huì)自動(dòng)提升,而函數(shù)表達(dá)式不會(huì)自動(dòng)提升。了解完這些后,問題原因一下子明朗了。
那么要解決上面的問題,最簡單的處理方式是交換類定義的順序。除此之外,我們還可以使用 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) { } }
問題來了,出現(xiàn)上面的問題,我交互個(gè)順序不就完了,為什么還要如此大費(fèi)周章 ?話雖如此,但這樣增加了開發(fā)者的負(fù)擔(dān),要時(shí)刻警惕類定義的順序,特別當(dāng)一個(gè) ts
文件內(nèi)包含多個(gè)內(nèi)部類的時(shí)候。所以更好地方式還是通過 forwardRef
來解決問題,下面我們就來進(jìn)一步揭開 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; } }
通過源碼可以看出,當(dāng)調(diào)用 forwardRef
方法時(shí),我們只是在 forwardRefFn
函數(shù)對(duì)象上,增加了一個(gè)私有屬性__forward_ref__
,同時(shí)覆寫了函數(shù)對(duì)象的 toString
方法。在上面代碼中,我們還發(fā)現(xiàn)了resolveForwardRef
函數(shù),通過函數(shù)名和注釋信息,我們很清楚地了解到,該函數(shù)是用來解析通過 forwardRef
包裝過的引用值。
那么 resolveForwardRef
這個(gè)函數(shù)是由誰負(fù)責(zé)調(diào)用,又是什么時(shí)候調(diào)用呢 ?其實(shí) resolveForwardRef
這個(gè)函數(shù)由 Angular 2 的依賴注入系統(tǒng)調(diào)用,當(dāng)解析 Provider
和創(chuàng)建依賴對(duì)象的時(shí)候,會(huì)自動(dòng)調(diào)用該函數(shù)。
// @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); } } /*************************************************************************/ /** * 構(gòu)造依賴對(duì)象 */ 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 解釋器不自動(dòng)提升 class ?
因?yàn)楫?dāng) class 使用 extends 關(guān)鍵字實(shí)現(xiàn)繼承的時(shí)候,我們不能確保所繼承父類的有效性,那么就可能導(dǎo)致一些無法預(yù)知的行為。
class Dog extends Animal {}function Animal { this.move = function() { alert(defaultMove); } } let defaultMove = "moving"; let dog = new Dog(); dog.move();
以上代碼能夠正常的輸出 moving
,因?yàn)?JavaScript 解釋器把會(huì)把代碼轉(zhuǎn)化為:
let defaultMove,dog; function Animal { this.move = function() { alert(defaultMove); } } class Dog extends Animal { } defaultMove = "moving"; dog = new Dog(); dog.move();
然而,當(dāng)我們把 Animal 轉(zhuǎn)化為函數(shù)表達(dá)式,而不是函數(shù)聲明的時(shí)候:
class Dog extends Animal {} let Animal = function () { this.move = function () { alert(defaultMove); } } let defaultMove = "moving"; let dog = new Dog(); dog.move();
此時(shí)以上代碼將會(huì)轉(zhuǎn)化為:
let Animal, defaultMove, dog; class Dog extends Animal { } Animal = function () { this.move = function () { alert(defaultMove); } } defaultMove = "moving"; dog = new Dog(); dog.move();
當(dāng) class Dog extends Animal
被解釋執(zhí)行的時(shí)候,此時(shí) Animal
的值是 undefined,這樣就會(huì)拋出異常。我們可以簡單地通過調(diào)整 Animal 函數(shù)表達(dá)式的位置,來解決上述問題。
let Animal = function () { this.move = function () { alert(defaultMove); } } class Dog extends Animal{} let defaultMove = "moving"; let dog = new Dog(); dog.move();
假設(shè) class
也會(huì)自動(dòng)提升的話,上面的代碼將被轉(zhuǎn)化為以下代碼:
let Animal, defaultMove, dog; class Dog extends Animal{ } Animal = function () { this.move = function () { alert(defaultMove); } } defaultMove = "moving"; dog = new Dog(); dog.move();
此時(shí) Dog 被提升了,當(dāng)解釋器執(zhí)行 extends Animal
語句的時(shí)候,此時(shí)的 Animal 仍然是 undefined,同樣會(huì)拋出異常。所以 ES6 中的 Class
不會(huì)自動(dòng)提升,主要還是為了解決繼承父類時(shí),父類不可用的問題。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。