The problem is that this
in functions is defined dynamically. That means that what determines the value of this
is how the function is called. If a function is called by itself and not as a method of another object, it uses the default this
binding which will give you the global object (window
in browsers) or undefined if running in strict mode
// in browser
myFunction() // this = window
If called from an object, a function is seen as a method and uses that object as its this
someObj.myFunction() // this = someObj
When you call functions from an existing this
, that same this is used in the function because that’s the object its called from
this.myFunction() // this = this
When working with classes this is normally how you maintain the correct this throughout your method calls from object instances.
function Game () {}
Game.prototype.restart = function () {
this.clearLocalStorage();
}
Game.prototype.clearLocalStorage = function () {
// do stuff
}
var game = new Game();
game.restart(); // this = game
// in restart: this.clearLocalStorage() // this = game
// in clearLocalStorage: // this = game...
When you have a function that is not called from an object, for example your instance or this
it loses that association and gets the default this
binding.
Game.prototype.restart = function () {
this.clearLocalStorage();
var temp = function() {
this.clearBoard(); // this = window (see below)
};
temp(); // not called from object, this = window
};
And this is the same thing that happens when you pass an inline function into setTimeout
. You’re just passing a function and when it gets called internally by setTimeout
, it doesn’t get called from an object such as your game instance, it gets called just like temp
is called above
Game.prototype.restart = function () {
this.clearLocalStorage(); // this = game instance
this.timer = setTimeout(function() { // not called from this
this.clearBoard(); // this = window
}, 0);
};
To fix this, you can use arrow functions. Arrow functions don’t follow the same rules for this
binding. Instead, they always pull the value of this
from the parent scope.
Game.prototype.restart = function () {
this.clearLocalStorage(); // this = game instance
this.timer = setTimeout(() => { // uses value of this above ^
this.clearBoard(); // this = game instance
}, 0);
};
If sticking to the older ES5 syntax, you can use bind()
to force your function to always be called with a this
value of your choosing, no matter how its called.
Game.prototype.restart = function () {
this.clearLocalStorage(); // this = game instance
this.timer = setTimeout(function() { // this = game instance thanks to bind
this.clearBoard(); // this = game instance
}.bind(this), 0); // bind forces function this = this (game instance)
};
In both cases, given that the timeout function is not being called off the instance, and instead being called like temp()
, using either arrow functions or bind()
lets us override that dynamic this
behavior and force the function to be called with a specific this
value.