0

Problem:

I have a very simple snippet of code. I create and object with a member variable "counter" and a member function "start()" which increases the counter variable by 1 every second. The problem here is that the counter variable does not increase when start() acts on it. This has been driving my head crazy.

Hypothesis:

I believe this is a problem with the "this" keyword or a scope/closure issue but I'm not sure what's exactly wrong.

Code:

var livePlayer = {
        counter : 0,
        liveIteration: null,

        start: function(){
            this.liveIteration = setInterval(this.incCounter, 1000);
        },

        incCounter: function(){
            this.counter++;
            alert(this.counter); <-- this should return 0, 1, 2, etc. but returns NAN instead
        }
    };

livePlayer.start();

JSFiddle:

http://jsfiddle.net/justinwong12337/1wjdr0dh/

Your help is greatly appreciated!

Additional info:

This object and its members are part of an AngularJS service and are to be used by a separate controller file.

justinSYDE
  • 307
  • 1
  • 2
  • 11

1 Answers1

3

The problem is neither with scope or closures. :-) It's with this, which is a slippery concept in JavaScript.

In JavaScript, this during a function call is set almost entirely by how the function is called, not where the function is defined. It's basically a special form of function argument. (This is quite different to its meaning in other languages with similar syntax.) The particular problem in your case is here:

this.liveIteration = setInterval(this.incCounter, 1000);

When the browser's timer code calls your incCounter, it will do so with this set to the global object, not to your object. So this.counter isn't the counter property on your object (because this doesn't refer to your object), and things don't work as expected.

You can solve this several ways, probably the most direct is ES5's Function#bind (which can be shimmed on older browsers):

this.liveIteration = setInterval(this.incCounter.bind(this), 1000);

Function#bind returns a function that, when called, calls the original function with this set to the first argument you give it.

More to explore (on my blog):

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thank you for your detailed and simple answer! The bind function works like magic! – justinSYDE Sep 19 '14 at 23:18
  • Another solution I stumbled upon was replacing any instance of "this" with the object name ("livePlayer") instead. So for instance, livePlayer.liveIteration = setInterval(livePlayer.incCounter, 1000) – justinSYDE Sep 19 '14 at 23:20
  • @justinSYDE: Yes, because your `livePlayer` object is a singleton, that's just fine as well. In fact, for a singleton, I'd say it's probably better. The above is for when you may have more than one object sharing the same code. – T.J. Crowder Sep 20 '14 at 07:12