Javascript에서 푸시, 팝, 시프트 또는 인덱스 기반 할당을 사용하여 배열이 수정 될 때 알림을받는 방법이 있습니까? 내가 처리 할 수있는 이벤트를 발생시킬 무언가를 원합니다.
watch()
SpiderMonkey 의 기능에 대해 알고 있지만 전체 변수가 다른 것으로 설정된 경우에만 작동합니다.
Javascript에서 푸시, 팝, 시프트 또는 인덱스 기반 할당을 사용하여 배열이 수정 될 때 알림을받는 방법이 있습니까? 내가 처리 할 수있는 이벤트를 발생시킬 무언가를 원합니다.
watch()
SpiderMonkey 의 기능에 대해 알고 있지만 전체 변수가 다른 것으로 설정된 경우에만 작동합니다.
답변:
몇 가지 옵션이 있습니다 ...
빠르고 더러운 경로로 이동하면 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
.
이제 이것은 하나의 방법 일 뿐이며 배열 내용을 변경하는 방법은 많습니다. 좀 더 포괄적 인 것이 필요할 것입니다 ...
메서드를 재정의하는 대신 자신 만의 관찰 가능한 배열을 만들 수 있습니다. 이 특정 구현 복사본으로 배열 객체 어레이 형 맞춤 제공 새로운 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()
그것은 우리를 더 가까워 지지만 여전히 방탄이 아닙니다 ...
프록시 는 또 다른 솔루션을 제공합니다. 메서드 호출, 접근 자 등을 가로 챌 수 있습니다. 가장 중요한 것은 명시적인 속성 이름을 제공하지 않고도이를 수행 할 수 있다는 것입니다 ... 임의의 인덱스 기반 액세스를 테스트 할 수 있습니다. 할당. 속성 삭제를 가로 챌 수도 있습니다. 프록시를 사용하면 변경 을 허용 하기로 결정하기 전에 변경 사항을 효과적으로 검사 할 수 있으며 사실 이후의 변경 사항도 처리 할 수 있습니다.
다음은 제거 된 샘플입니다.
(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);
})();
set(index)
배열의 프로토 타입 및 antisanity 같은 것을 말한다 할
여기에서 모든 답변을 읽음으로써 외부 라이브러리가 필요하지 않은 단순화 된 솔루션을 모았습니다.
또한 접근 방식에 대한 일반적인 아이디어를 훨씬 더 잘 보여줍니다.
function processQ() {
// ... this will be called on each .push
}
var myEventsQ = [];
myEventsQ.push = function() { Array.prototype.push.apply(this, arguments); processQ();};
push
length
배열 의 를 반환합니다 . 따라서에서 반환 된 값 Array.prototype.push.apply
을 변수로 가져 와서 사용자 지정 push
함수 에서 반환 할 수 있습니다.
이 작업을 수행하는 것으로 보이는 다음을 발견했습니다. https://github.com/mennovanslooten/Observable-Arrays
Observable-Arrays는 밑줄을 확장하며 다음과 같이 사용할 수 있습니다. (해당 페이지에서)
// 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');
});
arr[2] = "foo"
되면 변경 알림이 비동기 적 입니다. JS는 이러한 변경 사항을 감시하는 방법을 제공하지 않기 때문에이 라이브러리는 250ms마다 실행되는 시간 제한에 의존하고 배열이 전혀 변경되었는지 확인하므로 다음 시간까지 변경 알림을받지 못합니다. 시간 제한이 실행됩니다. 그러나 다른 변경 사항은 push()
즉시 (동 기적으로) 알림을받습니다.
다음 코드를 사용하여 배열 변경 사항을 수신했습니다.
/* @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;
}
});
}
이것이 유용했기를 바랍니다. :)
@canon 의 가장 많이 찬성 된 Override push 메서드 솔루션에는 제 경우에는 불편한 몇 가지 부작용이 있습니다.
그것은 푸시 속성 기술자 다른한다 ( writable
및 configurable
설정해야합니다 true
대신에 false
) 나중에에서 예외가 발생한다.
push()
여러 인수 (예 :)로 한 번 호출되면 이벤트가 여러 번 발생 myArray.push("a", "b")
하는데, 제 경우에는 불필요하고 성능에 좋지 않았습니다.
따라서 이것은 이전 문제를 수정하고 제 생각에 더 깨끗하고 간단하며 이해하기 쉬운 최고의 솔루션입니다.
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
}
});
내 소스에 대한 주석과 푸시 외에 다른 돌연변이 기능을 구현하는 방법에 대한 힌트를 참조하십시오 : 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'.
...
구문 이 비슷 하고 arguments
키워드 사용으로 쉽게 대체 할 수있는 나머지 매개 변수입니다 .
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);
Object.observe()
하고 Array.observe()
사양에서 철수했다. Chrome에서 이미 지원이 제공되었습니다. : /
이것이 절대적으로 모든 것을 포함하는지 확실하지 않지만, 배열에 요소가 추가 된 경우를 감지하기 위해 다음과 같은 것을 사용합니다 (특히 디버깅 할 때).
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;
}
});
흥미로운 컬렉션 라이브러리는 https://github.com/mgesmundo/smart-collection 입니다. 배열을 관찰하고보기를 추가 할 수도 있습니다. 내가 직접 테스트하고 있기 때문에 성능이 확실하지 않습니다. 곧이 게시물을 업데이트 할 예정입니다.
나는 주위를 돌아 다니며 이것을 생각해 냈습니다. 아이디어는 객체에 모든 Array.prototype 메소드가 정의되어 있지만 별도의 배열 객체에서 실행된다는 것입니다. 이것은 shift (), pop () 등과 같은 메소드를 관찰 할 수있는 기능을 제공합니다. concat ()과 같은 일부 메소드는 OArray 객체를 반환하지 않습니다. 이러한 메서드를 오버로드해도 접근자가 사용되는 경우 개체를 관찰 할 수 없습니다. 후자를 달성하기 위해 주어진 용량 내에서 각 인덱스에 대해 접근자를 정의합니다.
성능면에서 ... OArray는 일반 Array 객체에 비해 약 10-25 배 느립니다. 1-100 범위의 용량에 대한 차이는 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;
}
}
네이티브 프로토 타입을 확장하지 않는 것이 좋습니다. 대신 new-list와 같은 라이브러리를 사용할 수 있습니다. https://github.com/azer/new-list
네이티브 JavaScript 배열을 생성하고 변경 사항을 구독 할 수 있습니다. 업데이트를 일괄 처리하고 최종 차이점을 제공합니다.
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]
})