2017-05-13 4 views
2

J'essaye d'implémenter un décorateur qui remplace une propriété (1) et définit une propriété cachée (2). Supposons que l'exemple suivant:Décorateur tapuscrit et comportement bizarre 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); 

qui délivre en sortie:

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

Cependant, je préférerais attendre:

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

depuis enumerable est true pour propA.

Maintenant, si je remplace get et set avec

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

la sortie est maintenant:

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

Bien que enumerable est explicitement défini à false pour _propA en f. Donc, aussi bizarre que ces comportements puissent être, j'aimerais comprendre ce qui se passe ici, et comment j'implémenterais ce que j'essaie d'obtenir?

Répondre

2

D'accord, cela m'a pris un peu de temps, mais j'ai trouvé une solution de contournement. Le problème semble être que Object.defineProperty ne fonctionne pas correctement au moment de la décoration. Si vous le faites à l'exécution, les choses se passent comme prévu. Alors, comment définissez-vous la propriété à l'intérieur du décorateur, mais à l'exécution? Voici l'astuce: puisque le remplacement de la propriété dans le décorateur fonctionne au moment de la décoration (seul le comportement énumérable semble être rompu), vous pouvez définir la propriété mais utiliser une fonction d'initialisation à la place de getter et setter. Cette fonction sera exécutée la première fois que la propriété est affectée (set) ou accédée (get). Lorsque cela se produit, le mot this fait référence à l'instance d'exécution de votre objet, ce qui signifie que vous pouvez initialiser correctement ce que vous aviez l'intention de faire au moment de la décoration.

Voici la solution:

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 
     }); 
    }; 
} 

qui sort:

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

Cette solution prend en charge les valeurs par défaut, car ils sont affectés après avoir été initialisées le get/set correct.

Il soutient également enumerable correctement: mis enumerable à true pour les biens pKey et la sortie sera:

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

Ce n'est pas assez, je le sais, mais cela fonctionne et ne réduit pas les performances pour autant que je connaître.