DEV Community

Cover image for How to think about the JavaScript keyword 'this'
Christine Garaudy
Christine Garaudy

Posted on

How to think about the JavaScript keyword 'this'

The keyword this can be very useful, especially when creating classes or constructor functions, but it can be difficult to understand exactly what it's referring to sometimes because its meaning can change in different execution contexts within the program and with different patterns of invocation that may appear very similar when you're first exposed to them.

"Execution context" refers to the meaning and value of functions, objects, and variables, at a particular point in the program. Because the Javascript compiler will read the code from top to bottom, the execution context, and therefore the value of these aspects, can change from one line to the next depending on how and when we invoke functions.

There are four main patterns or styles of invoking functions in our programs. Let's explore those and see how they can each change the meaning of this and what it will be bound to.

Free Function/Global Invocation

Unless we specify, the context of this is bound to the global object (which is the window object in the browser) by default.

function garfield () {
  console.log("mmm lasagna");
  console.log(this === window);
}
console.log(garfield()); //logs mmm lasagna true

Since we didn't specify what we referring to, this automatically was bound to the global window object.

Method Invocation

When we call on a function that is created inside of an object, we say we are invoking a method of that object.

const cat = {
  noise: 'meow',
  speak: function() {
    console.log(this.noise);
  }
}

console.log(cat.speak()); //logs meow

Above, we invoke speak, which is a method of our cat object. Most of the time we can look to the left of the dot at call time of a function to see what this belongs to. In our case, cat is to the left of the dot when we invoke the function speak, so we know that this is bound to it.

Constructor Invocation

Constructor functions allow us to create a sort of blueprint for making new objects that are related. Instead of using camel case (camelCase) to declare a variable, we'll use an upper case letter to denote that this is a constructor function so other developers can tell right away that's its intended purpose. Then we use the keyword new to create new instances of that object that will share the characteristics specified inside of the constructor.

const Cat = function(breed, color) {
  this.breed = breed;
  this.color = color;
}

const myKitty = new Cat('Persian', 'white');

The new keyword lets us know that this will be bound to then newly-created object.

.apply(), .call(), and .bind()

Using .apply() or .bind() we can specify exactly what we want this to refer to, ensuring it will be what we intended and not a fun surprise.

.apply() takes two arguments- an object and an array of the arguments of the function we attach it to. .call() works in the same way as .apply, except that the function arguments will be separated by commas and not inside of an array literal. Both will invoke the function immediately.

const person = {
    firstName: 'Jenny',
    lastName: 'Smith'
}

function feed(greeting, foods) {
    return `${greeting}, human called ${this.firstName}.
            Feed me ${foods} now, or else.`;
}

console.log(feed.apply(person, ['Good morning', 'wet food and treats']));
//Good morning, human they call Jenny. Feed me wet food and treats now, or else.

Using .apply(), we specified that this would refer to our person object and we gave our feed function the arguments in the array.

What if you want to hang on to that context and re-use it? Instead of having to use .apply() or .call() over and over again, we can just use .bind() to return a function that will always have our specified context of this and save it with a variable.

const demands = feed.bind(person, ['Good morning', 'wet food and treats'])

console.log(demands());

This will give us the same output as the .apply() approach we used above, but with a lot less code to write. Each time we want to use it, we can simply invoke demands() and get the desired output for less work.

this can be a powerful tool for creating, using, and manipulating objects in JavaScript, but it takes some time and experimentation to learn how to use it correctly. Once we do, it can be a powerful tool in our developer toolbelts.

Top comments (3)

Collapse
 
greengreens3 profile image
Alex Boutin

In your Method Invocation section, the line
console.log(this.sound) would return an error and should be console.log(this.noise)

Collapse
 
christinegaraudy profile image
Christine Garaudy

Thanks so much for catching that!

Collapse
 
greengreens3 profile image
Alex Boutin

Glad to help, and a very nice article by the way 😊