DEV Community

Cover image for Mastering the JavaScript "this" Keyword
Aakash Srivastav
Aakash Srivastav

Posted on

Mastering the JavaScript "this" Keyword

“this” keyword allows you to decide which object should be focal when invoking a function or a method.

The this keyword is a very important concept in JavaScript, and also a particularly confusing one to both new developers and those who have experience in other programming languages. In JavaScript, this is a reference to an object. The object that this refers to can vary, implicitly based on whether it is global, on an object, or in a constructor, and can also vary explicitly based on usage of the Function prototype methods bind, call, and apply.

Five types of binding

  • The ‘this’ keyword is probably one of the most misunderstood aspects of JavaScript.

  • The first thing to understand regarding this keyword is to understand its purpose, or what it is that ‘this’ keyword allows us to do in JavaScript.

    • It allows us reuse functions with different contexts
    • It allows us to decide which objects should be focal when invoking a function or a method.

The first thing we need to ask when using this keyword is -
Where is this function invoked?

  • Because whenever you’re trying to find out what this keyword is you have to look at when the function was invoked… not when it was defined, but specifically when it was invoked.
  • We won’t know what this keyword is in a function until that function is invoked.

  • Now that you know the first step to figuring out what the this keyword is referencing is to look at where the function is being invoked, what’s next? To help us with the next step, we’re going to establish 5 rules or guidelines.

1) Implicit Binding
2) Explicit Binding
3) new Binding
4) Lexical Binding
5) window Binding

1. Implicit Bindind

  • Implicit Binding is the most common rule and will be found in about 80% of use cases when trying to figure out what this is.

Example 1:

// Implicit Binding
// Left of the Dot at Call Time
var me = {
  name: 'Aakash',
  age: 22,
  sayName: function() {
    console.log(this.name);
  }
};

me.sayName(); // Aakash
  • Implicit Binding says that in order to find the this keyword we look to the left of the dot of the function invocation. That’s what the this keyword is going to reference.
me.sayName();
  • In the code above, we see the sayName() invocation and look to the left of the dot. The me object is what this reference.

Example 2:

// Implicit Binding
// Left of the Dot at Call Time
var sayNameMixin = function(obj){
  obj.sayName = function(){
    console.log(this.name);
  }
}

var me = {
  name: 'Aakash',
  age: 22
};

var you = {
  name: 'Shehan',
  age: 21
}

sayNameMixin(me); 
sayNameMixin(you); 
  • When we pass both of these objects into our mixin it decorates them with a new sayName() property.
  • Then when we invoke each we look to the left of the dot to see what this references.
.
.
// Now it will print
me.sayName(); // Aakash
you.sayName(); // Shehan

Example 3:

// Implicit Binding
// Left of the Dot at Call Time
var Person = function(name, age) {
  return {
    name: name,
    age: age,
    sayName: function() {
      console.log(this.name);
    }
  }
}

var jim = Person('Aakash', 22);
jim.sayName(); // Aakash

But what if we made this a bit more complex.:

// Implicit Binding
// Left of the Dot at Call Time
var Person = function(name, age) {
  return {
    name: name,
    age: age,
    sayName: function() {
      console.log(this.name);
    },
    mother: {
      name: 'Sandhya',
      sayName: function(){
        console.log(this.name);
      }
    }
  }
}

var jim = Person('Aakash', 22);
jim.sayName(); // Aakash
jim.mother.sayName() // Sandhya

Once again we look to the left of the dot of the function invocation to get the object this refers to.

  • This seems deceptively easy, and because it is very straight-forward, whenever you get into situations where you need to find out what this is, the very first thing you should do is look at when the function was invoked and then look to the left of that function to find out what this is referencing.

2. Explicit Binding

Uses call, apply, or bind:

a) Example 1 - call

  • Let’s change things around so that sayName is just a function on the global scope but what we want to do is still call the function in the context of the stacey object.
// Explicit Binding
// call, apply, bind
var sayName = function() {
  console.log('My name is ' + this.name);
}

var aakash = {
  name: 'Aakash',
  age: 22
}

sayName.call(aakash) // Aakash
  • What we can do is type the function name and then use the call method, which is available to every function, to do just that.

  • The first argument that it takes in is the context that you want to call the function from.

So now the sayName function is going to be invoked but the this keyword inside of sayName will now reference the aakash object.

So in this example we’re explicitly stating what the this keyword is when we use call. It is the very first argument we pass to call.

  • .call() provides a new value of this to the function/method.

  • With call, you can write a method once and then inherit it in another object, without having to rewrite the method for the new object.

  • Additional arguments to the function are passed in one by one after the first argument.

Example 2 - call with args

  • Now if we want to pass a few more parameters to sayName we can do that.

  • Let’s create an array and then pass the array elements to the function.

  • The very first argument in .call() is the context. Every argument after that will be passed to the function.

// Explicit Binding
// call, apply, bind
var sayName = function(lang1, lang2, lang3) {
  console.log(`My name is ${this.name}. I know ${lang1}, ${lang2}, ${lang3}.`);
}

var aakash = {
  name: 'Aakash',
  age: 22
}

var languages = ['JavaScript', 'CSS', 'Python'];

// sayName.call(aakash, languages[0], languages[1], languages[2]);
// You can also use "spread" operator
sayName.call(aakash, ...languages); 
  • So, we are invoking sayName in the context of aakash and we are also passing along three arguments.

Example 3 - apply :

  • Next what we could do is rather than pass in the arguments one by one, we could pass them in as an array.
sayName.apply(aakash, languages);
  • This is exactly what .apply() does. It allows us to pass in the arguments as an array.

.apply() provides a new value of this to the function/method.

Instead of having to pass additional arguments one by one, you can pass them in as an array after context which is the first argument.

Example 4 - bind :

  • The .bind() is almost the same thing as .call() except there’s one thing that’s different.

What .bind() will do is return us a new function instead of invoking the original function.

Looking at our code from before.

// sayName.bind(aakash, languages[0], languages[1], languages[2]);
var newFn = sayName.bind(aakash, ...languages);
  • Now, instead of invoking sayName, it’s just going to bind this to aakash, pass in the languages arguments, and return a brand new function which we can call later.

Now we can invoke the new function with newFn().

SUMMARY

  • call, apply, and bind allow us to explicitly state what the this keyword is going to be in any given function.
  • call and apply behave in the exact same way. They will immediately invoke the function.
  • call requires additional arguments to be passed in one by one
  • apply allows you to pass in the arguments as an array
  • bind is the same as call except that instead of immediately invoking the function it returns a brand new function that can be invoked later

3. New Binding

  • Whenever you invoke a function with the new keyword, under the hood, the JavaScript interpretor will create a brand new object for you and call it this.
  • So, naturally, if a function was called with new, the this keyword is referencing that new object that the interpretor created.

Example:

function User (name, age) {
  /*
    Under the hood, JavaScript creates a new object called `this`
    which delegates to the User's prototype on failed lookups. If a
    function is called with the new keyword, then it's this new object
    that interpretor created that the this keyword is referencing.
  */

  this.name = name
  this.age = age
}

const me = new User('Aakash', 22)
// "this" will now refer to "me" always.

4. Lexical Binding

  • Arrow function allow you to write functions in a more concise format.
  • Even more than conciseness, arrow functions have a much more intuitive approach when it comes to this keyword. Unlike normal functions, arrow functions don’t have their own this. Instead, this is determined lexically. That’s a fancy way of saying this is determined how you’d expect,

Example :

const user = {
  name: 'Aakash',
  age: 22,
  languages: ['JavaScript', 'CSS', 'Python'],
  greet() {
    const hello = `Hello, my name is ${this.name} and I know`

    const langs = this.languages.reduce(function (str, lang, i) {
      if (i === this.languages.length - 1) {
        return `${str} and ${lang}.`
      }

      return `${str} ${lang},`
    }, "")

    alert(hello + langs)
  }
}
  • You’ll notice it’s throwing the error Uncaught TypeError: Cannot read property 'length' of undefined.
  • According to our error, this.langauges is undefined.It’s not referencing user.

Steps to find the this context:

  • First, we need to look at where the function is being invoked. Wait? Where is the function being invoked?
  • The function is being passed to .reduce so we have no idea.
  • We never actually see the invocation of our anonymous function since JavaScript does that itself in the implementation of .reduce. That’s the problem.
  • We need to specify that we want the anonymous function we pass to .reduce to be invoked in the context of user.
  • That way this.languages will reference user.languages.
  • As we learned above, we can use .bind.
const user = {
  name: 'Aakash',
  age: 22,
  languages: ['JavaScript', 'CSS', 'Python'],
  greet() {
    const hello = `Hello, my name is ${this.name} and I know`

    const langs = this.languages.reduce(function (str, lang, i) {
      if (i === this.languages.length - 1) {
        return `${str} and ${lang}.`
      }

      return `${str} ${lang},`
    }.bind(this), "")

    alert(hello + langs)
  }
}
  • So we’ve seen how .bind solves the issue, but what does this have to do with arrow functions. Earlier I said that with arrow functions "this is determined lexically. That’s a fancy way of saying this is determined how you’d expect, following the normal variable lookup rules."
  • If we re-write the code above and do nothing but use an anonymous arrow function instead of an anonymous function declaration, everything “just works”.
const user = {
  name: 'Aakash',
  age: 27,
  languages: ['JavaScript', 'CSS', 'Python'],
  greet() {
    const hello = `Hello, my name is ${this.name} and I know`

    const langs = this.languages.reduce((str, lang, i) => {
      if (i === this.languages.length - 1) {
        return `${str} and ${lang}.`
      }

      return `${str} ${lang},`
    }, "")

    alert(hello + langs)
  }
}

5. window Binding

Example:

// window Binding
var sayAge = function() {
  console.log(this.age);
};

var me = {
  age: 22
}
  • If we wanted to call sayAge in the context of me we’d have to do this.
sayAge.call(me); 
  • But if we don’t do this but instead just call sayAge we get undefined.
  • The reason is if we invoke a function that uses the this keyword but doesn’t have anything to the left of the dot, it’s not using the new binding, and it’s not using call, apply, or bind, then the this keyword is going to default to the window object.
  • So if we decide to add a property of age to the window object we will get that result back.
window.age = 22

function sayAge () {
  console.log(`My age is ${this.age}`)  
}

sayAge()  // 22

Binding rules recap

The four rules in a quick recap.

1) Implicit Binding - look to the left of the dot at call time
2) Explicit Binding - tells a function what the context of the this keyword is going to be using call, apply, or bind
3) new Binding - is whenever you have a function invoked with the new keyword where the this keyword is bound to the new object being constructed
4) lexical Binding - this is determined lexically i,e this is determined how you’d expect, following the normal variable lookup rules.
5) window Binding - if none of the previous rules apply then the this keyword is going to default to the window object unless you’re in strict mode in which case it will be undefined

Now , you should be able to determine the value of this in your programs :)

If you have any question regarding this or anything I should add, correct or remove, feel free to comment, email or DM me. Thanks !!!

Discussion (1)

Collapse
arpan_banerjee7 profile image
Arpan Banerjee

Good content!