2017-05-13 12 views
2

私はプロパティ(1)をオーバーライドし、隠しプロパティ(2)を定義するデコレータを実装しようとしています。次の例を想定しますTypescriptデコレータとObject.defineProperty奇妙な振る舞い

function f() { 
    return (target: any, key: string) => { 

     let pKey = '_' + key; 

     // 1. Define hidden property 
     Object.defineProperty(target, pKey, { 
      value: 0, 
      enumerable: false, 
      configurable: true, 
      writable: true 
     }); 

     // 2. Override property get/set 
     return Object.defineProperty(target, key, { 
      enumerable: true, 
      configurable: true, 
      get:() => target[pKey], 
      set: (val) => { 
       target[pKey] = target[pKey] + 1; 
      } 
     }); 
    }; 
} 

class A { 
    @f() 
    propA = null; 
    propB = null; 
} 

let a = new A(); 

console.log(Object.keys(a), a.propA, a._propA, a); 

出力する:

[ 'propB' ] 1 1 A { propB: null } 

しかし、私はむしろ期待:enumerable以来

[ 'propA', 'propB' ] 1 1 A { propA: 1, propB: null } 

propAためtrueです。私は

get: function() { 
    return this[pKey] 
}, 
set: function (val) { 
    this[pKey] = this[pKey] + 1; 
} 

getsetを交換する場合

は今、出力は以下のようになります。

[ '_propA', 'propB' ] 1 1 A { _propA: 1, propB: null } 

enumerableが明示的にf_propAためfalseに設定されているが。

このような振る舞いが奇妙なので、私はここで何が起こっているのか理解したいと思います。

答えて

2

申し訳ありませんが、しばらく時間がかかりましたが、回避策が見つかりました。問題は装飾時にObject.definePropertyが正しく動作しないことが原因です。あなたが実行時にそれを行うならば、物事は期待どおりに行きます。では、デコレータの内部ではどのようにプロパティを定義しますか?

デコレータ内のプロパティをオーバーライドするとデコレーション時に機能するため(列挙型の動作のみが壊れているように見える)、プロパティを定義できますが、gettersetterの代わりに初期化関数を使用できます。この関数は、プロパティが割り当てられた最初の時点(set)またはアクセスされた時点(get)に実行されます。その場合、thisという単語はオブジェクトのランタイムインスタンスを参照します。つまり、デコレーション時に意図したとおりに正しく初期化できます。

function f() { 
    return (target: any, key: string) => { 
     let pKey = `_${key}`; 

     let init = function (isGet: boolean) { 
      return function (newVal?) { 
       /* 
       * This is called at runtime, so "this" is the instance. 
       */ 

       // Define hidden property 
       Object.defineProperty(this, pKey, {value: 0, enumerable: false, configurable: true, writable: true}); 
       // Define public property 
       Object.defineProperty(this, key, { 
        get:() => { 
         return this[pKey]; 
        }, 
        set: (val) => { 
         this[pKey] = this[pKey] + 1; 
        }, 
        enumerable: true, 
        configurable: true 
       }); 

       // Perform original action 
       if (isGet) { 
        return this[key]; // get 
       } else { 
        this[key] = newVal; // set 
       } 
      }; 
     }; 

     // Override property to let init occur on first get/set 
     return Object.defineProperty(target, key, { 
      get: init(true), 
      set: init(false), 
      enumerable: true, 
      configurable: true 
     }); 
    }; 
} 

出力:

[ 'propA', 'propB' ] 1 1 A { propA: [Getter/Setter], propB: null } 

を正しく取得/設定が初期化された後、それらが割り当てられているので、このソリューションは、デフォルト値をサポートしています。ここ

は、ソリューションです。

また、適切enumerableをサポートしています。プロパティpKeyためtrueenumerableを設定し、出力は次のようになります。

[ '_propA', 'propA', 'propB' ] 1 1 A { _propA: 1, propA: [Getter/Setter], propB: null } 

それは私がいることを知って、きれいではありませんが、それが動作し、限り私としての性能を低下させません知っている。