What is this?

Table of Contents

What is this in JavaScript?   titlecard slide

Script   notes

So, I wrote the first draft for this talk a while ago having done zero research because it's just "not that complicated" of a topic, and started shopping it around a bit, and Webgeeks took it and I'm busy polishing it up and fact-checking so that when somebody in the audience pipes up and asks "What about X?" I'll have an answer for that question and I start to realize something:

   slide

mistake.jpg

Script   notes

A complete and thorough treatment of this in JavaScript is slightly longer than a five minute talk. So we won't cover absolutely everything—or if we do, it'll be very rushed—but we should at least cover the scenarios you're likely to run into.

   slide

Sometimes when I'm writing Javascript I want to throw up my hands and say "this is bullshit!" but I can never remember what "this" refers to

–Ben Halpern (via Twitter)

Script   notes

Certainly, if you look up all the scenarios in which this comes into play, it can be a bit overwhelming, but in typical day-to-day usage I don't think it's all that complicated. It seems to me what confuses people about this in JavaScript is that they come from other languages—like Java—where methods are attached to a class, and this always refers to an instance of that class. And yeah, if you come into JavaScript with that mental baggage, your intuition about the value of this is going to be wrong. So don't think of it that way, instead think of it like this:

this is a parameter   slide

  • Like other parameters: passed in by the caller
  • Unlike other parameters:
    • Put in strange place at call site
    • Defaulted weirdly
    • You can't pick name
function f() { console.log(this); }
var o1 = { x: 1, f: f };
o1.f(); // => { x: 1, f: ... }

var o2 = { x: 2 };
f.call(o2);  // => { x: 2 }
f.apply(o2); // => { x: 2 }

Script   notes

this is a parameter. An argument, passed in to your function, much like any other. It's segregated out at the call site—either coming before the function name, or as an additional argument to call or apply rather than being rolled in with the rest of the arguments—; it has some weird rules about how it gets defaulted if it isn't specified; and it autoboxes if you're not in strict mode; and you don't get to pick the name. It's definitely the black sheep of parameters, but fundamentally, the value of this is determined by the call site.

So when you're sitting there, staring at a function, wondering what the value of this is, just ask yourself: What is the caller setting it to? The odds are exceedingly good that'll get you to the right answer.

Any questions? Should I delve into specific examples and counter-examples?

Examples

Example: Explicit This (Normal)   slide

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

var v = { x: 1, f: f };
var w = { x: 2, f: f };

v.f(); //=> { x: 1, f: function }
w.f(); //=> { x: 2, f: function }

Script   notes

One of the common ways of passing this is simply to stick it in front of the function, like so. Not complicated!

Example: Explicit This (Normal 2)   slide

var o = {
    f: function () {
        console.log(this);
    }
};

var v = { x: 1, f: o.f };
var w = { x: 2, f: o.f };

o.f(); //=> { f: function }
v.f(); //=> { x: 1, f: function }
w.f(); //=> { x: 2, f: function }

Script   notes

Unlike your typical class-based language, functions in JavaScript don't belong to a class, or an object. They exist completely independently. You can copy them around into other objects, and call them from there.

Example: Explicit This (.call, .apply)   slide

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

var v = { x: 1 };
f.call(v); //=> { x: 1 }
f.apply(v); //=> { x: 1 }

Script   notes

Another, more explicit way of passing this to a function is to use .call or .apply. These really make it obvious that this is just a parameter.

Example: Defaulted This   slide

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

lf();               //=> window
lf.call(undefined); //=> window
lf.call(null);      //=> window

function sf() {
    "use strict";
    console.log(this);
}

sf();               //=> undefined
sf.call(undefined); //=> undefined
sf.call(null);      //=> null

Script   notes

And of course, if you don't specify this at all, there are some rules for how it gets defaulted. But whether it was passed in is still up to the call-site. Strict mode dicks with that explanation a little, but ultimately just brings it more in line with other unspecified arguments: you get undefined.

Counterexamples?   slide

Script   notes

Does anyone think they have some counter-examples?

What about .bind?   slide

// Simplified, obviously
Function.prototype.bind = function (oThis) {
    var fToBind = this;
    return function () {
        return fToBind.call(oThis);
    };
}

function f() {
    console.log(this);
}
var fBound = f.bind({ x: 1 });

fBound(); //=> { x: 1 }
f(); //=> window
fBound.call({ x: 2 }); //=> { x: 1 }

Script   notes

The trick with .bind is to remember what it actually is. .bind is just a function that returns a function that calls your function. Whatever calls the function that .bind returns is not the caller of your original function—your original function is called by the function that .bind returned. bind adds a level of indirection, but the direct caller is still passing this explicitly.

Well, hopefully browsers implement it more efficiently, but that's essentially all it is.

What about JS event handlers?   slide

function f() {
    console.log(this);
}
var node = document.getElementsByTagName("html")[0];

node.addEventListener("click", f);
node.click(); //=> <html></html>

Script   notes

The caller is some internal browser code, but thinking of this as parameter will serve your intuition well.

What about inline-HTML event handlers?   slide

<a onclick="console.log(this);">click me!</a>
<!-- click => <a></a> -->

Script   notes

Like JS handlers, the caller is some internal browser code, and thinking of this as parameter will serve your intuition well.

What about functions on the prototype?   slide

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

f.prototype.whee = protoF;

var v = new f();
v.whee(); //=> "f"-type object (v)
v.whee.call({ x: 1 }); //=> { x: 1 }

f.prototype.whee(); //=> f.prototype
f.prototype.whee.call({ x: 1 }); //=> { x: 1 }

Script   notes

Functions on the prototype are special in that some machination goes on to call them, but they're just normal functions with the same this as a function set directly on an object.

What about new?   slide

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

var v = new f();   //=> "f"-type object
var o = { g: f };
var w = new o.g(); //=> "f"-type object
f();               //=> window
f.call(o);         //=> { g: function }

Script   notes

new definitely does some magic, but in terms of actually calling your function, it's just a different way of setting this for the call. Well, mostly.

new ignores .bind   slide

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

var o = { x: 1 };
var b = f.bind(o);
new b(); // => "f"-type object (NOT o!)

Script   notes

new is special and bypasses the this argument to bind.

What about this outside of a function?   slide

whyyyyy_face_meme.jpg

console.log(this); //=> window

Script   notes

First off, why would you do that? But sure, you got me, outside of a function this is not a parameter. There are some rules about how it's set, feel free to look them up.

What about arrow functions?   slide

function fi() {
    (function () { console.log(this); })();
}
function fa() {
  (x => console.log(this))();
}

fi.call({x: 1}); //=> window
fa.call({x: 1}); //=> {x: 1}

function fb() {
    (function () { console.log(this); }).bind(this)();
}

fb.call({x: 1}); // => {x: 1}

Script   notes

Yeah, arrow functions are an exception, in that they inherit this from their enclosing scope. It's pretty much syntactic sugar for doing a .bind, though.

In The Weeds

this Type Coercion   slide

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

function strictF() {
    "use strict";
    console.log(this);
}

looseF();  //=> window
strictF(); //=> undefined

looseF.call(1);  //=> Number
strictF.call(1); //=> 1

looseF.call("str");  //=> String
strictF.call("str"); //=> "str"

Script   notes

If you pass a non-object as this, non-strict mode does some autoboxing. Strict mode makes this operate even more like a normal parameter.

What about class es?   slide

class C {
    constructor(x) {
        this.x = x;
    }
    f() {
        console.log(this);
    }
}

var v = new C(1);
v.f(); //=> { x: 1 }

var o = { y: 2, f: v.f }
o.f(); //=> { y: 2, f: function f }

Script   notes

ES6 classes are mere syntactic sugar around prototypes, and in no way change the behavior of this.

tl;dr   slide

this is a parameter. Usually.

Script   notes

If you think of this as a parameter, you're going to have a much easier time writing code that uses it.

Even More Nitty-Gritty   slide

For the full, gory details, ECMA-262 (e.g., the definition of OrdinaryCallBindThis) is essentially pseudo-code for a JavaScript implementation.