Come associarlo correttamente a un getter / setter in Javascript

Aug 25 2020

Diciamo che ho una classe che memorizza le proprietà delle sue istanze in un oggetto annidato:

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

Volevo definire getter / setter speciali per ciascuna delle proprietà (annidate) in modo da poter aggiungere controlli di intervallo / aggiornare automaticamente le proprietà degli elementi HTML specifici dell'istanza, ecc. Quando l'ho provato con il metodo normale, mi sono reso conto che ho Impossibile associare l'ambito a un argomento in getter / 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
}

Il codice sopra non viene eseguito: non perché BindArgs è un prototipo non valido, ma invece, non funziona perché il setter non è effettivamente una funzione . La risposta ha suggerito di utilizzare Object.defineProperty, che effettivamente ha funzionato:

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

Ora, quando ho alcune proprietà come nell'esempio sopra, questo andrebbe bene, ma doverlo fare per dozzine di proprietà diventa estremamente noioso, specialmente per le proprietà nidificate. Esiste un altro modo più ordinato per definire getter / setter personalizzati e poter associare loro argomenti? La sintassi normale sarebbe stata ideale poiché sarebbe stata tutta all'interno della definizione dell'oggetto e non sparsa come Object.defineProperty. La risposta ovvia sarebbe usare le normali funzioni per ottenere / impostare i valori, ma farlo significherebbe dover rifattorizzare molto codice ...

Risposte

4 Klaycon Aug 25 2020 at 00:31

Ti suggerisco di utilizzare i proxy per la convalida . Richiede modifiche al codice molto minime e puoi occuparti di più proprietà in un colpo solo.

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

Nota che questo utilizza una scorciatoia (lo stesso validatore per entrambi Propertiese Properties.Position), se trovi che potresti avere sovrapposizioni di nomi di proprietà potresti aver bisogno di più validatoroggetti.