Site icon JavaScript Artisan

What is `this`?

JavaScript has many interesting and useful quirks, and one that’s widely used yet often misunderstood (and admittedly a bit confusing) is the variable this. In traditional object-oriented languages, the this variable refers to [is bound to] the current instance of the class that it’s being called from. In that sense, it’s essentially a computer-languge pronoun that means “my current class instance”. It’s kind of like when I say “my mom”, I’m referring to “the woman named Donna Belhumeur”. It’s a shorthand that makes things flow more smoothly both in natural languge and in code.

JavaScript is actually a prototypal language, not a classical one. There are a few more rules around what this can be bound to, which is why it can be confusing. Let’s work through the binding to get some clarity on this.

Implicit Binding

Implicit binding is the most common and straightforward way this is used. When you call a method from an object, this is bound to the object. For example:

let car = {
	refuel: function() {
		console.log(Refueled with ${this.fuelType});
	},
	fuelType: gasoline,
};

car.refuel(); // outputs "Refueled with gasoline"

let independentRefuel = car.refuel;
independentRefuel(); // outputs "Refueled with undefined"

Note that in the above example, this is bound to the car object only when it was called from the car object: car.refuel(). The name for this relationship is context (the car object is the context for the refuel function call). Don’t confuse where it’s defined with where it’s called. independentRefuel is the same function, just called by itself rather than from the car object so this is not bound to the car object. this is bound at call time, not at definition. So what was the context for independentRefuel? Let’s see… 👇

Default Binding

The default rule applies when there is nothing bound. When there is no calling context, this is bound to the windowobject in the browser (or the global object in Node). In the previous example, independentRefuel is called from an object, so the reference to this inside of it is defaulted to the appropriate highest-level object, window. Since window.fuelType doesn’t exist, the value returned is undefined.

Explicit Binding

It’s also possible to call a function with any arbitrary context in what’s known as explicit binding. You can do this with Function.call or Function.apply (there’s a slight difference between the two you can read about on MDN). In our case, we’ll call independentRefuel a couple times with new, different contexts.

let truck = {
	fuelType: diesel,
};

let tesla = {
	fuelType: electricity,
};

independentRefuel.call(truck); // "Refueled with diesel"
independentRefuel.call(tesla); // "Refueled with electricity"
independentRefuel(); // "Refueled with undefined"

.call and .apply allow you to pass in a context each time you call the function. In some cases, you may want to make a binding more permanent. For that, you can create a copy of a function bound to a permanent context with Function.bind. The new function will use that context no matter how it is called (with one exception, discussed next).

let truckRefuel = independentRefuel.bind(truck);
truckRefuel(); // will now always output "Refueled with diesel"

// even using .call on a bound function doesn't change context
truckRefuel.call(tesla); // "Refueled with diesel"

New Binding

The final way context is determined is when you create an object with a constructor function. A quick intro (or refresher): a constructor function is intended to be used with the new keyword to make objects of a certain type. Calling a function with the new keyword is actually what makes it a constructor function. Put another way, constructor functions are just regular functions called with a special context. Example:

function Vehicle() {
	this.refuel = independentRefuel;
}

let vehicle = new Vehicle();

vehicle.refuel(); // "Refueled with undefined"
vehicle.refuel.call(tesla); // "Refueled with electricity"

When a constructor function is called, a new, empty object is magically created and bound to this, and is also automagically returned (unless you explicity return something else). If you were to .bind the constructor function before using it, it would still use the “magical new object” context intead of the bound context.

let SecondCar = Vehicle.bind(car);

let car2 = new SecondCar();

car2.refuel(); // "Refueled with undefined"

While the above example is an uncommon case (you’d never really .bind a constructor function), it illustrates both the context with the new keyword and the limitation of .bind.

Summary

this can be bound to a number of different things, but usually you’ll use implicit and explicit binding. The order of precedence is:

  1. new binding
  2. Explicit binding
  3. Implicit binding
  4. Default binding

Over time and with practice, you’ll get used to what to expect from this when you reference it. It’s a very powerful and useful tool and one of JavaScript’s hidden powers.

Exit mobile version