将事件监听器附加到数组以捕获push()事件

35
有没有办法知道用户何时通过 push() 方法将项目推入数组中?
基本上,我有一个异步脚本,允许用户将命令推入数组,一旦我的脚本加载,就会执行这些命令。问题是,用户可能在我的脚本已经运行后继续向数组中推入其他命令,我需要被通知这种情况。请记住,这只是用户自己创建的普通数组。Google Analytics 做了类似的事情。
我也找到了这个代码片段,这是我认为 Google 使用的代码,但我不太理解它:
    Aa = function (k) {
        return Object.prototype[ha].call(Object(k)) == "[object Array]"

我还找到了一个很好的例子,似乎涵盖了所有基础知识,但我无法让我添加的push方法正常工作:http://jsbin.com/ixovi4/4/edit


用户是通过您提供的UI创建这个数组的吗? - joelt
不行,它必须是一个常规数组,因为我的脚本还没有加载,所以我没有任何特殊的包装对象可以创建等。 - KingOfHypocrites
查看 Array.observe() 方法。 - Igor Fomenko
12个回答

38
你可以使用一个“eventify”函数,它覆盖了传递数组中的push方法。
var eventify = function(arr, callback) {
    arr.push = function(e) {
        Array.prototype.push.call(arr, e);
        callback(arr);
    };
};
在下面的例子中,应该会触发3个警报,因为这是在调用`eventify`之后事件处理程序(回调函数)所执行的操作。
var testArr = [1, 2];

testArr.push(3);

eventify(testArr, function(updatedArr) {
  alert(updatedArr.length);
});

testArr.push(4);
testArr.push(5);
testArr.push(6);

4
在提供的语境中,这个答案是完美的回答。 - Pandurang Patil
3
可以更通用一些:eventify = function(arr, action, callback) { arr[action] = function(e) { Array.prototype[action].call(arr, e); callback(arr); }; }; let test = []; eventify(test,'push',(arr)=>{ console.log(\`执行${action}操作后,数组长度为: ${arr.length}\`); }) test.push(1); test.push(2); test.push(3); - Nicholas Hamilton

22

唯一明智的方法是编写一个包装数组的类:

function EventedArray(handler) {
   this.stack = [];
   this.mutationHandler = handler || function() {};
   this.setHandler = function(f) {
      this.mutationHandler = f;
   };
   this.callHandler = function() { 
      if(typeof this.mutationHandler === 'function') {
         this.mutationHandler();
      }
   };
   this.push = function(obj) {
      this.stack.push(obj);
      this.callHandler();
   };
   this.pop = function() {
      this.callHandler();
      return this.stack.pop();
   };
   this.getArray = function() {
      return this.stack;
   }
}

var handler = function() {
   console.log('something changed');
};

var arr = new EventedArray(handler);

//or 

var arr = new EventedArray();
arr.setHandler(handler);


arr.push('something interesting'); //logs 'something changed'

我还发现了这个,我认为这就是谷歌的做法,但我不太理解代码:Aa = function (k) { return Object.prototype[ha].call(Object(k)) == "[object Array]" - KingOfHypocrites
如果我有一种方法可以在加载脚本后将常规数组转换为我的特殊对象类型,然后附加特殊事件处理程序。 - KingOfHypocrites
我使用了您提供的代码创建了一个示例,但是一旦我将项目推到数组中,该数组仍然没有任何项目:http://jsbin.com/ilehu5/edit>> 我还尝试了一个交换对象的示例,但第一个问题使我无法完全测试它:http://jsbin.com/isiti3/2/edit >> 非常感谢您的帮助。 - KingOfHypocrites
1
@KingOfHypocrites 更新了我的答案以定义getArray()方法。使用此方法访问底层数组。这是您的示例,已修改为与此方法一起使用:http://jsbin.com/ilehu5/3/edit - Jacob Relkin
再次感谢你,Jacob。最后一个问题...我找到了一个类似于你的示例,将构造函数指向“Stack”数组,以便您可以直接访问数组而无需使用方法或调用.Stack。这很有效,直到我尝试覆盖push方法...在push方法内部,我无法调用this.push()。http://jsbin.com/ilehu5/7/edit >>你能否帮我一个忙,看看你的想法?非常感谢。 - KingOfHypocrites
显示剩余2条评论

15

试一下这个:

var MyArray = function() { };
MyArray.prototype = Array.prototype;
MyArray.prototype.push = function() {
    console.log('push now!');
    for(var i = 0; i < arguments.length; i++ ) {
        Array.prototype.push.call(this, arguments[i]);
    }
};

var arr = new MyArray();
arr.push(2,3,'test',1);

你可以在push之后或push之前添加函数


4
我会把原始数组包装成一个简单的观察器接口,就像这样。
function EventedList(list){
    this.listbase = list;
    this.events = [];
}

EventedList.prototype.on = function(name, callback){
    this.events.push({
        name:name,
        callback:callback
    });
}

//push to listbase and emit added event
EventedList.prototype.push = function(item){
    this.listbase.push(item);
    this._emit("added", item)
}

EventedList.prototype._emit = function(evtName, data){
    this.events.forEach(function(event){
        if(evtName === event.name){
            event.callback.call(null, data, this.listbase);
        }
    }.bind(this));
}

那么我会用一个基础数组来实例化它

    //returns an object interface that lets you observe the array
    var evtList = new EventedList([]);

    //attach a listener to the added event
    evtList.on('added', function(item, list){
         console.log("added event called: item = "+ item +", baseArray = "+ list);
    })

    evtList.push(1) //added event called: item = 1, baseArray = 1
    evtList.push(2) //added event called: item = 2, baseArray = 1,2
    evtList.push(3) //added event called: item = 3, baseArray = 1,2,3

您还可以扩展观察者以观察其他事物,例如prePush或postPush或任何您希望在与内部基础数组交互时发出的事件。


3
为什么不直接这样做呢?
Array.prototype.eventPush = function(item, callback) {
  this.push(item);
  callback(this);
}

然后定义一个处理程序。
handler = function(array) {
    console.log(array.length)
}

然后在您想要特定事件发生的位置使用eventPush,传递处理程序,如下所示:
a = []
a.eventPush(1, handler);
a.eventPush(2, handler);

1
这将会给所有数组添加一个名为onPush的函数,默认情况下它不会执行任何操作,因此不会干扰普通数组的正常功能。
只需在单个数组上覆盖onPush即可。
Array.prototype.oldPush = Array.prototype.push;
Array.prototype.push = function(obj){
    this.onPush(obj);
    this.oldPush(obj);
};
//Override this method, be default this shouldnt do anything. As an example it will.
Array.prototype.onPush = function(obj){
    alert(obj + 'got pushed');
};

//Example
var someArray = [];

//Overriding
someArray.onPush = function(obj){
    alert('somearray now has a ' + obj + ' in it');
};

//Testing
someArray.push('swag');

这段文本的英译是:

这将会提示 'somearray现在已经包含了一个swag'


1
如果您想在单个数组上执行此操作:
var a = [];

a.push = function(item) {
    Array.prototype.push.call(this, item);
    this.onPush(item);
};

a.onPush = function(obj) {
    // Do your stuff here (ex: alert(this.length);)
};

1
有时在回调函数可用之前需要排队。这就解决了这个问题。将任何项目推送到数组中。一旦您想要开始使用这些项目,请将数组和回调传递给QueuedCallback()QueuedCallback会重载array.push作为您的回调,然后循环遍历任何排队的项目。继续将项目推送到该数组中,它们将直接转发到您的回调函数。数组将保持为空。
与所有浏览器和IE 5.5+兼容。
var QueuedCallback = function(arr, callback) {
  arr.push = callback;
  while (arr.length) callback(arr.shift());
};

示例用法 这里


0

虽然未经测试,但我认为类似这样的代码可能有效:

Array.prototype.push = function(e) {
    this.push(e);
    callbackFunction(e);
}

实际上可能不是这样,我正在尝试扩展内置的推送功能。 - moe
这很接近我所需的,但我想知道是否有一种方法只在一个特定的数组上执行此操作,或者我是否可以以某种方式知道数组的ID?我让它有点起作用了,但不确定如何确保它是我需要实际查看的那个数组: http://jsbin.com/axate5/edit - KingOfHypocrites
我也找到了这个,据我所知这就是 Google 的做法,但我并不太明白这段代码:Aa = function (k) { return Object.prototype[ha].call(Object(k)) == "[object Array]" - KingOfHypocrites
这个可行,我在下面的回答中建议了类似的东西[https://dev59.com/D2435IYBdhLWcg3woRnG#74764472]。 - Vinod Srivastav

0

为了调试目的,您可以尝试并从调用堆栈中跟踪调用函数。

yourArray.push = function(){debugger;}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接