JavaScript Lists --- This

JavaScript Lists --- This

Originally posted on 15 January 2013.

This post was inspired by a recent thread on Hacker News that created some conversation about the this keyword in JavaScript. I want to thank Henry for writing that post and laying out the base cases that I built on for this post.

JavaScript has some confusing features. Every now and then, I see blog posts detailing “how to understand closures in JavaScript” or “grokking closures in JavaScript.” Most of these are written, quite appropriately, by folks who are learning about JavaScript's features and sharing their knowledge as they learn useful tricks and build up an understanding of the language. As a result, these posts are often incomplete. Now, you can go to the JavaScript spec if you want a more detailed answer to your question, but the spec isn't nicely laid out as "here are all the ways this could be bound" and "here's how variable binding (doesn't) work in JavaScript."

I wish there was a resource that laid out features this way, so I'm going to try writing down a few lists of what I think are comprehensive descriptions of some features of JavaScript. I'll happily update the post (with credit) when folks find stuff that I forgot to add to the list. I'll try to order things so that corner-case uses come later in the list than the most frequent uses, but there will be some random judgment calls made by me in there. Without further ado...

Binding this

There are lots of ways to get the keyword this bound in JavaScript.

Method Invocation

In a method invocation expression, the result of evaluating the expression to the left of the dot or bracket will be bound as this inside the called method:

o = { x:22, fun: function() { return this.x; } };
o.fun(); // returns 22
o.["f" + "un"](); // returns 22

function return_o() { return o; }
return_o().fun(); // returns 22

Function Invocation

If you call a method with function call syntax, even if it used to be a field of an object, one of two things will happen:

  • The common case is that it will have window passed to it as this (it's actually just "the global object," which is a different thing if you're on the server running Node, but the common case is window in the browser).
    window.x = 42;
    o = { x:22, fun: function() { return this.x; } }
    extracted = o.fun;
    extracted(); // returns 42, not 22
  • If the target function is in strict mode (which you should start using immediately if you aren't), in which case undefined will be bound to this (the "set to global" option is skipped):
    window.x = 42;
    o = { x:22, fun: function() { "use strict"; return typeof this; } }
    extracted = o.fun;
    extracted(); // returns "undefined"

Callbacks To ECMAScript Builtins

When you pass callbacks to built-in ECMAScript functions like forEach, you can supply a thisArg, and if you don't, this will be bound to undefined (reference).

function f() { console.log(String(this)); }

[1,2].forEach(f); // prints undefined, undefined
[1,2].forEach(f, "mythisarg"); // prints "mythisarg" "mythisarg"

Callbacks To DOM Builtins

Some callbacks, like setInterval and setTimeout, pass the global object rather than undefined. ECMAScript-defined functions have been specified to take and explicit this argument or pass undefined, but the DOM APIs (to my knowledge) still usually pass the global object when there is no DOM node involved.

function f() { console.log(this); }

setTimeout(f,0); // prints the global object

Other callbacks, like those registered on DOM nodes themselves, pass the node as the this argument.

document.addEventListener('click', function() { console.log(this); })
// prints the document object when you click on the page

It's nice to think of this case as the browser doing a method invocation for you, with the DOM element to the left of the dot.

At the Toplevel

When using this at the toplevel (which is allowed), it is bound to the global object (even in strict mode, it's just the default global this binding ):

console.log(this) // prints the window object

Functions Invoked with new

When you use the new keyword, this is bound to a newly-created object with its internal proto set to the prototype field of the constructor.

function f() {
  console.log(Object.getPrototypeOf(this) === f.prototype);
}
new f(); // prints true

Call, Apply, and Bind

The builtins Function.prototype.call/apply/bind allow you to provide your own this argument to a function:

function f() { console.log(String(this)); }
f.call("mythis", "other", "args"); // prints "mythis"
f.apply("mythis", []); // prints "mythis"
f.bind("mythis")(); // prints "mythis"

Field Access with Getters and Setters

If a property is a getter or a setter, this is bound to the object to the left of the dot or bracket in the field access or assignment expression. This sort of matches the method invocation rule except for the fact that "method call" is implicit; there are no () in the expression o.x, but it may call a getter function for x that passes o as this (reference).

o = { foo: 42,
      bar: "another field",
      get x() { console.log(this.foo); },
      set x(_) { console.log(this.bar); } };
o.x; // prints 42
o.x = "whatever"; // prints "another field"

Caveat: Primitive Coercions

The dot and bracket operators implicitly convert primitives like numbers, strings, and booleans to objects, so it's not exactly what's to the left of the dot (this is why I have calls to String in a few examples to make them print the way I wanted):

    Number.prototype.identity = function() { return this; }
    var x = 5
    var x2 = x.identity()
    typeof x2 === 'object' // (true!)
    typeof x2 === 'number' // (false!)

Again, strict mode gives us saner behavior. You can get the raw primitive if you make the function strict:

    Number.prototype.identity2 = function() { "use strict"; return this; }
    var x = 5;
    var maybe_five = x.identity2();
    typeof maybe_five === 'object' // (false)
    typeof maybe_five === 'number' // (true)

The Next List

My next list will probably be a list of ways variables can be bound in JavaScript, but I'm open to other suggestions. Contact me if you'd like to see something else or want to report a bug in this list.