Comment lier correctement ceci à un getter / setter en Javascript

Aug 25 2020

Disons que j'ai une classe qui stocke les propriétés de ses instances dans un objet imbriqué:

this.Properties = {
  "Position":{
    "X": 400,
    "Y": 100
  },
  "Colour": "#007fff7f"
};

Je voulais définir des getters / setters spéciaux pour chacune des propriétés (imbriquées) afin que je puisse ajouter des vérifications de plage / mettre à jour automatiquement les propriétés des éléments HTML spécifiques à l'instance, etc. Quand je l'ai essayé avec la méthode normale, j'ai réalisé que je impossible de lier la portée à un argument dans les getters / setters:

//(based on https://stackoverflow.com/a/16400626)
//Define function prototype for binding an argument without overriding the old this:
Function.prototype.BindArgs = function(...boundArgs){
  const targetFunction = this;
  return function (...args) { return targetFunction.call(this, ...boundArgs, ...args); };
};

//...

{
  get X(){
    return this.__X__;
  },
  set X(Scope, Value){
    this.__X__ = Value;
    Scope.HTMLElement.style.left = Value + "px";
  }.BindArgs(this)  //This is incorrect syntax
}

Le code ci-dessus ne s'exécute pas: non pas parce que BindArgs est un prototype non valide, mais à la place, il ne fonctionne pas parce que le setter n'est pas réellement une fonction . La réponse a suggéré d'utiliser Object.defineProperty, qui a réellement fonctionné:

Object.defineProperty(this.Properties.Position, "X", {
  "get": function(){
    return this.__X__;
  }
  "set": function(Scope, Value){
    this.__X__ = Value;
    Scope.HTMLElement.style.left = Value + "px";
  }.BindArgs(this)
});

Maintenant, quand j'ai quelques propriétés comme dans l'exemple ci-dessus, ce serait bien, mais devoir le faire pour des dizaines de propriétés devient extrêmement fastidieux - en particulier pour les propriétés imbriquées. Existe-t-il une autre manière plus ordonnée de définir des getters / setters personnalisés et de pouvoir leur lier des arguments? La syntaxe normale aurait été idéale car tout serait à l'intérieur de la définition de l'objet et non dispersé comme Object.defineProperty. La réponse évidente serait d'utiliser des fonctions normales pour obtenir / définir les valeurs, mais cela signifierait avoir à refactoriser beaucoup de code ...

Réponses

4 Klaycon Aug 25 2020 at 00:31

Je vous suggère d'utiliser des Proxies pour la validation . Cela nécessite des changements de code très minimes et vous pouvez vous occuper de plusieurs propriétés d'un seul coup.

let validator = {
  set: function(obj, prop, value) {
    //in any of these cases you can return false or throw an error to refuse the new value
    switch(prop) {
      case "X":
        Scope.HTMLElement.style.left = value + "px";
        break;
      case "Y":
        Scope.HTMLElement.style.top = value + "px";
        break;
      case "Colour":
        Scope.HTMLElement.style.color = value;
    }

    obj[prop] = value;

    return true;
  }
};

this.Properties.Position = new Proxy(this.Properties.Position, validator);
this.Properties = new Proxy(this.Properties, validator);

Notez que cela utilise un raccourci (le même validateur pour les deux Propertieset Properties.Position), si vous trouvez que vous pouvez avoir des chevauchements de noms de propriétés, vous devrez peut-être plusieurs validatorobjets.