9

Possible Duplicate:
Javascript - How to extend Array.prototype.push()?

How can I be notified (run a pre-defined function) of any change to a registered array (or at least any addition or removal of elements)? I tried using prototype. I don't want to be scolded for not providing SOME code examples of my own. So here's how I would like to use it.

var myArray = [];
myArray.bind(function() {
    console.log('wtf'); // Wed Thu Fri and what were you thinking?
});

I don't need overkill. I basically know the Array function scope that I will be using (push, pop, splice and maybe a couple others). It's a way to use backbone's MVC. I want to run logic on an array and THEN have the views highlighted accordingly. But the view is already attached to a collection. Any change to that collection re-renders the actual DOM's in the view. I don't want that. I simple want to add, or remove, a class to the corresponding DOM's in the view for CSS purposes.

Community
  • 1
  • 1
Adrian Bartholomew
  • 2,506
  • 6
  • 29
  • 37

3 Answers3

14

What I did is I made my own "array" type that just extended the prototype array, which then I added my own handlers to.

For example:

var MyArray = function() {
    var arr = [];
    arr.push = function() {
        console.log("PUSHING", arguments);
        return Array.prototype.push.apply(this, arguments);
    }

    return arr;
};

Usage:

var arr = new MyArray;
arr.push(12, 3, 45);
...

Fiddle: http://jsfiddle.net/maniator/vF659/

Naftali
  • 144,921
  • 39
  • 244
  • 303
  • Could you give me an example? I'm not being lazy, honest. My difficulty is that for e.g., 'push', I don't want to replace it. I want to run the callback at the end of it. – Adrian Bartholomew Dec 11 '12 at 18:18
  • 3
    I would recommend this approach, since overriding the main `push` method will definitely cause performance issues throughout the website. – Christian Dec 11 '12 at 18:19
  • So like this? http://jsfiddle.net/vF659/3/ – Adrian Bartholomew Dec 11 '12 at 18:37
  • @AdrianBartholomew although if you follow the "new" method as shown in my edit, you will be able to reuse `MyArray` with no issue by doing `new MyArray` – Naftali Dec 11 '12 at 18:39
  • It doesnt would using 'new'. Am I doing something wrong? http://jsfiddle.net/vF659/4/ – Adrian Bartholomew Dec 11 '12 at 18:47
  • @AdrianBartholomew you need to remove the `()` surrounding the function: http://jsfiddle.net/maniator/vF659/5/ – Naftali Dec 11 '12 at 18:54
  • One last thing. It does work when I remove ()(). But how come it works with both "new MyArray" and "new MyArray()". Is there a functional difference? – Adrian Bartholomew Dec 11 '12 at 19:10
  • +1 @AdrianBartholomew to accommodate your edit, you can certainly extend this snippet to loop through your functions (arr["push"],arr["pop"], etc.) – Christophe Dec 11 '12 at 19:11
  • @AdrianBartholomew they both just pass nothing to the constructor. they essentially are the same thing in this case ^_^ – Naftali Dec 11 '12 at 19:12
  • @Neil - I will. I'm trying to to get Christophe's loop to work with you simple design. – Adrian Bartholomew Dec 11 '12 at 21:46
2

You're looking for Object.observe, but it's not widely available yet. In Chrome Canary, with "Experimental JavaScript" enabled on about:flags you can try the following:

​var arr = [];

​Object.observe(arr, function(changes) {
    console.log("The array changed. Changes:", changes);
});
pimvdb
  • 151,816
  • 78
  • 307
  • 352
1

Something like this will set up global monitoring of array push()'s.

(function() {
  var _push = Array.prototype.push;
  Array.prototype.push = function() {
    console.log("push");
    return _push.apply(this, arguments);
  }
})();

Otherwise, as Neal suggested, you can create another class.

var MonitoredArray = function() {
  var rv = [];
  var _push = rv.push;
  rv.push = function() {
    console.log("push()");
    console.log(arguments);
    return _push.apply(this, arguments);
  }
  return rv;
}

To set up basic monitoring of N function calls at once.

var MonitoredArray = function() {
  var rv = [];

  // the names of the functions we want to log:
  var logged_fns = ["push", "pop"];

  for (var i in logged_fns) { (function() {
    var name = logged_fns[i]
    var fn = rv[name];

    rv[name] = function() {
      console.log(name + "()");
      console.log(arguments);
      return fn.apply(rv, arguments);
    }
  })()}

  return rv;
}

A similar adaptation should work for the first example too.

svidgen
  • 13,744
  • 4
  • 33
  • 58
  • Is there an efficient way to apply this to all of Array's functions? Or would I need to set it up for each one? – Adrian Bartholomew Dec 11 '12 at 18:23
  • @svidgen -- your `push` function restricts it to accept only **one** varable, which is no the same for the native push function. – Naftali Dec 11 '12 at 18:31
  • Added an example which performs multiple overrides at "once" with a variable number of arguments. – svidgen Dec 11 '12 at 18:33
  • @Neal, good point. I'll edit the first two examples to use apply() instead of call(). – svidgen Dec 11 '12 at 18:36
  • @AdrianBartholomew even if you apply it to all functions you're not covered, you won't detect a simple assignment array[array.length]=... – Christophe Dec 11 '12 at 18:42
  • This will fail because `_fn` is in the wrong scope. – pimvdb Dec 11 '12 at 18:52
  • @Christophe - I know, thanks for pointing that out but I don't need overkill. I basically know the Array function scope that I will be using. It's a way to use backbone's MVC. I want to run logic on an array and THEN have the views highlighted accordingly. But the view is already attached to a collection. Any change to that collection re-renders the actual DOM's. I don't want that. I simple want to add a class to corresponding DOM's for CSS purposes. TMI - maybe but you all took the time to help. – Adrian Bartholomew Dec 11 '12 at 18:53
  • @AdrianBartholomew this comment would be a nice addition to the original question (maybe you could even list the functions?). Also, understanding why you want to use an array and not an object for example might help. – Christophe Dec 11 '12 at 18:57
  • @Christophe - You're correct. – Adrian Bartholomew Dec 11 '12 at 19:02
  • @pimvdb, _fn only *appears* to be out of scope. But, it's not. I promise. – svidgen Dec 11 '12 at 19:20
  • Are you sure? `.push` does in fact do `.pop`: http://jsfiddle.net/VGu34/. JavaScript does not have block scope. – pimvdb Dec 11 '12 at 19:22
  • Well hmm. It doesn't seem to work. http://jsfiddle.net/LjL44/ – Adrian Bartholomew Dec 11 '12 at 19:22
  • Ah, I see there is a problem. It's not that _fn is out of scope though ... it's that I'm overwriting it. If I have a few minutes this afternoon, I'll fix it. The first two examples should work though. – svidgen Dec 11 '12 at 19:37
  • Curiosity got the best of me -- I took another look at it. The solution, ironically, was to add another layer of scope to prevent the referenced function (and name) from being overwritten. The solution above has been tested (and is working) in the Chrome console. – svidgen Dec 11 '12 at 19:44
  • Note, I also added the missing `return`s. – svidgen Dec 11 '12 at 19:45
  • Got it to work. It was a closure problem with your loop. – Adrian Bartholomew Dec 11 '12 at 22:53
  • Yeah. My latest code (as of about 3 hours ago) should be be functional. It was tested in the Chrome console. My apologies for posting before testing earlier! – svidgen Dec 11 '12 at 23:08