Lasse Karagiannis
Lasse Karagiannis

Reputation: 481

this refers to global object despite using .bind(this)

I am confused regarding how to work with this in combination with bind. I know the problem stems from this being the global object. Could somebody explain how I could circumvent?

In the constructor I have done this:

var BoundedStartTimer = this.StartTimer.bind(this);
var BoundedStopTimer = this.StopTimer.bind(this);
var BoundedClearTimeString = this.ClearTimeString.bind(this);
BoundedClearTimeString();

The last one works, but the timer does not start when I call BoundedStartTimer();.

I am not sure what I am doing. Below are my declarations:

MemoryGame.prototype.StartTimer = function(){
  var playTimeInMilliseconds = 0;
  this.timeString = "";
  this.timer = window.setInterval(function(){
    if(playTimeInMilliseconds >= 1000)
      this.timeString = .....
    this.handleToTimerText.textContent = this.timeString;
  }, 10);
}

MemoryGame.prototype.StopTimer = function(){
  clearInterval(this.timer);
}

MemoryGame.prototype.ClearTimeString = function(){
  this.handleToTimerText.textContent = "00:000";
}

Upvotes: 4

Views: 304

Answers (2)

Travis J
Travis J

Reputation: 82267

The issue here is with your setInterval call. setInterval executes functions in the global scope, meaning that this === window which is not ideal. You were right that bind can work here though, just bind the interval callback to this:

window.setInterval(function(){
  if(playTimeInMilliseconds >= 1000)
    this.timeString = .....
  this.handleToTimerText.textContent = this.timeString;
}.bind(this), 10);
//     ^ bind this from your MemoryGame instance to the interval callback

Upvotes: 3

CR Drost
CR Drost

Reputation: 9807

So here's the deal.

Every time you see the keyword function, you should think two things.

First is, "oh, this creates a new scope for variables!" Lots of people from Java think this happens with each curly brace { that is not used as an object literal; that's false, it's only a function context (or, now with ES6, there is let and => that do it too.)

The second thing you should think is, "oh, probably this and arguments are going to be different in the new variable scope." That's because these two keywords are especially "magical" in JavaScript.

So when you write:

this.timer = window.setInterval(function () {
    if (playTimeInMilliseconds >= 1000) {
        this.timeString = .....
    }
    this.handleToTimerText.textContent = this.timeString;
}, 10);

...you have to see that function as a new variable scope with its own this and arguments.

Now, playTimeInMilliseconds is going to be fine if you don't at any point in this function write var playTimeInMilliseconds, which will get "hoisted" to the top of the function declaration and declare a new variable local to that scope. As long as you never do this, playTimeInMilliseconds will peek at the parent variable scope and find the variable you defined in the outer scope.

However, this.timeString and this.handleToTimerText are not fine because the value of this was declared to be window.

There are two remedial approaches:

  1. In the outer function, capture this into your own variable. Common variable names for this purpose are are me and self. Just write var self = this; in the outer function, then do not write var self in the inner function, then write self.timeString and self.handleToTimerText.

  2. Take the function and explicitly .bind() it. So pull out the function like so:

    function updateTimerText() {
        if (playTimeInMilliseconds >= 1000) {
            this.timeString = .....
        }
        this.handleToTimerText.textContent = this.timeString;
    }
    this.timer = setInterval(updateTimerText.bind(this), 10);
    

Obviously, bind has a lot of power! So don't do crap like this:

var BoundedStartTimer = this.StartTimer.bind(this);
var BoundedStopTimer = this.StopTimer.bind(this);
var BoundedClearTimeString = this.ClearTimeString.bind(this);
BoundedClearTimeString();

I have no idea what scope this stuff is occurring in, but it's probably not in the right scope. Because whatever you're binding as this has to be the instance of new MemoryGame() that you created -- maybe let's say you called it var myMemoryGame = new MemoryGame(); or so; just use myMemoryGame.startTimer() (or whatever) and let the this come naturally. Whenever you write a.b.c.d.e() the this in the context of function e() is magically set to a.b.c.d. Don't reassign it to this unless you know what the heck you're doing and that you have to preserve this in some new context. At the very least be nice to readers and .bind(myMemoryGame) so that it's 100% clear.

Upvotes: 2

Related Questions