1

In the code below I create an object called Slates and add some functions to it.

"use strict";

function Slates() {
    this.canvas;
    this.ctx;
    this.width;
    this.height;
}

Slates.prototype.init = function() {
    this.canvas = document.getElementById("canvas");
    this.ctx = this.canvas.getContext("2d");
    this.resize();
};

Slates.prototype.resize = function() {
    this.width = window.innerWidth;
    this.height = window.innerHeight;
    this.canvas.width = this.width;
    this.canvas.height = this.height;
    console.log("resizing");
};

Slates.prototype.render = function() {
    this.ctx.clearRect(0, 0, this.width, this.height);
    this.ctx.fillStyle = "rgba(0,0,100,.5)";
    this.ctx.fillRect(0, 0, this.width, this.height);
};

window.onload = function() {
    var slates = new Slates();
    slates.init();
    window.onresize = slates.resize;
    window.requestAnimationFrame(slates.render);
};

The problem is that when I pass the function slates.render as a callback into a function such as window.requestAnimationFrame the this keyword refers to the window object rather than the Slates instance, and therefore I get a

Uncaught TypeError: Cannot read property 'clearRect' of undefined

How do I fix this so that it refers to the instance of my object instead? Here is a link to a codepen.

Vivek
  • 307
  • 1
  • 6

2 Answers2

5

You can use, for example, Function.prototype.bind, which will return a function but with constant this value:

window.requestAnimationFrame(slates.render.bind(slates))

or wrap this invocation into another function:

window.requestAnimationFrame(function() { slates.render() })
Kuba Jagoda
  • 3,210
  • 2
  • 17
  • 20
4

L-values and r-values

In programming, there are two kinds of expressions: l-values ("l" for left) and r-values ("r" for right). An l-value is something that can be assigned to while an r-value is something that we can assign to something else. For example, say we had the following HTML

<div id="my-div" style="background-color: blue;"></div>

and the following JS:

var myDiv = document.getElementById("my-div");

Then we could use myDiv.style.backgroundColor as an l-value (i.e. assign something to it)

myDiv.style.backgroundColor = "black"; // myDiv.style.backgroundColor is an l-value

or as an r-value (assign it / pass it to something else):

var newDiv = document.createElement("div");
newDiv.style.backgroundColor = myDiv.style.backgroundColor // now both are "black"

When we have a "dot chain" like in the above myDiv.style.backgroundColor, its value in an expression is equal to the value of the last link in the chain. So when we use myDiv.style.backgroundColor in an expression, we are simply using the backgroundColor. Assigning this to a new element, say newDiv, does not create any sort of link between myDiv.style and newDiv.style.

Likewise, when you assign slates.render to window.onload, you're assigning the render function only - there is no longer any connection to slates.

You might wonder though... okay, but shouldn't this be a link back to slates? Well, think about it, you never actually assigned anything to this in your render function, right? You just used it. The language engine supplies the value of this for you. Ordinary variables/properties in JS, e.g. slates or slates.render can be either l-values or r-values, depending on the context. However, the this keyword is only valid as an r-value. If we try to assign directly to the this keyword, we will see an error like this:

enter image description here

The error says what we already know -- that this is not a valid l-value. So where does it get its value from?

This brings us to a discussion of scope in JS and why this is different from other variables in an important way.

Lexical scope (how variables work)

In JS, variables are lexically scoped, meaning that the value of a variable in a function call is determined not by the scope of the call, but rather by the scope of the function's definition.

var x = {name: "outer"};
function y() {
    var x = {name: "inner"};
    return function() {
        console.log(x.name);
    };
}

var z = y(); // z is a function here
z(); // logs "inner", not "outer" because JS is lexically scoped

In this example, we define a function y which itself returns a function which gets assigned to z. When z executes, it does console.log(x.name), but the question is... which x should it refer to? If JS were dynamically scoped, then we might expect it to print "outer", since this is the x defined at the same "scope" as the variable z. But it doesn't. It prints "inner" because JS is lexically scoped, meaning that x is equal to the x which was in scope at the time of definition.

Dynamic Scope (how the this keyword works)

If the this keyword was lexically scoped like other variables in JS, the code in your post would have worked. But it's not. While normal variables in JS are lexically scoped, the this keyword is not lexically scoped. It has dynamic scope. Meaning that its value depends not on the context in which your function is defined, but rather the context in which your function executes. What context did you execute it in? The window object! If you imagine the load event being called like window.onload() then this becomes more clear.

In fact, this is the behavior we want. Because if you had set a click event on a button, then you might do something like:

var inputBox = document.getElementById("my-input-box");
function myFunc() {
    alert("Uh oh, you typed " + this.value);
}
inputBox.onchange = myFunc;

This is the same behavior. What's a little confusing is the fact that window is also the default this value. (More on that below).

With a few exceptions -- including the new operator and the bind, call, and apply methods -- the only way to pass a this value into a function in JS is to call it on an object, like this:

object.method(); // this now refers to object within `method`

An analogy

Here's a real world analogy. Say that you have a ball. Define a method on the ball called kick. The method is defined as move your leg to exert force on *it*. The "it" in that phrase is much like this in JavaScript. It's meaning is determined by context in which it is used, or in this case in which context the function is called. Without the ball, kick still has meaning but the it which it references is not defined. What I'm kicking is unknown.

When you pass a method to the window.onload handler like that, you're passing in the kick function only, which on its own has no connection to the ball. Only when we call it like ball.kick() does kick get a this value of ball. In real life, there is no "default" it value. We don't assume that it refers to, say, the earth if we're not sure. Unlike real life however, JS does have a default it/this value. That default value is the window object. Trying to reason why the default is window is probably futile - it's just an implementation decision that was made.

One solution

In your case, the easiest thing to do is to use the bind function. The bind function (introduced in the last version of JS) allows you to bind a certain this value to a function, returning the new this-bound function as its return value. So for instance, if you had a function like

Ball.prototype.kick = function() {

    this.touch();
    this.move();
    console.log(this);

}

and you did

myBall = new Ball();
kickBall = ball.kick.bind(myBall); // returns a new function, doesn't affect the original ball.kick

Then you could call kickBall without the normal ball.kick() syntax and its this value would be myBall.

kickBall(); // myBall
window.setTimeout(myBall.kick, 1000); // window

A simpler solution

The simpler (and more common) way is to just assign a function which itself calls slates.render() to window.onload, like so:

window.onload = function() {

   slates.render(); // this works because doing obj.method() passes obj as a this value to method

};

Further reading

Here's a better writeup of what this means in JS: https://stackoverflow.com/a/3127440/2407870

Community
  • 1
  • 1
Chris Middleton
  • 4,734
  • 1
  • 25
  • 54
  • You might want to mention an alternative to using `bind` is to wrap your function call in a closure such as `function() { myBall.kick(); }` – Thank you Nov 10 '14 at 00:35
  • @naomik I'm trying to figure out how to explain that method in a meaningful way - if you have an idea of how, please feel free to add to/edit the answer. And thanks for the compliment. – Chris Middleton Nov 10 '14 at 00:44
  • +1 great answer and analogy. Thanks for taking the time to write all this out! – jasonscript Nov 10 '14 at 02:06