The Javascript `this` keyword explained

The Javascript `this` keyword explained

Β·

8 min read

Hi friend, welcome to my blog!😁

In this tutorial, we are going to explore the javascript this keyword...

The this keyword in Javascript is always a hard nut to crack for people learning Javascript. However, the rules governing this are relatively straightforward once you get more familiar with them.

This, a reserved word in Javascript, refers to some object. Its value is determined at execution.

I am going to explain how to determine the value of the this keyword using the terms default binding, implicit binding, explicit binding, and 'new' binding from my favorite javascript book You Don't Know JS by Kyle Simpson.

Default Binding:

The default binding exists when the this keyword is in the global context. When the this keyword is out of a declared object, its value is the global object, which is the Window object in the browser.

Eg:

console.log(this) // Output: window object

let item = this;
console.log(item); // Output: window object

function defaultBindingExample() {
    return this;
}

console.log(defaultBindingExample()); // Output: window object

Note: In strict mode, when the this keyword is out in the wild, outside of a function or class, its value is still the global object, however, inside a function, if the value of this is not set when entering an execution context, it remains undefined.

Eg:

'use strict'

console.log(this) // Output: window object

let item = this;
console.log(item); // Output: window object

function defaultBindingExample() {
    return this;
}

console.log(defaultBindingExample()); //  Output: undefined

Implicit binding

When the keyword this is inside of a declared object, the value is the closest parent object

const schoolProfile = {
    name: 'Amaka',
    university: 'University of Nigeria',
    department: 'Computer Science',
    greeting: function () {
        return `Hi, my name is ${this.name} I attend ${this.university}`
    },
    checkThisContext: function () {
        return this === SchoolProfile
    },
    courses: {
        listCourses: function () {
            return `At the department of ${this.department}, we study python programming`
        },
        checkThisContext: function () {
            return this === SchoolProfile
        }
    }
}

console.log(schoolProfile.greeting()) 
// Output: Hi, my name is Amaka I attend University of Nigeria
console.log(schoolProfile.checkThisContext()) // Output: true
console.log(schoolProfile.courses.listCourses()) 
// Output: At the department of undefined, we study python programming
console.log(schoolProfile.courses.checkThisContext()) // Output: false

Following the implicit binding rule, this in schoolProfile.greeting() refers to schoolProfile which is the closest parent object, in the same way, this in schoolProfile.courses.listCourses() refers to courses, and because the courses object does not have a key named department, the value is undefined.

To solve this, we need to explicitly change the value of this and that is where the call, bind, apply comes in as we will see in the next section.

Explicit binding.

To explicitly determine which object the this keyword refers to, we can use one of three methods: call, apply, or bind. These functions allow us to choose what we want the context of this to be.

Note that the three methods can only be used by functions.

Call()

The first argument in the call method is whatever you want the value of this to be. The second to nth parameters of call are the parameters of the function you are invoking, separated by commas.

Note: call will immediately invoke the function that it is attached to.

const studentGreetings = {
    greeting: function () {
        return `${this.name} is a ${this.gender} studying ${this.department}`
    }
}

const student1 = {
    name: 'Solomon Ant',
    gender: 'male',
    department: 'Computer Science'
}

const student2 = {
    name: 'Amanda Brandy',
    gender: 'female',
    department: 'Medicine and Surgery'
}

console.log(studentGreetings.greeting.call(student1))
 //Output: Solomon Ant is a male studying Computer Science

console.log(studentGreetings.greeting.call(student2)) 
//Output: Amanda Brandy is a female studying Medicine and Surgery

With argument:

const classmates = {
    person1: "Kim",
    person2: "Khloe",
    person3: "Kylie"
}

function greet(myName) {
    return `Hi, my name is ${myName}, my classmates are ${this.person1}, ${this.person2}, and ${this.person3}.`;

}

console.log(greet.call(classmates, 'Amaka'))
// Output: Hi, my name is Amaka, my classmates are Kim, Khloe, and Kylie.

One of the most common use cases of the call method is to convert array-like object eg function arguments into actual array.

Eg:

function addArguments1() {
    return arguments.reduce((acc, next) => {
        return acc + next;
    }, 0);
}

console.log(addArguments1(1, 2, 3, 4, 5))
// Output: TypeError: arguments.reduce is not a function at addArguments1 


function addArguments2() {
    // using the slice method, make a copy of the array using call to set arguments as the context
    let argumentsArr = [].slice.call(arguments)

    return argumentsArr.reduce((acc, next) => {
        return acc + next;
    }, 0)
}

console.log(addArguments2(1, 2, 3, 4, 5))
// Output: 15

Apply()

Apply is almost identical to call, the only difference is that it takes two parameters at most. The first is what we want the value of the keyword this to be, the second is an array of arguments we want to pass the function in which we are changing the value of this. Apply also immediately invokes the function that it is attached to.

let aboutNick = {
    name: 'Nick',
    sayHi: function () {
        return `Hi ${this.name}`
    }
}

let aboutTom = {
    name: 'Tom'
}

console.log(aboutNick.sayHi()) // Output: Hi Nick
console.log(aboutNick.sayHi.apply(aboutTom)) // Output: Hi Tom

With arguments:

const person = {
    firstName: 'Adam',
    lastName: 'Levine'
}

function greet(greeting, message) {
    return `${greeting} ${this.firstName}. ${message}`;
}

console.log(greet.apply(person, ['Hello', 'How are you?']));
// Output: Hello Adam. How are you?

One of my favorite use cases of the apply method is when used with Math.max function. The Math.max function return the largest number among a group of numbers passed to it. When used with apply, you can pass in an array as parameter and get the largest number

Eg:

//Normal use of Math.max()

console.log(Math.max(1, 16, 22, 30, 23)) // Output: 30

//Math.max()- passing an array to it, we get NaN

console.log(Math.max([1, 16, 22, 30, 23])) // Output: NaN

//apply() to the rescue!

console.log(Math.max.apply(this, [1, 16, 22, 30, 23])) // Output: 30

Another helpful use case is in flattering an array of arrays:

Eg:

const arrays = [[29, 392, 18], ['Orange', 'Mango'], ['London', 'Canada']]

let flatenedArr = [].concat.apply([], arrays)

console.log(flatenedArr) // Output: [29, 392, 18, "Orange", "Mango", "London", "Canada"]

Bind()

Bind is also similar to call, the difference is instead of invoking the function immediately, it returns a function definition. Bind is powerful and is the building block for some advanced programming concepts eg Currying.

const person = {
    firstName: 'Adam',
    lastName: 'Levine'
}

function greet(greeting, message) {
    return `${greeting} ${this.firstName}. ${message}`;
}

let adamGreeting = greet.bind(person, 'Hi', 'How are you?')

console.log(adamGreeting);

// OutputπŸ‘‡πŸ‘‡: bind() returns a function definition

//  greet(greeting, message) {
//     return `${greeting} ${this.firstName}. ${message}`;
// }

console.log(adamGreeting());

// Hi Adam. How are you?

Bind is useful when we do not know the whole argument that will be passed to a function, which means we don't want to invoke the function right away, we just want to return a function definition.

Eg:

const student = {
    name: 'Christiene',
}

function calculation(x, y, z) {
    return `${this.name} calculated ${x + y + z}`;
}

let chrisCalc = calculation.bind(student, 2, 3)


console.log(chrisCalc);

// Output: πŸ‘‡πŸ‘‡

//calculation(x, y) {
//return `${this.name} calculated ${x + y}`;
//}

console.log(chrisCalc(10));

// Christiene calculated 15

Another use case is to set the context of the this keyword for a function that will be called later.

Eg:

In the code below, the this in the asynchronous setTimeOut() function, loses its context to the global object.

const aboutNick = {
    name: 'Nick',
    sayHi: function () {
        setTimeout(function () {
            console.log(`Hi ${this.name}`)
        }, 1000)
    }
}

aboutNick.sayHi() // Hi undefined (after one sec)

Using bind(), we can set the context of this to be the object it's in

const aboutNick = {
    name: 'Nick',
    sayHi: function () {
        setTimeout(function () {
            console.log(`Hi ${this.name}`)
        }.bind(this), 1000)
    }
}

aboutNick.sayHi() // Hi Nick (after one sec)

Important Note

With the introduction of ES6 arrow function, using bind in this way is no longer required since the arrow function retain the scope of this.

const aboutNick = {
    name: 'Nick',
    sayHi: function () {
        setTimeout(() => { console.log(`Hi ${this.name}`) }, 1000)
    }
}

aboutNick.sayHi() // Hi Nick

Summary of the call, bind, apply in a tabular form:

Name of MethodParametersInvoked Immediately?
CallthisArg, a, b, c, d, ...Yes
ApplythisArg, [a, b, c, d, ...]Yes
BindthisArg, a, b, c, d, ...No

The new keyword

Consider the following code:

function Person (name, age){
    this.name = name,
    this.age= age
}

Notice the capital letter P I am using to start the name of the function, it's just a convention for writing a special type of function called the constructor function.

Let's see what happens when we invoke this function.

let aboutJohn = Person('John Doe', 35)

console.log(aboutJohn);  // undefined

oops! we get undefined. Rightly so, there's no return keyword on the function, there's no explicit binding of the this keyword, the Person function isn’t called inside of a parent object, the default binding took place.

function Person(name, age) {
    this.name = name,
    this.age = age
}

let aboutJohn = Person('John Doe', 35)

console.log(aboutJohn);  // undefined

console.log(window.name) // John Doe
console.log(window.age) // 35

To change the value of this to the constructor function we use the new keyword. Let's see how that works below:

function Person(name, age) {
    this.name = name,
    this.age = age
}

let aboutJohn = new Person('John Doe', 35)

console.log(aboutJohn);  // {name: "John Doe", age: 35}

This is what the new keyword did above

  1. it creates a new empty object.
  2. sets the context of the this keyword to the created object .
  3. adds an implicit return this to the function.

Conclusion

You will encounter the this keyword a lot in object oriented programming and understanding how its value is assigned is very important to avoid sneaky bugs. In the article I explained the rules guiding the determining the value of this keyword. They are not official javascript rules, they are just a way to make you understand how the this keyword gets assigned a value.

Let me know what you think in the comments section!