Polyov
Polyov

Reputation: 2311

setTimeout scope issue

I have a setTimeout defined inside of a function that controls the player's respawn (i am creating a game):

var player = {
    ...
    death:(function() {
        this.alive = false;
        Console.log("death!");
        var timer3 = setTimeout((function() {
            this.alive = true;
            Console.log("alive!");
        }),3000);
    }),
    ...
}

When it executes, I read in the console, "death!" and 3 seconds later "alive!". However, alive is never really set back to true, because if i write player.alive in the console, it returns false. How come i can see "alive!" but the variable is never set back to true?

Upvotes: 36

Views: 48915

Answers (5)

zostay
zostay

Reputation: 3995

You have to be careful with this. You need to assign your this in the outer scope to a variable. The this keyword always refers to the this of the current scope, which changes any time you wrap something in function() { ... }.

var thing = this;
thing.alive = false;
Console.log("death!");
var timer3 = setTimeout((function() {
    thing.alive = true;
    Console.log("alive!");
}),3000);

This should give you better success.

Update 2019-10-09: The original answer is true, but another option is now available for recent versions of JavaScript. Instead of using function, you can use an arrow function instead, which does not modify this:

this.alive = false;
Console.log("death!");
var timer3 = setTimeout(() => {
    this.alive = true;
    Console.log("alive!");
}), 3000);

This is supported from ES6 forward, which is part of all current browsers but IE (of course), I think. If you are using a modern framework to build your project via Babel or whatever, the framework should make sure this works as expected everywhere.

Upvotes: 50

Samantha Guergenenov
Samantha Guergenenov

Reputation: 51

With ES6 function syntax, the scope for 'this' doesn't change inside setTimeout:

var timer3 = setTimeout((() => {
    this.alive = true;
    console.log("alive!");
}), 3000);

Upvotes: 5

James M
James M

Reputation: 16718

Probably because this isn't preserved in the timeout callback. Try:

var that = this;
...
var timer3 = setTimeout(function() {
    that.alive = true;
    ...

Update (2017) - or use a lambda function, which will implicitly capture this:

var timer3 = setTimeout(() => {
    this.alive = true;
    ...

Upvotes: 5

user1106925
user1106925

Reputation:

It's because this in the setTimeout handler is referring to window, which is presumably not the same value as referenced by this outside the handler.

You can cache the outer value, and use it inside...

var self = this;

var timer3 = setTimeout((function() {
    self.alive = true;
    Console.log("alive!");
}),3000);

...or you can use ES5 Function.prototype.bind...

var timer3 = setTimeout((function() {
    this.alive = true;
    Console.log("alive!");
}.bind(this)),3000);

...though if you're supporting legacy implementations, you'll need to add a shim to Function.prototype.


...or if you're working in an ES6 environment...

var timer3 = setTimeout(()=>{
    this.alive = true;
    Console.log("alive!");
},3000);

Because there's no binding of this in Arrow functions.

Upvotes: 25

Kokodoko
Kokodoko

Reputation: 28128

Just in case anyone reads this, the new javascript syntax allows you to bind a scope to a function with "bind":

window.setTimeout(this.doSomething.bind(this), 1000);

Upvotes: 10

Related Questions