如何观察阵列变化?
2022-08-30 04:29:09
在Javascript中,有没有办法在使用推送,弹出,移位或基于索引的赋值修改数组时收到通知?我想要一些东西来触发我可以处理的事件。
我知道SpiderMonkey中的功能,但这只有在整个变量设置为其他变量时才有效。watch()
在Javascript中,有没有办法在使用推送,弹出,移位或基于索引的赋值修改数组时收到通知?我想要一些东西来触发我可以处理的事件。
我知道SpiderMonkey中的功能,但这只有在整个变量设置为其他变量时才有效。watch()
有几个选项...
走快速而肮脏的路线,您可以覆盖数组1 的方法:push()
Object.defineProperty(myArray, "push", {
// hide from for..in and prevent further overrides (via default descriptor values)
value: function () {
for (var i = 0, n = this.length, l = arguments.length; i < l; i++, n++) {
RaiseMyEvent(this, n, this[n] = arguments[i]); // assign/raise your event
}
return n;
}
});
1 或者,如果要定位所有数组,可以覆盖 Array.prototype.push()。
不过,要小心。环境中的其他代码可能不喜欢或期望进行此类修改。不过,如果一个包罗万象的东西听起来很吸引人,只需用Array.prototype
替换myArray
。
现在,这只是一种方法,有很多方法可以更改数组内容。我们可能需要更全面的东西...
您可以创建自己的可观察数组,而不是重写方法。此特定实现将数组复制到新的类似数组的对象中,并提供自定义 、、、和方法以及自定义索引访问器(前提是数组大小只能通过上述方法或属性之一进行修改)。push()
pop()
shift()
unshift()
slice()
splice()
length
function ObservableArray(items) {
var _self = this,
_array = [],
_handlers = {
itemadded: [],
itemremoved: [],
itemset: []
};
function defineIndexProperty(index) {
if (!(index in _self)) {
Object.defineProperty(_self, index, {
configurable: true,
enumerable: true,
get: function() {
return _array[index];
},
set: function(v) {
_array[index] = v;
raiseEvent({
type: "itemset",
index: index,
item: v
});
}
});
}
}
function raiseEvent(event) {
_handlers[event.type].forEach(function(h) {
h.call(_self, event);
});
}
Object.defineProperty(_self, "addEventListener", {
configurable: false,
enumerable: false,
writable: false,
value: function(eventName, handler) {
eventName = ("" + eventName).toLowerCase();
if (!(eventName in _handlers)) throw new Error("Invalid event name.");
if (typeof handler !== "function") throw new Error("Invalid handler.");
_handlers[eventName].push(handler);
}
});
Object.defineProperty(_self, "removeEventListener", {
configurable: false,
enumerable: false,
writable: false,
value: function(eventName, handler) {
eventName = ("" + eventName).toLowerCase();
if (!(eventName in _handlers)) throw new Error("Invalid event name.");
if (typeof handler !== "function") throw new Error("Invalid handler.");
var h = _handlers[eventName];
var ln = h.length;
while (--ln >= 0) {
if (h[ln] === handler) {
h.splice(ln, 1);
}
}
}
});
Object.defineProperty(_self, "push", {
configurable: false,
enumerable: false,
writable: false,
value: function() {
var index;
for (var i = 0, ln = arguments.length; i < ln; i++) {
index = _array.length;
_array.push(arguments[i]);
defineIndexProperty(index);
raiseEvent({
type: "itemadded",
index: index,
item: arguments[i]
});
}
return _array.length;
}
});
Object.defineProperty(_self, "pop", {
configurable: false,
enumerable: false,
writable: false,
value: function() {
if (_array.length > -1) {
var index = _array.length - 1,
item = _array.pop();
delete _self[index];
raiseEvent({
type: "itemremoved",
index: index,
item: item
});
return item;
}
}
});
Object.defineProperty(_self, "unshift", {
configurable: false,
enumerable: false,
writable: false,
value: function() {
for (var i = 0, ln = arguments.length; i < ln; i++) {
_array.splice(i, 0, arguments[i]);
defineIndexProperty(_array.length - 1);
raiseEvent({
type: "itemadded",
index: i,
item: arguments[i]
});
}
for (; i < _array.length; i++) {
raiseEvent({
type: "itemset",
index: i,
item: _array[i]
});
}
return _array.length;
}
});
Object.defineProperty(_self, "shift", {
configurable: false,
enumerable: false,
writable: false,
value: function() {
if (_array.length > -1) {
var item = _array.shift();
delete _self[_array.length];
raiseEvent({
type: "itemremoved",
index: 0,
item: item
});
return item;
}
}
});
Object.defineProperty(_self, "splice", {
configurable: false,
enumerable: false,
writable: false,
value: function(index, howMany /*, element1, element2, ... */ ) {
var removed = [],
item,
pos;
index = index == null ? 0 : index < 0 ? _array.length + index : index;
howMany = howMany == null ? _array.length - index : howMany > 0 ? howMany : 0;
while (howMany--) {
item = _array.splice(index, 1)[0];
removed.push(item);
delete _self[_array.length];
raiseEvent({
type: "itemremoved",
index: index + removed.length - 1,
item: item
});
}
for (var i = 2, ln = arguments.length; i < ln; i++) {
_array.splice(index, 0, arguments[i]);
defineIndexProperty(_array.length - 1);
raiseEvent({
type: "itemadded",
index: index,
item: arguments[i]
});
index++;
}
return removed;
}
});
Object.defineProperty(_self, "length", {
configurable: false,
enumerable: false,
get: function() {
return _array.length;
},
set: function(value) {
var n = Number(value);
var length = _array.length;
if (n % 1 === 0 && n >= 0) {
if (n < length) {
_self.splice(n);
} else if (n > length) {
_self.push.apply(_self, new Array(n - length));
}
} else {
throw new RangeError("Invalid array length");
}
_array.length = n;
return value;
}
});
Object.getOwnPropertyNames(Array.prototype).forEach(function(name) {
if (!(name in _self)) {
Object.defineProperty(_self, name, {
configurable: false,
enumerable: false,
writable: false,
value: Array.prototype[name]
});
}
});
if (items instanceof Array) {
_self.push.apply(_self, items);
}
}
(function testing() {
var x = new ObservableArray(["a", "b", "c", "d"]);
console.log("original array: %o", x.slice());
x.addEventListener("itemadded", function(e) {
console.log("Added %o at index %d.", e.item, e.index);
});
x.addEventListener("itemset", function(e) {
console.log("Set index %d to %o.", e.index, e.item);
});
x.addEventListener("itemremoved", function(e) {
console.log("Removed %o at index %d.", e.item, e.index);
});
console.log("popping and unshifting...");
x.unshift(x.pop());
console.log("updated array: %o", x.slice());
console.log("reversing array...");
console.log("updated array: %o", x.reverse().slice());
console.log("splicing...");
x.splice(1, 2, "x");
console.log("setting index 2...");
x[2] = "foo";
console.log("setting length to 10...");
x.length = 10;
console.log("updated array: %o", x.slice());
console.log("setting length to 2...");
x.length = 2;
console.log("extracting first element via shift()");
x.shift();
console.log("updated array: %o", x.slice());
})();
请参阅对象。defineProperty()
以供参考。
这让我们更接近,但它仍然不是防弹的...这使我们:
代理对象为现代浏览器提供了另一种解决方案。它允许您拦截方法调用,访问器等。最重要的是,您甚至可以在不提供显式属性名称的情况下执行此操作...这将允许您测试任意的,基于索引的访问/分配。您甚至可以拦截属性删除。代理将有效地允许您在决定允许更改之前检查更改...除了事后处理变更。
下面是一个精简的示例:
(function() {
if (!("Proxy" in window)) {
console.warn("Your browser doesn't support Proxies.");
return;
}
// our backing array
var array = ["a", "b", "c", "d"];
// a proxy for our array
var proxy = new Proxy(array, {
deleteProperty: function(target, property) {
console.log("Deleted %s", property);
return true;
},
set: function(target, property, value, receiver) {
target[property] = value;
console.log("Set %s to %o", property, value);
return true;
}
});
console.log("Set a specific index..");
proxy[0] = "x";
console.log("Add via push()...");
proxy.push("z");
console.log("Add/remove via splice()...");
proxy.splice(1, 3, "y");
console.log("Current state of array: %o", array);
})();
通过阅读这里的所有答案,我组装了一个简化的解决方案,不需要任何外部库。
它还更好地说明了该方法的一般思想:
function processQ() {
// ... this will be called on each .push
}
var myEventsQ = [];
myEventsQ.push = function() { Array.prototype.push.apply(this, arguments); processQ();};