어레이 변경을 관찰하는 방법은 무엇입니까?
Javascript에서 푸시, 팝, 시프트 또는 인덱스 기반 할당을 사용하여 배열이 수정 될 때 알림을받는 방법이 있습니까? 내가 처리 할 수있는 이벤트를 발생시킬 무언가를 원합니다.
watch()
SpiderMonkey 의 기능에 대해 알고 있지만 전체 변수가 다른 것으로 설정된 경우에만 작동합니다.
몇 가지 옵션이 있습니다 ...
1. 푸시 방법 무시
빠르고 더러운 경로로 이동하면 push()
배열 1에 대한 메서드를 재정의 할 수 있습니다 .
Object.defineProperty(myArray, "push", {
enumerable: false, // hide from for...in
configurable: false, // prevent further meddling...
writable: false, // see above ^
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()
. 하지만주의하십시오. 환경의 다른 코드는 그러한 종류의 수정을 좋아하거나 기대하지 않을 수 있습니다. 포괄 소리가 호소하는 경우 그러나, 바로 교체 myArray
와 함께 Array.prototype
.
이제 이것은 하나의 방법 일 뿐이며 배열 내용을 변경하는 방법은 많습니다. 좀 더 포괄적 인 것이 필요할 것입니다 ...
2. 사용자 지정 관찰 가능 배열 만들기
메서드를 재정의하는 대신 자신 만의 관찰 가능한 배열을 만들 수 있습니다. 이 특정 구현 복사본으로 배열 객체 어레이 형 맞춤 제공 새로운 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());
})();
참조 를 참조 하십시오 .Object.defineProperty()
그것은 우리를 더 가까워 지지만 여전히 방탄이 아닙니다 ...
3. 프록시
Proxies offer another solution... allowing you to intercept method calls, accessors, etc. Most importantly, you can do this without even providing an explicit property name... which would allow you to test for an arbitrary, index-based access/assignment. You can even intercept property deletion. Proxies would effectively allow you to inspect a change before deciding to allow it... in addition to handling the change after the fact.
Here's a stripped down sample:
(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, {
apply: function(target, thisArg, argumentsList) {
return thisArg[target].apply(this, argumentList);
},
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);
})();
From reading all the answers here, I have assembled a simplified solution that does not require any external libraries.
It also illustrates much better the general idea for the approach:
function processQ() {
// ... this will be called on each .push
}
var myEventsQ = [];
myEventsQ.push = function() { Array.prototype.push.apply(this, arguments); processQ();};
I found the following which seems to accomplish this: https://github.com/mennovanslooten/Observable-Arrays
Observable-Arrays extends underscore and can be used as follow: (from that page)
// For example, take any array:
var a = ['zero', 'one', 'two', 'trhee'];
// Add a generic observer function to that array:
_.observe(a, function() {
alert('something happened');
});
I used the following code to listen to changes to an array.
/* @arr array you want to listen to
@callback function that will be called on any change inside array
*/
function listenChangesinArray(arr,callback){
// Add more methods here if you want to listen to them
['pop','push','reverse','shift','unshift','splice','sort'].forEach((m)=>{
arr[m] = function(){
var res = Array.prototype[m].apply(arr, arguments); // call normal behaviour
callback.apply(arr, arguments); // finally call the callback supplied
return res;
}
});
}
Hope this was useful :)
The most upvoted Override push method solution by @canon has some side-effects that were inconvenient in my case:
It makes the push property descriptor different (
writable
andconfigurable
should be settrue
instead offalse
), which causes exceptions in a later point.It raises the event multiple times when
push()
is called once with multiple arguments (such asmyArray.push("a", "b")
), which in my case was unnecessary and bad for performance.
So this is the best solution I could find that fixes the previous issues and is in my opinion cleaner/simpler/easier to understand.
Object.defineProperty(myArray, "push", {
configurable: true,
enumerable: false,
writable: true, // Previous values based on Object.getOwnPropertyDescriptor(Array.prototype, "push")
value: function (...args)
{
let result = Array.prototype.push.apply(this, args); // Original push() implementation based on https://github.com/vuejs/vue/blob/f2b476d4f4f685d84b4957e6c805740597945cde/src/core/observer/array.js and https://github.com/vuejs/vue/blob/daed1e73557d57df244ad8d46c9afff7208c9a2d/src/core/util/lang.js
RaiseMyEvent();
return result; // Original push() implementation
}
});
Please see comments for my sources and for hints on how to implement the other mutating functions apart from push: 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'.
if (!Array.prototype.forEach)
{
Object.defineProperty(Array.prototype, 'forEach',
{
enumerable: false,
value: function(callback)
{
for(var index = 0; index != this.length; index++) { callback(this[index], index, this); }
}
});
}
if(Object.observe)
{
Object.defineProperty(Array.prototype, 'Observe',
{
set: function(callback)
{
Object.observe(this, function(changes)
{
changes.forEach(function(change)
{
if(change.type == 'update') { callback(); }
});
});
}
});
}
else
{
Object.defineProperties(Array.prototype,
{
onchange: { enumerable: false, writable: true, value: function() { } },
Observe:
{
set: function(callback)
{
Object.defineProperty(this, 'onchange', { enumerable: false, writable: true, value: callback });
}
}
});
var names = ['push', 'pop', 'reverse', 'shift', 'unshift'];
names.forEach(function(name)
{
if(!(name in Array.prototype)) { return; }
var pointer = Array.prototype[name];
Array.prototype[name] = function()
{
pointer.apply(this, arguments);
this.onchange();
}
});
}
var a = [1, 2, 3];
a.Observe = function() { console.log("Array changed!"); };
a.push(8);
An interesting collection library is https://github.com/mgesmundo/smart-collection. Allows you to watch arrays and add views to them as well. Not sure about the performance as I am testing it out myself. Will update this post soon.
Not sure if this covers absolutely everything, but I use something like this (especially when debugging) to detect when an array has an element added:
var array = [1,2,3,4];
array = new Proxy(array, {
set: function(target, key, value) {
if (Number.isInteger(Number(key)) || key === 'length') {
debugger; //or other code
}
target[key] = value;
return true;
}
});
I fiddled around and came up with this. The idea is that the object has all the Array.prototype methods defined, but executes them on a separate array object. This gives the ability to observe methods like shift(), pop() etc. Although some methods like concat() won't return the OArray object. Overloading those methods won't make the object observable if accessors are used. To achieve the latter, the accessors are defined for each index within given capacity.
Performance wise... OArray is around 10-25 times slower compared to the plain Array object. For the capasity in a range 1 - 100 the difference is 1x-3x.
class OArray {
constructor(capacity, observer) {
var Obj = {};
var Ref = []; // reference object to hold values and apply array methods
if (!observer) observer = function noop() {};
var propertyDescriptors = Object.getOwnPropertyDescriptors(Array.prototype);
Object.keys(propertyDescriptors).forEach(function(property) {
// the property will be binded to Obj, but applied on Ref!
var descriptor = propertyDescriptors[property];
var attributes = {
configurable: descriptor.configurable,
enumerable: descriptor.enumerable,
writable: descriptor.writable,
value: function() {
observer.call({});
return descriptor.value.apply(Ref, arguments);
}
};
// exception to length
if (property === 'length') {
delete attributes.value;
delete attributes.writable;
attributes.get = function() {
return Ref.length
};
attributes.set = function(length) {
Ref.length = length;
};
}
Object.defineProperty(Obj, property, attributes);
});
var indexerProperties = {};
for (var k = 0; k < capacity; k++) {
indexerProperties[k] = {
configurable: true,
get: (function() {
var _i = k;
return function() {
return Ref[_i];
}
})(),
set: (function() {
var _i = k;
return function(value) {
Ref[_i] = value;
observer.call({});
return true;
}
})()
};
}
Object.defineProperties(Obj, indexerProperties);
return Obj;
}
}
I wouldn't recommend you to extend native prototypes. Instead, you can use a library like new-list; https://github.com/azer/new-list
It creates a native JavaScript array and lets you subscribe to any change. It batches the updates and gives you the final diff;
List = require('new-list')
todo = List('Buy milk', 'Take shower')
todo.pop()
todo.push('Cook Dinner')
todo.splice(0, 1, 'Buy Milk And Bread')
todo.subscribe(function(update){ // or todo.subscribe.once
update.add
// => { 0: 'Buy Milk And Bread', 1: 'Cook Dinner' }
update.remove
// => [0, 1]
})
참고URL : https://stackoverflow.com/questions/5100376/how-to-watch-for-array-changes
'developer tip' 카테고리의 다른 글
What is the difference between session_unset() and session_destroy() in PHP? (0) | 2020.09.21 |
---|---|
Monitor vs lock (0) | 2020.09.21 |
HTML5 동영상 크기 (0) | 2020.09.20 |
요소 집합에서 최대 높이를 가진 요소 (0) | 2020.09.20 |
텍스트 상자에 레이블이 아닌 값을 적용하는 자동 완성 (0) | 2020.09.20 |