上一篇文章中,咱們討論了TypeScript源碼中關於方法裝飾器的實現,搞明白了以下幾個問題:javascript
__decorate
函數幹了些什麼事情?接下來咱們繼續屬性裝飾器的觀察。java
屬性裝飾器的聲明標識以下:segmentfault
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
以下咱們爲一個類的屬性添加了一個名爲@logProperty
的裝飾器閉包
class Person { @logProperty public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } }
上一篇解釋過,當這段代碼最後被編譯成JavaScript執行時,方法__decorate
會被調用,但此處會少最後一個參數(經過Object. getOwnPropertyDescriptor
屬性描述符)app
var Person = (function () { function Person(name, surname) { this.name = name; this.surname = surname; } __decorate( [logProperty], Person.prototype, "name" ); return Person; })();
須要注意的是,此次TypeScript編譯器並沒像方法裝飾器那樣,使用__decorate
返回的結果覆蓋原始屬性。緣由是屬性裝飾器並不須要返回什麼。函數
Object.defineProperty(C.prototype, "foo", __decorate( [log], C.prototype, "foo", Object.getOwnPropertyDescriptor(C.prototype, "foo") ) );
那麼,接下來具體實現這個@logProperty
裝飾器工具
function logProperty(target: any, key: string) { // 屬性值 var _val = this[key]; // getter var getter = function () { console.log(`Get: ${key} => ${_val}`); return _val; }; // setter var setter = function (newVal) { console.log(`Set: ${key} => ${newVal}`); _val = newVal; }; // 刪除屬性 if (delete this[key]) { // 建立新的屬性 Object.defineProperty(target, key, { get: getter, set: setter, enumerable: true, configurable: true }); } }
實現過程首先聲明瞭一個變量_val
,並用所裝飾的屬性值給它賦值(此處的this
指向類的原型,key
爲屬性的名字)。this
接着聲明瞭兩個方法getter
和setter
,因爲函數是閉包建立的,因此在其中能夠訪問變量_val
,在其中能夠添加額外的自定義行爲,這裏添加了將屬性值打印在控制檯的操做。prototype
而後使用delete
操做符將原屬性從類的原型中刪除,不過須要注意的是:若是屬性存在不可配置的屬性時,這裏if(delete this[key])
會返回false。而當屬性被成功刪除,方法Object.defineProperty()
將建立一個和原屬性同名的屬性,不一樣的是新的屬性getter
和setter
方法,使用上面新建立的。code
至此,屬性裝飾器的實現就完成了,運行結果以下:
var me = new Person("Remo", "Jansen"); // Set: name => Remo me.name = "Remo H."; // Set: name => Remo H. name; // Get: name Remo H.
類裝飾器的聲明標識以下:
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
能夠像以下方式使用類裝飾器:
@logClass class Person { public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } }
和以前不一樣的是,通過TypeScript編譯器編譯爲JavaScript後,調用__decorate
函數時,與方法裝飾器相比少了後兩個參數。僅傳遞了Person
而非Person.prototype
。
var Person = (function () { function Person(name, surname) { this.name = name; this.surname = surname; } Person = __decorate( [logClass], Person ); return Person; })();
值得注意的是,__decorate
的返回值複寫了原始的構造函數,緣由是類裝飾器必須返回一個構造器函數。接下來咱們就來實現上面用到的類裝飾器@logClass
:
function logClass(target: any) { // 保存對原始構造函數的引用 var original = target; // 用來生成類實例的方法 function construct(constructor, args) { var c : any = function () { return constructor.apply(this, args); } c.prototype = constructor.prototype; return new c(); } // 新的構造函數 var f : any = function (...args) { console.log("New: " + original.name); return construct(original, args); } // 複製原型以便`intanceof`操做符可使用 f.prototype = original.prototype; // 返回新的構造函數(會覆蓋原有構造函數) return f; }
這裏實現的構造器中,聲明瞭一個名爲original
的變量,並將所裝飾類的構造函數賦值給它。接着聲明一個工具函數construct
,用來建立類的實例。而後定義新的構造函數f
,在其中調用原來的構造函數並將初始化的類名打印在控制檯,固然咱們也能夠添加一些其餘自定義的行爲。
原始構造函數的原型被複制給f
的原型,以確保在建立一個Person
的新實例時,instanceof
操做符如願以償,具體緣由可參考鄙人另外一篇文章原型與對象。
至此類裝飾器的實現就完成了,能夠驗證下:
var me = new Person("Remo", "Jansen"); // New: Person me instanceof Person; // true