3

I'm new to JavaScript. Hence this problem is a bit confusing. I'm trying to simply define a counter and increment it in a class method but its not behaving as I expect it to. Specifically console.log(this.tick_count); prints undefined.

JavaScript:

function Game() {
    this.fps = 50;
    this.ticks = 3;
    this.current_time = (new Date).getTime();
    this.draw_object = document.getElementById('game_canvas').getContext('2d');
    this.tick_count = 0;
}

Game.prototype.update = function (time) {
    this.current_time = time;
}

Game.prototype.draw = function () {
    this.draw_object.fillRect(10, 10, 55, 50);
}

Game.prototype.run = function () {
    self.setInterval(this.tick, 1000 / (this.fps * this.tick));
}

Game.prototype.tick = function () {
    this.tick_count++;
    console.log(this.tick_count);
}

function start_game() {
    var game_object = new Game();
    game_object.run();
}

HTML:

<body onload="start_game()">
    <canvas id="game_canvas" width="1024" height="1024"></canvas>
</body>

Coming from a Python background I find this behavior strange. How should I set up my class variables correctly?

12
  • 2
    Where are you defining self? And this inside setInterval isn't what you think it is. Commented Sep 26, 2013 at 16:25
  • possible duplicate of Javascript setInterval scoping issue Commented Sep 26, 2013 at 16:26
  • 1
    Try self.setInterval(this.tick.bind(this), 1000 / (this.fps * this.tick)); and see how it goes Commented Sep 26, 2013 at 16:27
  • @PSL That will still not work due to the lack of self. It is probably just a type due to his Python backgrounds. Commented Sep 26, 2013 at 16:49
  • @jshthornton lack of self what? what is self by the way. Commented Sep 26, 2013 at 16:49

4 Answers 4

10

This is what is happening.

Essentially you tick function is no longer running in the context of your game_object object. This might sound odd coming from a Python background but basically the this object is set to something else.

So what is it set to? Easy, the window object, how do we know this? Because setInterval's context is the window object.

Moving example as code will not format correctly below

Bind Example

setInterval(this.tick.bind(this), 1000 / (this.fps * this.tick)); //Native (JS v1.8+)
$.proxy(this.tick, this); //jQuery
_.bind(this.tick, this); //underscore / lodash

Explicit context example

Game.prototype.run = function () {  
    var _this = this;  
    setInterval(function() {  
        //So what is this at the moment? window.
        //Luckily we have a reference to the old this.
        _this.tick();  
    }, 1000 / (this.fps * this.tick));  
 };

You can get around this two ways.

  1. Bind your function to the object you want it to be on Bind JS v1.8 (Seeing as you're using canvas that shouldn't be an issue.
  2. Invoke the method explicitly with its context. (See above)
Sign up to request clarification or add additional context in comments.

Comments

4

Try

setInterval(this.tick.bind(this), 1000 / (this.fps * this.tick));
// without "self"

Thanks to PSL and TJ Crowder

3 Comments

I think call is incorrect here, it will invoke the function and set the result as setInterval's calback. You should use bind.
Hmm.. not sure, i test
No, setTimeout(this.tick.call(this), ... will call tick immediately, and pass the return value into setTimeout.
2

This will work:

setInterval(this.tick.bind(this), 1000 / (this.fps * this.tick));

As will this:

var self = this;
setInterval(function () {
    self.tick();
}, 1000 / (this.fps * this.tick));

Comments

0

Even though this has been answered I think you need to understand what this refers to. See this answer for more details.

If you would like to use closures instead of bind you can limit the scope by calling a function that returns a function (outside of the currently running function). This is so you can minimise the amount of variables that will be available to the closure. Sounds complicated but with a minor adjustment to your code you can do it:

Game.prototype.run = function () {
    //best not to define a closure here because the next variable
    //will be available to it and won't go out of scope when run
    //is finished
    var memoryEatingVar=new Array(1000000).join("hello world");;
    //note that this.tick(this) is not passing this.tick, it's invoking
    //it and the return value of this.tick is used as the callback
    //for the interval
    setInterval(this.tick(this), 1000 / (this.fps * this.tick));
}
//note the "me" variable, because the outer function Game.prototype.tick
//returns a inner function (=closure) the me variable is available in 
//the inner function even after the outer function is finished
Game.prototype.tick = function (me) {//function returning a function
  return function(){
    me.tick_count++;
    console.log(me.tick_count);
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.