讓我來深刻地瞭解一下TypeScript對於裝飾器模式的實現,以及反射與依賴注入等相關特性。javascript
在Typescript
的源代碼中,能夠看到裝飾器能用來修飾class
,property
,method
,parameter
:java
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void; declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void; declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void; declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
接下來深刻地瞭解一下每種裝飾器:git
首先來根據上面的標識,實現一個名爲log
的方法裝飾器。使用裝飾器的方法很簡單:在裝飾器名前加@
字符,寫在想要裝飾的方法上,相似寫註釋的方式:github
class C { @log foo(n: number) { return n * 2; } }
裝飾器其實是一個函數,入參爲所裝飾的方法,返回值爲裝飾後的方法。在使用以前須要提早實現這個裝飾器函數,以下:數組
function log(target: Function, key: string, descriptor: any) { // target === C.prototype // key === "foo" // descriptor === Object.getOwnPropertyDescriptor(C.prototype, "foo") // 保存對原方法的引用,避免重寫 var originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { // 將「foo」函數的參數列表轉化爲字符串 var a = args.map(a => JSON.stringify(a)).join(); // 調用 foo() 並獲取它的返回值 var result = originalMethod.apply(this, args); // 將返回的結果轉成字符串 var r = JSON.stringify(result); // 打印日誌 console.log(`Call: ${key}(${a}) => ${r}`); // 返回調用 foo 的結果 return result; } // 返回已編輯的描述符 return descriptor; }
該裝飾器函數包含三個參數:app
target
:所要修飾的方法。key
:被修飾方法的名字。descriptor
:屬性描述符,若是爲給定能夠經過調用Object.getOwnPropertyDescriptor()
來獲取。咱們觀察到,類C
中使用的裝飾器函數log
並無顯式的參數傳遞,難免好奇它所須要的參數是如何傳遞的?以及該函數是如何被調用的?函數
TypeScript最終仍是會被編譯爲JavaScript執行,爲了搞清上面的問題,咱們來看一下TypeScript編譯器將類C
的定義最終生成的JavaScript代碼:this
var C = (function () { function C() { } C.prototype.foo = function (n) { return n * 2; }; Object.defineProperty(C.prototype, "foo", __decorate([ log ], C.prototype, "foo", Object.getOwnPropertyDescriptor(C.prototype, "foo"))); return C; })();
而爲添加裝飾器所生成的JavaScript代碼以下:es5
var C = (function () { function C() { } C.prototype.foo = function (n) { return n * 2; }; return C; })();
對比二者發現使用裝飾的不一樣,只是在類定義中,多了以下代碼:prototype
Object.defineProperty( __decorate( [log], // 裝飾器 C.prototype, // target:C的原型 "foo", // key:裝飾器修飾的方法名 Object.getOwnPropertyDescriptor(C.prototype, "foo") // descriptor ); );
經過查詢MDN文檔,能夠知悉defineProperty
的做用:
Object.defineProperty()
方法可直接在一個對象上定義一個新的屬性,或者修改對象上一個已有的屬性,而後返回這個對象。
TypeScript編譯器經過defineProperty
方法重寫了所修飾的方法foo
,新方法的實現是由函數__decorate
返回的,那麼問題來了:__decorate
函數在哪聲明的呢?
掘地三尺不難找到,來一塊兒把玩一下:
var __decorate = this.__decorate || function (decorators, target, key, desc) { if (typeof Reflect === "object" && typeof Reflect.decorate === "function") { return Reflect.decorate(decorators, target, key, desc); } switch (arguments.length) { case 2: return decorators.reduceRight(function(o, d) { return (d && d(o)) || o; }, target); case 3: return decorators.reduceRight(function(o, d) { return (d && d(target, key)), void 0; }, void 0); case 4: return decorators.reduceRight(function(o, d) { return (d && d(target, key, o)) || o; }, desc); } };
第一行使用了或操做符(||
),以確保若是函數__decorate
已被建立,他將不會被重寫。
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
第二行是一個條件語句,使用了JavaScript的一個新特性:元數據反射。這個主題後續再展開講述,下面咱們先聚焦觀察下該新特性的兼容方案:
switch (arguments.length) { case 2: return decorators.reduceRight(function(o, d) { return (d && d(o)) || o; }, target); case 3: return decorators.reduceRight(function(o, d) { return (d && d(target, key)), void 0; }, void 0); case 4: return decorators.reduceRight(function(o, d) { return (d && d(target, key, o)) || o; }, desc); }
此處__decorate
函數接受了4個參數,因此case 4
將被執行。平心而論這塊代碼有點生澀,不要緊掰開揉碎了看。
reduceRight
方法接受一個函數做爲累加器和數組的每一個值(從右到左)將其減小爲單個值。
爲了方便理解,上面的代碼重寫以下:
[log].reduceRight(function(log, desc) { if(log) { return log(C.prototype, "foo", desc); } else { return desc; } }, Object.getOwnPropertyDescriptor(C.prototype, "foo"));
能夠看到當這段代碼執行的時候,裝飾器函數log
被調用,而且參數C.prototype
,"foo"
,previousValue
也被傳入,如此以前的問題如今能夠解答了。
通過裝飾過的foo
方法,它依然按照原來的方式執行,只是額外執行了附件的裝飾器函數log
的功能。
const c = new C(); const r = c.foo(23); // "Call: foo(23) => 46" console.log(r); // 46