我有Array of Objects
:
var boxes = [
{ left: 10, top: 20 }, // boxes[0]
{ left: 100, top: 200 } // boxes[1]
];
boxes[0].left = 20; // example of 'set' action
我希望能够为boxes
个对象项的每个属性定义通用getter或setter,即使添加了更多项,例如:boxes.push({ left: 30, top: 40 });
通过这种方式,最终目标是隐式地将addListener
函数附加到每个对象项,该对象项将侦听器函数绑定到属性的setter。像这样:
boxes[0].addListener("left", function(new_value){
console.log("left has been changed to "+new_value);
});
boxes[2].addListener("top", ...
假设每个boxes[i]
项都将填充如下:
{
left: 10,
top: 20,
set left(val){ /* would trigger every left listener */ },
set top(val){ /* would trigger every top listener */ },
addListener: function(prop, func){ /* ... */ }
}
当然,那些侦听器/ getter / setter必须动态实现(即与Array.push()功能兼容)。
并且"锦上添花"是boxes
数组应该与JSON.stringify()保持一致,所以:
JSON.stringify(boxes); // would return [{"left":10,"top":20},{"left":100,"top":200}]
我想应该可以使用Proxies和Object.defineProperty()的某种组合,但我无法做到......
来自JS大师的任何想法?
答案 0 :(得分:0)
最后,经过长时间的试验和错误,我找到了一个优雅的解决方案。
1。首先,我们使用一些基本上注册给定侦听器的通用Object.prototype
和addListener(prop, listener)
函数覆盖removeListener(prop, listener)
(每个对象/数组的常规函数)一个"看不见的" __listeners__
数组:
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。现在我们要检测任何"设置" boxes
对象的任何属性/子属性的操作。为实现此目的,我们需要设置递归使用Proxies。
基本上,Proxy是给定对象/数组的图像。默认情况下,代理上的每个操作也都在Object / Array上进行,反之亦然:
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
现在的不同之处在于,可以检测并覆盖在代理服务器上执行的每个操作。
在boxes_proxy[0].left = 20;
的情况下,检测到的操作分为两部分:
boxes_proxy[0].
:boxes
上检测到的操作是"获取属性0",返回boxes[0]
作为默认值" get&#34 ;处理程序。
.left = 20;
left
的{{1}}属性设置为20,但未检测到它,因为它不是同一个对象。要检测它,我们必须在boxes[0]
上创建另一个代理。
这是有趣的部分。要为任何子级别的对象属性(例如boxes[0]
)解决此问题,我们可以创建boxes[0].foo.bar[2].font.color
和recursiveGetter
函数,方式是genericSetter
返回代理而不是相应的Object / Array并绑定任何" set"对触发侦听器的recursiveGetter
函数的操作:
genericSetter
所以现在我们只需要在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;
}
上应用这些getter / setter,它们将以递归方式应用:
new Proxy(boxes)
由于添加的对象/函数不可枚举,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}]
仍然适用。