4

Language: Javascript
Operating System: Ubuntu 14.04
Browser: Tested in Chromium, Chrome, Firefox.

Background

I'm making a HTML5 Canvas game that has various collectable items. The superclass for these items is Collectable, which has a render() function:

Collectable.prototype.render = function(){
    // renders the collectables
}

There are different subclasses of Collectable, all of which delegate render() to the superclass. For example:

var Heart = function(x, y) {
    Collectable.call(this, x, y);
}
Heart.prototype = Object.create(Collectable.prototype);

I instantiate my collectables by first creating an array containing all of my collectables:

var collectables = [];
var numOfEach = 5;
// IIFE so as to not pollute global scope
(function() {
    for (var i = 0; i < numOfEach; i++) {
        // TODO: make this cleaner
        var heart = new Heart(100, 200);
        collectables.push(heart);
        var gemBlue = new GemBlue(100, 200);
        collectables.push(gemBlue);
        var gemOrange = new GemOrange(100, 200);
        collectables.push(gemOrange);
    }
})();

Then in my game loop file, I have a renderEntities() function that repeatedly calls individual render methods on my objects:

function renderEntities() {
    // render my other objects
    ...

    // render collectables
    collectables.forEach(function() {
        // test what 'this' is bound to
        console.log(this);

        // call render method
        this.render();
    });
}

...however in this case this is bound to window and therefore this.render is not a function.

Question

How do I bind this to each individual element of my collectables array?

My research

I've read the documentation for the bind function, but I'm afraid I can't work out how this would be applied with forEach() and my anonymous function.

I then found this existing question which explained how forEach takes an optional parameter to set this. After reading the documentation on forEach I tried the following with unsuccessful results:

 collectables.forEach(function() {
     // test what 'this' is bound to
     console.log(this);

     // call render method
     this.render();
 }, this);

...which obviously had the same effect of setting this to the global object.

 collectables.forEach(function() {
     // test what 'this' is bound to
     console.log(this);

     // call render method
     this.render();
 }, collectables);

...this got me a little closer, as this is bound to the collectables array as a whole, but obviously this.render is still not a function. I can't see what argument I can use here in forEach to bind this to each element of the array.

I have a feeling that after typing all this out the answer will be very simple. I'm relatively new to the concept of this so go easy on me!

I haven't found another question that answers this for me, or at least one that I'm able to understand given my level of knowledge. For example, I have considered the following questions:

Community
  • 1
  • 1
andydavies
  • 3,081
  • 4
  • 29
  • 35
  • try `collectables.forEach.call(collectables, callback)` – Daniel Lizik May 17 '16 at 17:09
  • 1
    Are you interested in using the `this` keyword specifically? It seems like your example would work if you included the array item as part of the `forEach` callback. E.g., `collectibles.forEach(function(collectible) { collectible.render(); });` – Himmel May 17 '16 at 17:10
  • Also, wouldn't each object passed to the `forEach` callback have access to the `render` method? In that case you don't even really need `this` in the callback. – Daniel Lizik May 17 '16 at 17:12
  • 1
    You can pass the this value to `forEach()` as a second parameter after the callback. `myArray.forEach((e,i,a) => this.render(e), this);` – Redu May 17 '16 at 17:16

2 Answers2

4

If I understand correctly, you are looking to get the object associated with each collectable item. In this case, simply pass an argument for the forEach loop. In this case, I've called that argument theObj but perhaps theCollectable or even just c would be more accurate.

collectables.forEach(function(theObj) {
    // test what 'this' is bound to
    console.log(theObj);

    // call render method
    theObj.render();
});

Update: the this you are passing in the forEach call is passing the current value of this to the loop. At that point, this is executed within the scope of window, and that's why this is window. You do not need to pass this if you do not wish to refer to the window, and since window is global you really have no need to pass this at all, so I've removed it from the suggested answer.

Update: the MDN is a great resource for JavaScript documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

Elly Post
  • 336
  • 3
  • 12
1

The .forEach() facility passes three arguments to your callback function:

  1. The current element of the array;
  2. The index into the array;
  3. The array itself.

Thus:

collectibles.forEach(function(collectible) {
  // the parameter "collectible" will be the
  // current array element
});

There's really no need to bind this, but if you absolutely wanted to you could:

collectibles.forEach(function callback(collectible) {
  if (this !== collectible)
    callback.call(collectible, collectible);
  else {
    // body of function
  }
});
Pointy
  • 405,095
  • 59
  • 585
  • 614