Javascript 如何监视阵列更改?

Javascript 如何监视阵列更改?,javascript,dom-events,Javascript,Dom Events,在Javascript中,是否有一种方法可以在使用push、pop、shift或基于索引的赋值修改数组时得到通知?我想要一些能引发我能处理的事件的东西 我知道SpiderMonkey中的watch()功能,但只有当整个变量设置为其他变量时,它才起作用。有几个选项 1.覆盖推送方法 按照快速而肮脏的路线,您可以覆盖阵列1的“push()”方法: 我发现以下几点似乎可以做到这一点: 可观察数组扩展了下划线,可按如下方式使用: (摘自该页) 我不建议您扩展本机原型。相反,您可以使用类似于新列表的库

在Javascript中,是否有一种方法可以在使用push、pop、shift或基于索引的赋值修改数组时得到通知?我想要一些能引发我能处理的事件的东西


我知道SpiderMonkey中的
watch()
功能,但只有当整个变量设置为其他变量时,它才起作用。

有几个选项

1.覆盖推送方法 按照快速而肮脏的路线,您可以覆盖阵列1的“push()”方法:
我发现以下几点似乎可以做到这一点:

可观察数组扩展了下划线,可按如下方式使用: (摘自该页)


我不建议您扩展本机原型。相反,您可以使用类似于新列表的库

它创建一个本地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]

})

这里有一个有趣的藏书室。允许您查看阵列并向其添加视图。不确定性能,因为我自己正在测试。很快就会更新这篇文章。

通过阅读这里的所有答案,我组装了一个简化的解决方案,不需要任何外部库

它还更好地说明了该方法的总体思路:

function processQ() {
   // ... this will be called on each .push
}

var myEventsQ = [];
myEventsQ.push = function() { Array.prototype.push.apply(this, arguments);  processQ();};

我使用以下代码来侦听对数组的更改

/* @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;
                 }
    });
}

希望这是有用的:)

不确定这是否涵盖了所有内容,但我使用类似的方法(特别是在调试时)来检测数组何时添加了元素:

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;
    }
});

我胡思乱想,想出了这个。其思想是该对象定义了所有Array.prototype方法,但在单独的Array对象上执行它们。这使我们能够观察shift()、pop()等方法,尽管有些方法(如concat()等)不会返回OArray对象。如果使用访问器,重载这些方法将不会使对象可见。为了实现后者,将为给定容量内的每个索引定义访问器

性能方面。。。与普通阵列对象相比,OArray大约慢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;
    }
}
classoarray{
建造师(能力、观察员){
var Obj={};
var Ref=[];//引用对象以保存值并应用数组方法
如果(!observer)observer=函数noop(){};
var propertyDescriptors=Object.getOwnPropertyDescriptors(Array.prototype);
key(propertyDescriptors).forEach(函数(属性){
//该属性将绑定到Obj,但应用于Ref!
变量描述符=propertyDescriptors[属性];
变量属性={
可配置:descriptor.configurable,
可枚举:描述符。可枚举,
可写:descriptor.writable,
值:函数(){
观察员呼叫({});
返回描述符.value.apply(Ref,参数);
}
};
//长度例外
如果(属性=='length'){
删除属性.value;
删除属性。可写;
attributes.get=函数(){
返回参考长度
};
attributes.set=函数(长度){
参考长度=长度;
};
}
Object.defineProperty(对象、属性、属性);
});
变量索引器属性={};
对于(var k=0;k<容量;k++){
索引器属性[k]={
对,,
get:(函数(){
var_i=k;
返回函数(){
返回Ref[_i];
}
})(),
set:(函数(){
var_i=k;
返回函数(值){
Ref[_i]=值;
观察员呼叫({});
返回true;
}
})()
};
}
Object.defineProperties(Obj,索引器属性);
返回Obj;
}
}

最受欢迎的覆盖推送方法由@canon提供的解决方案有一些副作用,这些副作用对我来说是不方便的:

  • 它使推送属性描述符不同(
    writable
    configurable
    应设置为
    true
    ,而不是
    false
    ),这会在以后导致异常

  • 当使用多个参数(例如
    myArray.push(“a”、“b”)
    )调用一次
    push()
    时,它会多次引发事件,在我的情况下,这是不必要的,对性能有害

因此,这是我能找到的最好的解决方案,可以修复以前的问题,而且在我看来更清晰/更简单/更容易理解

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
    }
});

有关如何实现push之外的其他变异函数的提示,请参阅我的源代码注释:“pop”、“shift”、“unshift”、“splice”、“sort”、“reverse”。

谢谢!这适用于正则数组方法。关于如何为“arr[2]=“foo”之类的内容引发事件,您有什么想法吗?我想您可以实现一个方法
set(index)
在数组的原型中,并执行类似于antisanity saysIt的操作。修改数组的子类会更好。修改数组的原型通常不是一个好主意。这里的答案非常好。Observatearray的类非常好。+1“'\u Array.length==0&&delete\u self[index];“-你能解释这一行吗?这很好,但有一个重要的警告:当数组被修改为
arr[2]=”foo“
,更改通知是异步的。由于JS不提供任何方法来监视此类更改,因此该库依赖于每250毫秒运行一次的超时,并检查阵列是否发生了任何更改——因此您不会收到更改通知
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;
    }
});
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;
    }
}
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
    }
});