裝飾器與元數據反射(2)屬與類性裝飾器

上一篇文章中,咱們討論了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

接着聲明瞭兩個方法gettersetter,因爲函數是閉包建立的,因此在其中能夠訪問變量_val,在其中能夠添加額外的自定義行爲,這裏添加了將屬性值打印在控制檯的操做。prototype

而後使用delete操做符將原屬性從類的原型中刪除,不過須要注意的是:若是屬性存在不可配置的屬性時,這裏if(delete this[key])會返回false。而當屬性被成功刪除,方法Object.defineProperty()將建立一個和原屬性同名的屬性,不一樣的是新的屬性gettersetter方法,使用上面新建立的。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
相關文章
相關標籤/搜索