What is `this`?

Now that I've finished writing about each item on that very long list of array methods, I'm finally getting around to some of the other topics on my Trello board that I've been wanting to put onto this blog. As I've said previously, starting and maintaining this blog is an exercise in breaking down my imposter syndrome when it comes to web development. I love having a written record and time capsule of what I've learned and when I've learned it, and so far writing about a concept, a method, or how I solved some problem has really helped to solidify that knowledge in my own head, and I feel like I've been becoming a better developer in that process.

Today I want to write about one of the more confusing (at least to me) bits of JavaScript: the this keyword. Over the last three or so months I've been reading Kyle Simpson's series, You Don't Know JS. Stepping back a little bit, I've twice tried reading and coding through Marijn Haverbeke's Eloquent JavaScript. Marijn is clearly a very talented programmer, but for some reason that book just will not click with me. Maybe it's a bit advanced or it just skims over certain topics that need a bit more time and explanation so as to keep them from being speed bumps for beginner developers. The this keyword is one of those. I got an online referal to try reading the YDKJS books, and while it's still a bit heavy on the theoretical side, I love the deep dive that Kyle takes in talking through weird and misunderstood parts of the language. This blog post is a distillation of some of his fine work in his book, this & Object Prototypes.

As a quick aside, none of this is meant to disparage Eloquent JavaScript or Marijn Haverbeke. It was presented to me as a good book for beginners, but so much of it has just flown completely over my head. I will give it another shot after I've finished YDKJS, so I definitely have not given up on it.

Why learn this?

So what is this and why would anyone want to learn about it? I've now written at least my first real world application, and I didn't really use this, and if I did it was trial and error as to what it was referring to. You see, the this keyword in Javascript is a reference to some sort of data or functional context. What it refers to depends on where and how a function that uses it has been called, and while it can be difficult to decipher sometimes, proper usage of this is an elegant way of passing context around your application.

You can get by without it (and I have), but getting by isn't growth, and the second you start looking at code produced by more experienced developers, it can look like pure magic when they're throwing this around and their code is producing wonderful results. For my part, I felt that limitation when I started looking into the NPM dependencies that my application was using, just to see what was going on under the hood. Trying to recreate and learn from that code's behavior led to me writing console.log(this) over and over and over and over just to see what in the hell was going on. Clearly, I just didn't understand the convention of what they were writing, and yet ostensibly this is a convention in a programming language that I knew.

The trouble with this

So why is this so difficult? I think there are two main problems.

  1. The this keyword itself. "This" in the English language is already a vague word, a pronoun that supposes that you know what it refers to, and that's just in a written/spoken language. Now when it appears in a difficult, dynamic programming language, its behavior changes and it can just be straight up quirky and unpredictable at times. Compounding the problem is the the fact that this is just linguistically difficult to write or speak about as a concept. When we start to break the concept down, we often wind up referring to multiple this use cases, and start falling into this trap of saying stuff like "this this as opposed to that this." As an English major, it drives me crazy (and I'm sure this blog post will take me forever to copy edit). For what it's worth, when I'm refering to the JavaScript keyword, it will look like so: this. Otherwise it will be regular font or italicized where appropriate.
  2. Bad teaching about the rules. At least in my coding bootcamp, the instructors did not break down the various rules that dictate what this was referring to. Again, all this is is a context for a function at the time that function is executing. That context will hold data and other functions. What that context actually is completely depends on how and where that function is called at execution time.

What isn't this?

There are two common misconceptions that are important to get out of the way first.

  1. this does not refer to the function itself. Look at the following code:
const names = ["Joey", "Sebastian", "Bad Bart", "Franklin"];

function takeAttendance(name) {
  console.log(`Hello ${name}!`);
  this.numberOfAttendees += 1;
};

takeAttendance.numberOfAttendees = 0;

names.forEach((name) => {
  takeAttendance(name);
});

console.log(takeAttendance.numberOfAttendees);
// result: 0

We're trying to increment the numberOfAttendees value each time we greet someone in the names array. But wait a second, we called takeAttendance() four times, why didn't its count increment? That's because the this here doesn't point to the function itself. Instead, we need to do some work to actually bind this to the object we want:

const names = ["Joey", "Sebastian", "Bad Bart", "Franklin"];

function takeAttendance(name) {
  console.log(`Hello ${name}!`);
  this.numberOfAttendees += 1;
};

takeAttendance.numberOfAttendees = 0;

names.forEach((name) => {
  takeAttendance.call(takeAttendance, name);
});

console.log(takeAttendance.numberOfAttendees);
// result: 4

We'll discuss how this works pretty soon.

  1. this does not refer to the function's lexical scope. Look at the following code:
function randomNumber() {
  const number = Math.floor(Math.random() * 10);
  printRandomNumber();
};

function printRandomNumber() {
  console.log(this.number);
};

randomNumber();
// result: undefined

What's going on here? Well we're trying to pass the number variable, which exists in the randomNumber() scope, to a different function, printRandomNumber(). But even though we call printRandomNumber() inside of randomNumber(), printRandomNumber() doesn't have access to what's in randomNumber()'s scope. No bridge can be made here, and especially not using the this keyword.

What is this?

So then what the hell is this?

this is a binding made at runtime, and what it refers to is entirely dependent on where, when, and how its function was called to execute.

There are four major binding rules determining what context this refers to:

  1. Default Binding
  2. Implicit Binding
  3. Explicit or Hard Binding
  4. new Binding

Default Binding

Default Binding occurs when you're invoking a function at the global scope (often window in a browser). It can be thought of as the default catch-all rule for this binding. Look at the following code.

function logColor() {
  console.log(this.color);
};

var color = "Blue";

logColor();
// result: "Blue"

The first thing to note is that when you do a plain variable declaration at the global scope, that variable and its value are made into a key-value pair that exists globally. In a browser this is window. The following console.log()s are exactly the same:

var color = "Blue";

console.log(color);
console.log(window.color);

Secondly, in the previous example, the logColor() function resolves to the global (window) object. If we examine its call site, it's called as a plain function reference, and none of the other rules (that we'll get to shortly) apply.

An important note: if we're working in strict mode, the global object is not eligible for default binding, so the above code would throw a TypeError.

function logColor() {
  "use strict";

  console.log(this.color);
};

var color = "Blue";

logColor();
// result: TypeError: Cannot read property 'color' of undefined

Implicit Binding

The second rule we'll look at is Implicit Binding, which occurs when a context object contains a reference to the function that it's calling. Let's take a look:

function logColor() {
  console.log(this.color);
};

var car = {
  color: 'Blue',
  logColor: logColor
};

car.logColor();
// result: "Blue"

In this code we have declared the logColor() function, then later added a reference to it as a property on the car object. This allows the callsite of the function to use the car context to reference the function, so this.color and car.color are the same thing.

This is handy enough, but it has its pitfalls in which this binding can be lost, or point to something unintended (often it's falling back to Default Binding).

function logColor() {
  console.log(this.color);
};

var car = {
  color: 'Blue',
  logColor: logColor
};

const logTruckColor = car.logColor;

var color = 'Red';

logTruckColor();
// result: "Red" -- NOT "Blue"!

So what happened here? For one thing, even though logTruckColor() references the logColor() function reference inside of the car object, it's really just a reference to the original logColor() function itself. Secondly, what's important when determining this is the function's call site. In this case, we're calling logTruckColor() with a plain function call, so Default Binding occurs, and this points to the global object, on which we have added a color: "Red" property.

Be especially wary of this behavior when working with callback functions.

Explicit Binding

Where Implicit Binding requires us to mutute the object we're working with to contain a reference to the function we want to use, the third rule, Explicit Binding, allows us to deliberately tell a function what object we want to use. The syntax involves using .call() or .apply() methods, which are available on all functions we would declare. The first method each parameter takes is the object you want this to be a reference to.

function logColor() {
  console.log(this.color);
};

var car1 = {
  make: 'Toyota',
  model: 'Corolla',
  color: 'Blue',
  year: 2005
};

var car2 = {
  make: 'Toyota',
  model: 'Camry',
  color: 'Red',
  year: 2001
};

logColor.call(car1);
// result: "Blue"

logColor.call(car2);
// result: "Red"

Hard Binding

Even using Explicit Binding won't always solve the problem of losing this binding to global scope (as could occur when we're working in some framework or using some code dependency). But a variation called Hard Binding occurs helps mitigate this issue while allowing you to pass in additional arguments.

function calculateCarTotal(fees) {
  console.log(`Price: ${this.price}, Fees: ${fees}`);
  return this.price += fees;
};

var car = {
  make: 'Toyota',
  model: 'Corolla',
  year: 2005,
  color: 'Blue',
  price: 1000
};

var bindCarPrice = function() {
  return calculateCarTotal.apply(car, arguments);
};

var corollaPrice = bindCarPrice(200);
console.log(corollaPrice);
// results:
// Price: 1000, Fees: 200
// 1200

This pattern is so common that as of ES5 Function.prototype.bind is a utility built into the language, and it's used like this:

function calculateCarTotal(fees) {
  console.log(`Price: ${this.price}, Fees: ${fees}`);
  return this.price += fees;
};

var car = {
  make: 'Toyota',
  model: 'Corolla',
  year: 2005,
  color: 'Blue',
  price: 1000
};

var bindCarPrice = calculateCarTotal.bind(car);

var corollaPrice = bindCarPrice(200);
console.log(corollaPrice);
// results:
// Price: 1000, Fees: 200
// 1200

Language APIs

A lot of functions and methods built into JavaScript provide an optional parameter that allows this binding, which almost certainly uses Explicit Binding under the hood. You might remember this syntax from the Array.from() method:

Array.from(arrayLike[, mapFn[, thisArg]]);

And here was an example of using just the first mapFn parameter:

const doubleNumbers = Array.from([1, 2, 3, 4], x => x *2);
console.log(doubleNumbers);
// expected output:
// (4) [2, 4, 6, 8]

Now here we can produce the same results using the third thisArg parameter. When looking at the difference between the following example and the previous one, remember that arrow functions can't use the this keyword!

const doubleNumbers = Array.from([1, 2, 3, 4], function(item) {
  return item * this.multiplier;
}, { multiplier: 2 });
console.log(doubleNumbers)
// will return : [2, 4, 6, 8]

new Binding

The final rule to observe is new Binding, which occurs when we use the new keyword to call a constructor.

function Car(make, model, year, color) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.color = color;
};

var corolla = new Car('Toyota', 'Corolla', 2005, 'Blue');

console.log(corolla.color);
// result: "Blue"

Put it all together

So those are the four rules to follow when determining this, and they're presented here in order from lowest to highest precedence. new Binding will be observed over all other rules, then Explicit Binding or Hard Binding, then Implicit Binding, and if none of those rules apply, it falls back to Default Binding.

Some questions to ask yourself:

(1) Is the function called with new? If so, observe the new Binding rule. this refers to the newly constructed object.

function Car(color) {
  this.color = color;
};

var corolla = new Car('Blue');
console.log(corolla.color);

(2) Is the function called with .call() or .apply()? If so, observe the Explicit Binding rule. this refers to the object being called or applied to the the function.

function logColor() {
  console.log(this.color);
};

var car = {
  color: 'Blue'
};

var corolla = function() {
  logColor.call(car);
};

corolla();

(2.1) Is the function called with .bind()? Observe the Explicit Binding rule, specifically Hard Binding behavior. this refers to the object being bound to the function.

function logColor() {
  console.log(this.color);
};

var car = {
  color: 'Blue'
};

var corolla = logColor.bind(car);

corolla();

(3) Is the function called with a context object, and does that object have a reference to the function as a property? Observe the Implicit Binding rule.

function logColor() {
  console.log(this.color);
};

var car = {
  color: 'Blue',
  logColor: logColor
};

car.logColor();

(4) Otherwise fall back on Default Binding, in which (if not in strict mode) this will be the Global object.

function logColor() {
  console.log(this.color);
};

var color = "Blue";

logColor();
// result: "Blue"

Once again I cannot recommend Kyle Simpson's this & Object Prototypes, and really his entire You Don't Know JS series. It gets into much more nuance than I do here, and explores more use cases, theory, exceptions, and gotchas for the rules surrounding this.