A Guide to ‘this’— One of the Least Understood Parts of JavaScript

What I wish I knew when I started learning about the ‘this’ keyword

Published on
Sep 3, 2019

Read time
8 min read

Introduction

The keyword this is the cause of a lot of confusion in JavaScript. It’s also important enough that it’s difficult to avoid. If you use React or Angular, for example, you’ll likely to be using JavaScript classes, where the this keyword is almost a necessity.

But even in plain old vanilla JavaScript, you may encounter this — or its side-effects — more often than you think. For example, ES6’s so-called “fat arrow” syntax automatically adopts the this binding from the
enclosing scope. It’s useful when creating new objects from prototypes and writing regular JavaScript functions.

The present article is intended for developers who may have used the this keyword, but who don’t feel like they have a strong grasp of its behaviour. I hope that — if you try out the examples — you should finish the article with a much firmer idea of what this does and why. For those who want to go further, I’ve included some resources at the bottom.

Why ‘this’ means different things at different times

this can seem tricky because its meaning changes depending on the context in which it is used. We’ll start by looking at its most basic context.

Though this is mostly found within objects, functions and classes, we can also call it globally, like so:

console.log(this);

Using this without a more specific context is equivalent to calling the global object: in a web browser, that’s the window object.

If that’s the result we want, we could also call self, frames or globalThis. But these always refer to the window object: in other words, they lack the flexibility that makes this useful.

Not every JavaScript program runs in a browser. You should be aware that, in non-windowed environments, the global meaning of this won’t be the window object — because this doesn’t exist!

For example, when used globally in Node.js, this represents an empty object:

console.log(this); // {}

this.foo = "bar";

console.log(this); // { foo: 'bar' }

Scope vs Context

When approaching this for the first time, many developers presume that it works in a similar way to scope. But that thinking can lead to mistakes!

That’s because this depends on context, rather than scope. In the previous section, we were talking about global context rather than global scope. As it happens, the code above had both a global context and a global scope, so what’s the difference?

What is Scope?

Scope controls the context in which variables are accessible. Here’s a classic example:

function foo() {
  const bar = true;
  return bar;
}

console.log(foo()); // true
console.log(bar); // ReferenceError: bar is not defined

The variable bar is only accessible inside our function, foo. If we call foo(), we’ll get the value of bar. But if we try to use bar outside of foo, we’ll get a ReferenceError. This is known as local scope or functional scope.

It’s tempting to imagine that this works in the same way. But this misunderstanding is probably the reason for most beginners’ mistakes!

What is Context?

To get an understanding of context, let’s re-write our function foo to set the value of this.bar:

function foo() {
  this.bar = true;
  return bar;
}

console.log(foo()); // true
console.log(bar); // true

Beginners often assume that, in the above example, this refers to the function foo. But when we try to access bar, we can!

That’s because the expression this.bar = true is actually writing a global variable bar. That’s because we haven’t set a specific context and so, as in the first example in this article, this refers to the global object. To give this a context, you need to use a method like call, apply or bind.

Defining a Context for ‘this’

We now understand that scope and context are different, but how do we define a context for this?

Let’s begin with the simplest possible example:

function func() {
  return this;
}

If we call func(), we’ll get the global window object like before. But there are three methods we can use to change the meaning of this when we call it: call, apply and bind.

Say we wanted this to represent the string 'Hello World!', we can do that in a number of ways:

func.call("Hello World");
func.apply("Hello World");
func.bind("Hello World")();

Each line of the code above would return the string 'Hello World!'. Like a function argument, we can use any data type with the above methods. Most commonly, you’ll likely encounter objects. Take the example below:

const a = 1;
const obj = { a: 2 };

function whatIsA() {
  return this.a;
}

Whether whatIsA() returns 1 or 2 will depend on how we call it:

whatIsA(); // 1
whatIsA(obj); // 1

whatIsA.call(obj); // 2
whatIsA.apply(obj); // 2
whatIsA.bind(obj)(); // 2

Notice that passing obj as an argument of whatIsA has no effect on the result of this.

So Why Bother?!

At this point, you might be wondering why we should go to all the extra effort. Wouldn’t a regular function argument work fine?

Well, in simple examples like the ones above, regular arguments would be fine! The usefulness of this becomes more apparent when we start chaining functions to one another:

function round() {
  return (this.price / 100).toFixed(2);
}

function toCurrency() {
  return "$" + round.call(this);
}

const sale = {
  price: 1299,
};

const sale2 = {
  price: 3750,
};

console.log(toCurrency.call(sale)); // $12.99
console.log(toCurrency.call(sale2)); // $37.50

In the code above, we are converting integers to dollars in two steps. First, we round the number to two decimal places. Then we add the dollar symbol.

Of course, the desired effect could also be achieved using arguments:

function round(obj) {
  return (obj.price / 100).toFixed(2);
}

function toCurrency(obj) {
  return "$" + round(obj);
}

But, as the number or complexity of our functions increases, it can quickly become difficult to keep track of every argument and make sure it’s in the correct form.

By contrast, when using this, we can be sure of the exact form our context will be taking: it will always be exactly whatever we pass into our call or apply method.

Call and Apply

The difference between call and apply is simply semantic: call() accepts an argument list, while apply() accepts a single array of arguments.

To demonstrate, let’s extend our toCurrency function so that it allows different currencies and gives the option to change the dot to a comma, so we can get results like €12,99.

function round() {
  return (this.price / 100).toFixed(2);
}

function toCurrency(code, separator) {
  let symbol;
  let number = round.call(this);
  switch (code) {
    case "EUR":
      symbol = "€";
      break;
    case "JPY":
      symbol = "¥";
      break;
    case "USD":
    default:
      symbol = "$";
      break;
  }

  if (separator) {
    number = number.toString().replace(".", separator);
  }

  return symbol + number;
}

If we want to display a price in Euros, with a comma as a separator instead of a dot, we could either use:

toCurrency.call(sale, "EUR", ","); // €12,99

Or we could use:

toCurrency.apply(sale, ["EUR", ","]); // €12,99

Bind

Up to this point, whenever we want to use a specific context, we’ve had to state it at the point we call our function. But what if we want to associate a function to a specific context, so that our chosen context is presumed every time we call the function? This is where bind comes in!

const o = {
  x: 10,
  getX: function () {
    return this.x;
  },
};

let y = o.getX;

If we call y, what’s will the result be? Without any specific context, this will take the value of x in the global context. In this case, there isn’t one, so we’ll get undefined.

But we can bind our function y to the context of o:

const o = {
  x: 10,
  getX: function () {
    return this.x;
  },
};

let y = o.getX;

y = y.bind(o);

Now, whenever we call y, we’ll get the number 10. Pretty useful!

Using Bind in Class Methods

If you’re used to a framework like React, which makes heavy use of JavaScript classes, you’ll likely be used to binding your methods inside your class’s constructor method:

import React from "react";

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.addOne = this.addOne.bind(this);
  }

  addOne(event) {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <button type="button" onClick={this.addOne}>
        Click Me
      </button>
    );
  }
}

The reason for the longwinded expression this.addOne = this.addOne.bind(this) is that we always want the addOne method to be executed in the context of the current React class. Otherwise, our value of this will not be what we expect. React classes run in ‘strict mode’, so instead of this defaulting to window, it returnsundefined: our addOne method would attempt to use undefined.setState, which — of course — doesn’t exist!

Using the bind method means that, whenever we call addOne, it will use the setState method that is part of our class.

Implicit Binding with Arrow Functions

More experienced React developers might prefer to avoid writing out the bind expression with every new method. Instead, the Counter component above could be re-written like so:

import React from "react";

class Counter extends React.Component {
  state = { count: 0 };

  addOne = (event) => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <button type="button" onClick={this.addOne}>
        Click Me
      </button>
    );
  }
}

This works because the arrow function automatically adopts the this binding from the enclosing scope — in this class, Counter.

this in Objects

Lastly, it’s worth noting that when this is used in an object method, its context — unless otherwise defined — will be that of the most immediate object.

function func() {
  return this;
}

let obj = { func, a: 10 };

Take the code above. If we call func(), we’ll get the global object. But if we call obj.func(), we’ll get our obj itself.

Puzzles

If you’d like to test your understanding of this, here are 3 short puzzles. All of these should work if you paste them directly into your browser’s console, so I recommend trying them out for yourself once you’ve had a go at the answer.

Puzzle 1: Scope and Context

Now that you have a sense of the difference between scope and context, here’s a puzzle for you. What number will calling doThing return?

const a = 1;

function doThing() {
  const a = 2;

  this.doIt = function () {
    return a;
  };

  return doIt();
}

Puzzle 2: Implicit Binding with Arrow Functions

Given that arrow functions automatically adopt the this binding from the enclosing scope, what do you think will happen when we call obj.func() below?

const func = () => {
  return this;
};

let obj = { func, a: 10 };

Puzzle 3: Nested Objects

Based on the code below, if we call outerObj.innerObj.val, what will we get?

this.val = 10;

const outerObj = {
  val: 50,
  innerObj: {
    val: 100,
    func: function () {
      return this.val;
    },
  },
};

const func = outerObj.innerObj.func;

Answer 1

The answer is 2. doIt is defined globally, but because of where it is defined (within the functional scope of doThing) it has access to var a = 2!

This might seem confusing at first, but once we’ve recognised that scope and context are different, it should make sense!

Answer 2

We get the global window object!

This cannot be changed by using call, apply or bind. If you need to change a binding, use a regular function.

Answer 3

The context of this is our innerObj, so the val we get is 100.

If we wanted to get 50, we would have to change our context to the outerObj by calling outerObj.innerObj.func.call(outerObj).

And if we wanted to get 10, we could call outObj.innerObj.func.call(this).

Further Reading

That’s the end of our whistle-stop tour of the this keyword. I hoped this article helped clarify what this can do, what it isn’t, and where it can be useful.

If you’d like some further examples of this in action, or you want to dig a little deeper, I recommend the following.

1. Mozilla Developer Network, this

As ever, MDN is an extremely useful resource, with lots of examples of this in action.

this - JavaScript | MDN
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

2. Todd Motto, Understanding the this keyword in JavaScript

There are lots of articles about this on the web, but this one jumped out at me for its simple and clear examples!

Understanding the this keyword in JavaScript
https://ultimatecourses.com/blog/understanding-the-this-keyw...

3. Kyle Simpson, You Don’t Know JS — this & Object Prototypes

You Don’t Know JSA book series on JavaScript
https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/this-...

© 2024 Bret Cameron