Matth Juarez
Matth Juarez

Reputation: 23

"This" becomes "window" JavaScript

I'm trying to develop a small game and I have the following problem: I have a pseudo class "Cannon", each Cannon has an array that stores the areas it should guard and an array that stores "intruders" that have entered one of those guarded areas. I created the next function as part of Cannon's prototype:

Cannon.prototype.checkIntruderLeftGuardedAreas = function(){
    debugger;
    this.intruders = this.intruders.filter(function(intruder){
        for(var i = 0, l = this.guardedAreas.length; i < l; i ++)
        {
            if(intruder.inInsideGuardedArea(this.guardedAreas[i]))
            {
                return true;
            }
        }
        return false;
    });
}

On the third line I'm trying to filter those intruders that have left the guarded areas, but "this" references to "window" instead of the Cannon objetc that invoked the function.

I also tried this code:

var intrudersArray = [];
    for(var i = 0, l = this.guardedAreas.length; i < l; i ++)
        {
            for(var j = 0, k = this.intruders.length; j < k; j++)
            {
               if(this.intruders[j].inInsideGuardedArea(this.guardedAreas[i]))
               {
                   intrudersArray.push(this.intruders[j]);
               }
            }

        }

        this.intruders = intrudersArray;

This, according to my logic, does the same thing than the previous block of code: filter the array of intruders. The problem is that on the 6th line where the "if" begins, suddenly "this" references to "window"!. Before reaching that if condition, "this" references to the Cannon object that called the function.

Upvotes: 2

Views: 498

Answers (2)

Stefan Steiger
Stefan Steiger

Reputation: 82306

When you call checkIntruderLeftGuardedAreas, bind the "this" object from the callee.

obj.checkIntruderLeftGuardedAreas.bind(this)(arg1, arg2, ..., argn);

In JavaScript, this is a context, not the current class.

Example: Define a function that outputs "this"

function lol(){console.log(this);}

Now call that function

lol();

and you get window.

Furthermore, define a function, and call it in an object:

var obj = {  abc: function(){lol(); }}

Then

obj.abc();

will still get you window.
You need

var obj = {  abc: function(){lol.bind(this)(); }}

And the this context will change to obj, when you call the function

obj.abc();

You'd probably have to do this:

Cannon.prototype.checkIntruderLeftGuardedAreas = function(){
    debugger;
    this.intruders = this.intruders.filter.bind(this)(function(intruder){
        for(var i = 0, l = this.guardedAreas.length; i < l; i ++)
        {
            if(intruder.inInsideGuardedArea(this.guardedAreas[i]))
            {
                return true;
            }
        }
        return false;
    });
}

Notice the "bind" after filter

Upvotes: 1

JSelser
JSelser

Reputation: 3630

Yep, scoping can get tricky, you could try the following:

Cannon.prototype.checkIntruderLeftGuardedAreas = function(){
    var self = this;

    self.intruders = self.intruders.filter(function(intruder){
        for(var i = 0, l = self.guardedAreas.length; i < l; i ++)
        {
            if(intruder.inInsideGuardedArea(self.guardedAreas[i]))
            {
                return true;
            }
        }
        return false;
    });
}

Notice the line var self = this;. It's purpose is saving a reference to your cannon instance to use inside nested callbacks (note how this is now self inside filter) in order to avoid scope issues, it's common to see such stuff in jQuery code.

This practice ensures you reference your cannon without the need to track what's going on with this, self like that is obvious at first glance.

Upvotes: 2

Related Questions