DEV Community

Cover image for Back to Basics: Understanding and Conquering "this" in Javascript
Zack Sheppard
Zack Sheppard

Posted on • Edited on • Originally published at blog.zack.computer

Back to Basics: Understanding and Conquering "this" in Javascript

I've been thinking about this a lot recently because I've been messing around with a lot of chained callback functions in my web code. This is a good opportunity to go back to basics and recap how this works in Javascript and what tools exist to tame its quirks.

For new developers coming from a more typically object-oriented language like Java or Swift, Javascript's weird use of the this keyword is a trap waiting to crash your code at any moment. This is especially dangerous if you're using React's class components, where you're often defining methods on your class to act as a callback handler. If you blindly assume that this is going to behave the way you've come to expect, you're gonna have a bad time. So, let's understand this enemy so we can learn how to fight it:

What is this

What's This

Let's start with the basics of how we expect this to work under the best circumstances:

'use strict';

class Person {
  name;

  constructor(theirName) {
    this.name = theirName;
  }

  introduce() {
    console.log("Hello I'm " + this.name);
  }
}

const william = new Person("Bill");
william.introduce(); // Prints out "Hello I'm Bill"
Enter fullscreen mode Exit fullscreen mode

This is pretty straightforward: there is a class of object called Person. Each Person remembers a variable called name and has a method called introduce. When you call introduce on a person it looks at that person's name and prints an introduction. So, this is a reference to the object whose instance of introduce we're looking at, right?

Well, not quite. Take a look at this:

// Continued from above

// This doesn't RUN william's introduce function,
// it makes a REFERENCE to it
const introduceWilliam = william.introduce;

// Because it's a reference to a method that worked,
// we might assume the reference will also work but...
introduceWilliam();
// Uncaught TypeError! Cannot read property 'name' of undefined
Enter fullscreen mode Exit fullscreen mode

Now we've delved below the calm surface into the dark depths of a functional programming language written in the 90's.

You have to remember that as far as Javascript is concerned functions are just another kind of object. They can be stored, passed around, and executed anywhere.

When you call someThing.someFunc(), Javascript parses that you want to execute the instructions in someFunc in the context of someThing. That is to say, set this to someThing and then execute the instructions.

But if you make a reference to someFunc, you could execute it anywhere. Above, we called it in the global context, which leaves this as undefined when you're in strict mode. You can even use the function's call or apply methods (functions on a function!) to provide any context and args you desire.

Let's write some mildly horrifying code to demonstrate this:

// Still using william from above
const william = new Person("Bill");
// Make a reference to william's introduce method
let introduce = william.introduce;

// Make an unrelated object - Bagel the Beagle
const puppy = { name: "Bagel", breed: "Beagle" };
// Run function with manual `this` - Dogs can talk now
introduce.call(puppy); // Prints "Hello I'm Bagel"
Enter fullscreen mode Exit fullscreen mode

Taming this Beast

This this is incredibly, and often unnecessarily, powerful. Like many incredibly powerful things, it is also incredibly dangerous. Because of how often we pass around references to functions - to use as callbacks for buttons or forms, for example - the unbound nature of this is just lying in wait to trip you up.

So how do we tame this? I could shake my cane at you and croak "Well, back in **my* day..."* but the truth is that the ES5 and ES2015 revisions to Javascript gave us everything we need to clamp down wandering this values:

Function.prototype.bind()

Added in ES5, the first tool we got was the bind() function, a standardization of this hacks that the various utility libraries of the 2000's had innovated.

// Bind this reference to introduce so this is ALWAYS william.
let alwaysIntroduceWilliam = william.introduce.bind(william);

alwaysIntroduceWilliam(); // Prints "Hello I'm Bill"
alwaysIntroduceWilliam.call(puppy); // Prints "Hello I'm Bill"
Enter fullscreen mode Exit fullscreen mode

bind does what it says on the tin. It binds the function to a chosen this - ensuring that the instructions inside are always run in the context we choose. Here you can see that even if we try to use call to set a different this, the bind overpowers and we're always introducing william. This was a great first step towards fixing this, but these days is less commonly used because of...

Arrow'd =>

Added in ES2015, arrow functions gave us (almost accidentally) the most common way of fixing this to the value that we expect. This is because an arrow function creates a closure over the context in which it was defined. What that means is that all the variables referenced inside the arrow will always reference the same points in memory as when the arrow was first parsed.

This is incredibly useful for capturing local variables so that they can be used later, but it has the added benefit of capturing the value of this that was set when the arrow was defined. And, since this is (basically) always going to be the object being created during construction, we can use arrow functions to make methods where this will behave exactly like we expect:

// Rewriting Person with arrows
class ArrowPerson {
  name;

  constructor(theirName) {
    this.name = theirName;
  }

  introduce = () => {
    // The arrow captures `this` so it is actually a
    // reference to THIS Person.
    console.log("Hello I'm " + this.name);
  }
}

const arrowBill = new ArrowPerson("Arrow Bill");
arrowBill.introduce(); // "Hello I'm Arrow Bill"

// Now `this` is fixed even as we pass the function around:
const introduceRef = arrowBill.introduce;
introduceRef(); // "Hello I'm Arrow Bill"
introduceRef.call(puppy); // "Hello I'm Arrow Bill"
Enter fullscreen mode Exit fullscreen mode

this all makes more sense now

I hope you understand this a little bit better now. To be honest, I think I understand it better just from writing this all out. And, because the Javascript this can affect all your code that transpiles into Javascript, hopefully this will also help you understand the twists and turns of function context in other languages like Typescript.

If you have any questions about this, drop them in the comments below. Even after years writing for the web, I'm still learning so I'm sure there are terrible dangers and cool facts about this I forgot or don't yet know.

Top comments (8)

Collapse
 
keithprice profile image
Keith Price

Thanks, very interesting. I am not far into my JavaScript studies and the this keyword is one of those things that hasn't been easy to understand at times. You've certainly made it clearer for me.

Collapse
 
zenwork profile image
Florian Hehlen • Edited

You have explained 'this' very well even for someone like me who already knows all this but has never articulated it. I often get frustrated at why people out there complain that any approach in JS that relies on 'this' is broken. Now I have an easy read to point them to.

Collapse
 
nicozerpa profile image
Nico Zerpa (he/him)

Wow, I had no idea that apply and call couldn't override this in arrow functions and functions in which bind was applied. Thank you!

Collapse
 
zackdotcomputer profile image
Zack Sheppard

This is something that I learned fresh as well while writing this. Not only arrow functions, but even regular ones too! bind turns out to be super powerful.

Collapse
 
dhorse1 profile image
David G. Horsman

Thanks, I wasn't sure about 'this' in a closure but you confirmed my assumptions.
"This" can be a pain in c#. It's a read only value you can't pass by reference. I create a Self field during construction and pass that instead but you can't do that in constructor " x() : baseX() " calls.

Collapse
 
tilkinsc profile image
Cody Tilkins

Try singletons instead.

SomeClass.Instance.SomeFunc()

Using 'This' should only boil down to extra specification on which fields you are modifying. You shouldn't have to use it outside the class and its available everywhere in the class. If you need multiple instances elsewhere, it's much better to consume an array of the data and pass around a reference to that.

Collapse
 
uttambr profile image
Uttam Rabari

Closure reference in arrow functions, it makes more sense now.

Collapse
 
andrewbaisden profile image
Andrew Baisden

Good read some nice explanations.