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:
boxes_proxy[0].
: die erfasste Aktion auf boxes
ist "Eigentum 0 erhalten", die boxes[0]
als Standard gibt Handler "get".
.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.
Ja, versuchen Sie einen Proxy mit einem 'set' Trap zu verwenden. – Bergi
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"! –
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. –