2016-09-13 2 views
1

Ich habe ein Array of Objects:Getter/Setter für Objekteigenschaften in einem Array

var boxes = [ 
    { left: 10, top: 20 }, // boxes[0] 
    { left: 100, top: 200 } // boxes[1] 
]; 

boxes[0].left = 20; // example of 'set' action 

Ich möchte generische Getter oder Setter für jede Eigenschaft zu definieren, in der Lage sein von boxes Objektpositionen, auch wenn weitere Artikel sind hinzugefügt, wie folgt: boxes.push({ left: 30, top: 40 });

Auf diese Weise ist das endgültige Ziel, implizit eine addListener Funktion an jedes Objekt Element, das eine Listener-Funktion an den Setter der Eigenschaft bindet anhängen. Etwas wie folgt aus:

boxes[0].addListener("left", function(new_value){ 
    console.log("left has been changed to "+new_value); 
}); 

boxes[2].addListener("top", ... 

jeden boxes[i] Artikel Unter der Annahme, würde wie folgt gefüllt werden:

{ 
    left: 10, 
    top: 20, 
    set left(val){ /* would trigger every left listener */ }, 
    set top(val){ /* would trigger every top listener */ }, 
    addListener: function(prop, func){ /* ... */ } 
} 

natürlich die Zuhörer/Getter/Setter haben dynamisch umgesetzt werden (das heißt kompatibel mit der Array.push()-Funktion).

Und „Tüpfelchen auf dem i“ ist, dass die boxes Array mit JSON.stringify() parsable bleiben sollte, so:

JSON.stringify(boxes); // would return [{"left":10,"top":20},{"left":100,"top":200}] 

Ich denke, es mit einer Kombination aus Proxies und Object.defineProperty() oder etwas möglich sein sollte, aber ich kann‘ t machen es ...

Irgendwelche Ideen von einem JS-Guru?

+1

Ja, versuchen Sie einen Proxy mit einem 'set' Trap zu verwenden. – Bergi

+0

Bitte beachten Sie die "Wenn Fragen enthalten" Tags "in ihren Titeln?" (Http://meta.stackexchange.com/questions/19190/should-questions-include-tags-in-the-titles), wo der Konsens ist "nein, sollten sie nicht"! –

+1

Müssen Sie IE unterstützen? Wenn dies der Fall ist, benötigen Sie einen Polyfill wie [this one] (https://github.com/GoogleChrome/proxy-polyfill) - beachten Sie, dass dies mit dem Nachteil verbunden ist, dass Sie einem vorhandenen Objekt keine dynamischen Eigenschaften hinzufügen können Objekt. Damit 'JSON.stringify()' wie gewünscht funktioniert, können Sie Ihre eigene 'toJSON'-Methode implementieren. –

Antwort

0

Endlich fand ich nach langen Versuchen und Fehlern eine elegante Lösung.

1. Zuerst haben wir Object.prototype (allgemeine Funktionen zu jedem Objekt/Array) mit einigen generic addListener(prop, listener) und removeListener(prop, listener) Funktionen außer Kraft setzen, die im Grunde die gegebenen Zuhörer zu einer "unsichtbaren" __listeners__ Array registrieren:

Object.defineProperty(Object.prototype, "addListener", { 
    value: function(prop, listener){ 
     if(!("__listeners__" in this)){ 
      Object.defineProperty(this, "__listeners__", {value: {}, writable: true}); 
     } 
     if(!(prop in this.__listeners__)) this.__listeners__[prop] = []; 
     if(this.__listeners__[prop].indexOf(listener) == -1){ 
      this.__listeners__[prop].push(listener); 
      return true; 
     } else return false; 
    } 
}); 
Object.defineProperty(Object.prototype, "removeListener", { 
    value: function(prop, listener){ 
     if("__listeners__" in this && prop in this.__listeners__){ 
      var index = this.__listeners__[prop].indexOf(listener); 
      if(index > -1){ 
       this.__listeners__[prop].splice(index, 1); 
       return true; 
      } else return false; 
     } else return false; 
    } 
}); 

2. Nun wollen wir jede "set" Aktion einer beliebigen Eigenschaft/Untereigenschaft des Objekts boxes erkennen. Um dies zu erreichen, müssen wir eine rekursive Verwendung von Proxies einrichten.

Grundsätzlich ist ein Proxy ein Bild eines gegebenen Object/Array. Standardmäßig wird jede Aktion, die auf dem Proxy auch auf dem Objekt/Array gemacht, und umgekehrt:

var boxes = [ 
    { left: 10, top: 20 }, // boxes[0] 
    { left: 100, top: 200 } // boxes[1] 
]; 

var boxes_proxy = new Proxy(boxes); 

boxes_proxy[0].left = 20; 

console.log(boxes_proxy[0].left); // 20 
console.log(boxes[0].left); // 20 

nun der Unterschied ist, dass jede Aktion auf einem Proxy gemacht erkannt und außer Kraft gesetzt werden.
Im Falle von boxes_proxy[0].left = 20; die erfasste Aktion in zwei Teile geteilt ist:

  1. boxes_proxy[0].: die erfasste Aktion auf boxes ist "Eigentum 0 erhalten", die boxes[0] als Standard gibt Handler "get".

  2. .left = 20; Die left Eigenschaft von boxes[0] ist auf 20 gesetzt, aber es wird nicht erkannt, da es nicht das gleiche Objekt ist.Um es zu erkennen, müssen wir einen weiteren Proxy auf boxes[0] erstellen.

Hier kommt der interessante Teil. Um dieses Problem für jede Unterebene der Objekteigenschaft (zum Beispiel boxes[0].foo.bar[2].font.color) zu lösen, können wir die Funktionen recursiveGetter und genericSetter so erstellen, dass die recursiveGetter einen Proxy anstelle des entsprechenden Object/Array zurückgibt und jede "set" -Aktion bindet die genericSetter Funktion, die die Zuhörer auslöst:

var recursiveGetter = function(real_object, prop){ 
    if(typeof real_object[prop] == "object"){ 
     var prop_proxy = new Proxy(real_object[prop], { 
      get: recursiveGetter, 
      set: genericSetter 
     }); 
     return prop_proxy; 
    } else return real_object[prop]; 
} 

var genericSetter = function(real_object, prop, value){ 
    real_object[prop] = value; 
    if("__listeners__" in real_object && prop in real_object.__listeners__){ 
     real_object.__listeners__[prop].forEach(function(fct){ fct(value); }); 
    } 
    return true; 
} 

So, jetzt müssen wir diese Getter/Setter auf einem new Proxy(boxes) gelten nur, und sie werden rekursiv angewendet werden:

var boxes_proxy = new Proxy(boxes, { get: recursiveGetter, set: genericSetter }); 

boxes_proxy[0].addListener("left", function(new_value){ 
    console.log("left has been changed to "+new_value); 
}); 

boxes_proxy[0].left = 20; // "left has been changed to 20" 

JSON.stringify(boxes); // [{"left":20,"top":20},{"left":100,"top":200}] 

Da die hinzugefügten Objekte/Funktionen sind nicht enumerab le, JSON.stringify ist noch anwendbar.

Verwandte Themen