What is this?
Table of Contents
What is this in JavaScript? titlecard
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:
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.
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
- 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)
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)
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)
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
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?
Script notes
Does anyone think they have some counter-examples?
What about .bind?
// 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?
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?
<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?
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?
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
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?
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?
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
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?
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
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
For the full, gory details, ECMA-262 (e.g., the definition of OrdinaryCallBindThis) is essentially pseudo-code for a JavaScript implementation.