1

Normally I don't have too much trouble figuring out a problem in JS but this time I really need some help understanding this block of code. Mary Rose Cook used this logic in her space invaders game to filter through the bodies array to find collisions with other bodies.

var bodies = [];
...
update: function () {
// bodies is an array of all bodies in the game
var bodies = this.bodies;
var notCollidingWithAnything = function (b1) {
    return bodies.filter(function(b2) { return colliding(b1, b2); }).length === 0;
};

this.bodies = this.bodies.filter(notCollidingWithAnything)

// ...insert function to draw the bodies that are in the new bodies array...

}

Can someone please explain how this.bodies.filter(notCollidingWIthAnything) works without passing in any parameters to the argument function? How does the the compiler know to check each element of the array against each other element of the array? Please guide me through what exactly happens in the compiler so that I can understand this.

Streamer
  • 173
  • 1
  • 1
  • 11
  • This is checking each item of the `bodies` array with each other (including by temselves) by invoking a function as `colliding(b1,b2)` Normally i would expect the items when checked by themselves to collide and return `true` hence i would expect the resulting filtered array to have the same length with bodies but god knows what's happening in the colliding function. It may tell perfectly overlapping bodies as the same and might return `false` (a body can not collide by itself) – Redu Jun 19 '16 at 10:19
  • Right, the colliding() function makes sure to account for whether or not the objects are equal to themselves first, and then all of the other positional stuff. That is why the test is whether or not the length is equal to 0, instead of 1. I didn't include that part, though. I was more interested in how exactly filter() works as a function. An additional layer of complexity is added if another filter() is thrown into the mix, but once dissected it isn't too difficult to understand I find. – Streamer Jun 19 '16 at 10:40
  • I had invented a generic Array method to apply a callback within each items of two arrays. `Array.prototype.withEachOther()` it might as well do this job. See the working of it explained at http://stackoverflow.com/a/37745094/4543207. – Redu Jun 19 '16 at 10:54

1 Answers1

3

Can someone please explain how this.bodies.filter(notCollidingWIthAnything) works without passing in any parameters to the argument function? How does the the compiler know to check each element of the array against each other element of the array?

The compiler (well, the JavaScript engine) doesn't know how to call notCollidingWIthAnything with the elements; Array#filter does.

notCollidingWIthAnything is a reference to the function. (Functions are proper objects in JavaScript, so we have references to them just like we have references to other objects.) The code passes that reference into Array#filter, and then Array#filter calls that function once for each element in the array, passing in the element value (and index, and array; it passes three args although we usually only use the first). Then it uses the return value of the callback to decide whether to include the element in the new array it builds.

Here's simplified code for Array#filter so you can see what's going on:

function arrayFilter(callback) {
    // Remember this is called with `this` referring to an array-like object

    // Create a new, empty array for the result
    var result = [];

    // Loop through the items
    for (var index = 0; index < this.length; ++index) {
        // Get the value for this entry
        var value = this[index];

        // Call the callback
        if (callback(value, index, this)) {
            // Got a truthy return value, include the value in the result
            result.push(value);
        }
    }

    // Return the new array
    return result;
}

Again, that's simplified, not perfectly correct; for the perfectly correct steps, see the algorithm in the spec.

Here's an example with logging showing exactly who's doing what:

function arrayFilter(callback) {
  console.log("Starting arrayFilter");
  var result = [];
  for (var index = 0; index < this.length; ++index) {
    var value = this[index];
    console.log("arrayFilter calling callback with value " + value);
    if (callback(value, index, this)) {
      console.log("arrayFilter got truthy result, include the value");
      result.push(value);
    } else {
      console.log("arrayFilter got falsy result, don't include the value");
    }
  }
  console.log("arrayFilter done");
  return result;
}

function isOdd(value) {
  var retval = value % 2 == 1;
  console.log("isOdd called with " + value + ", returning " + retval);
  return retval;
}

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log("calling `arrayFilter` with `a` as `this`, using `isOdd` callback");
var odds = arrayFilter.call(a, isOdd);

console.log("Resulting array: ", odds);
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Alright, I just had one of those "light-bulb moments" and I think that I finally understand this. So, bodies.filter is called first, taking notCollidingWithAnything() as a parameter. This function already has a reference to that particular array index when it is defined (b1). So, when it's called, it runs filter again, this time with a reference to b2. The first body is checked against b2[0] (which is just bodies) then b2[1], b2[2] and so on, returning a boolean. So if it's false, that body isn't colliding with any other bodies. Then, it does the same with b1 until completion. Did I get it?! – Streamer Jun 19 '16 at 10:19
  • @Azurasky: Perfect! – T.J. Crowder Jun 19 '16 at 10:27