Introduction
The 'this' keyword is one such concept that has many layers to it and often a JavaScript newbie can find it overwhelming.
I can assure you that after going through this article, you'll be able to identify what 'this' means based on your function's execution context.
In this article, we will be covering the following topics:
- Implicit binding
- Explicit binding
- 'new' binding
- Lexical binding
- window binding
Why does 'this' exist in JavaScript?
Before we get into the details, let us try and understand why 'this' exists in JavaScript. I think it'll be easier to explain it using a code example:
function sayHello(name) {
console.log(`Hello ${name}`);
}
The above is a function declaration of 'sayHello' that accepts 'name' as the argument. You'll not be able to say what will display on the console unless the function is invoked and a value is passed to the 'name' argument is passed to the function.
sayHello('Skay');
//Output -> Hello Skay
When the function 'sayHello' is invoked with 'Skay', the output 'Hello Skay' displays on the console.
You can think of 'this' keyword behavior to be very similar to that of the above function argument example. The value of 'this' keyword will vary based on how the function is invoked.
Simply stated, the 'this' keyword exists in order to allow the user to decide which object should be focal when invoking a function or a method.
Implicit Binding
Implicit Binding rule states that in order to figure out what 'this' keyword references, first, look to the left of the dot of the function that is invoked.
Let us look at a simple example to figure out what the above means:
const user = {
name: 'Skay',
age: 38,
sayHello: function () {
console.log(`Hello ${this.name}`); // The 'this' keyword references the 'user' object
},
};
//Note: Implicit Binding Rule
//To the left of the function call 'sayHello()' the user object is present
//Hence 'this' in this function invocation refers to the 'user' object
console.log(user.sayHello());
//Output -> Hello Skay
Things to Note:
- The 'user' object contains the 'sayHello' function which references the 'this' keyword.
- To the left of the function call 'sayHello' function is the user object and by implicit binding rule, the 'this' keyword references the 'user' object.
${this.name} -> gets translated to -> ${user.name}
So, what happens when there isn't any dot to the left of the function call? That brings us to the next concept, explicit binding.
Explicit Binding
Let us take the above example and move the function 'sayHello' out of the 'user' object.
const user = {
name: 'Skay',
age: 38,
};
//The function sayHello() moved out of the user object
const sayHello = function () {
console.log(`Hello ${this.name}`);
};
Now, how do we bind the 'user' object to the 'sayHello' function? We have the 'call' function to our rescue.
Call
'call' is a property of every function and the first argument you pass to 'call' will reference 'this' keyword within the function through Explicit binding.
//Using 'call' keyword, we are passing the 'user' object as an argument.
//This explicitly binds the 'user' object to the 'this' keyword
sayHello.call(user);
As you can see from the above code, the 'user' object is explicitly bound to the 'sayHello' function using the 'call' property.
Apply
Let us take another example of the below function 'polyglot' that accepts arguments language1 and language2.
const user = {
name: 'Skay',
age: 38,
};
const polyglot = function (language1, language2) {
console.log(
`Hello, I'm ${this.name} and I know ${language1} and ${language2}`
);
};
Now, we know that using the 'call' property, we can bind the user object to the function explicitly and pass the arguments as shown below.
polyglot.call(user, 'JavaScript', 'Java');
//Output -> Hello, I'm Skay and I know JavaScript and Java
Now, the 'apply' keyword gives you the flexibility to send multiple parameters using an array instead of individual values.
//Define the array that needs to be passed as arguments to the function
const languages = ['JavaScript', 'Java'];
//Use the apply keyword to explicitly bind the user object & pass the languages array
polyglot.apply(user, languages);
//Output -> Hello, I'm Skay and I know JavaScript and Java
In other words, 'apply' is exactly the same as 'bind' except for the fact that it provides you the flexibility to pass a single array which will spread each element of the array as arguments to the function.
Bind
So far, we have seen both 'call' and 'apply' keywords under the explicit binding. The last one is 'bind', which is again exactly the same as 'call', but it returns a function that can be invoked at a later time than running it right away.
Let us look at the first example and find out how 'bind' keyword works with it.
const user = {
name: 'Skay',
age: 38,
};
const sayHello = function () {
console.log(`Hello ${this.name}`);
};
//The 'bind' keyword returns a function instead of invoking the function immediately
const laterFn = sayHello.bind(user);
//The function is invoked to display the greeting on the console
laterFn(); //Output -> Hello Skay
That concludes all the various ways of how to identify 'this' keyword with explicitly binding.
new Binding
In JavaScript, you can define a constructor function through which other objects can be created. Let us look at the below code example.
//Fruit is a constructor which accepts name and color as arguments
//Whenever the function 'Fruit' is invoked using the new keyword a new object is created
//The new object created will reference the 'this' keyword
const Fruit = function (name, color) {
this.name = name;
this.color = color;
this.greet = function () {
console.log(`Hello, I'm ${this.name} and my color is ${this.color}`);
};
};
//Apple object will be created with the this.name & this.color referencing Apple & Red
const apple = new Fruit('Apple', 'Red');
//Banana Object will be created with the this.name & this.color referencing Banana & Yellow
const banana = new Fruit('Banana', 'Yellow');
//Greet function will be invoked with 'apple' reference
apple.greet(); //Output -> Hello, I'm Apple and my color is Red
//Greet function will be invoked with 'banana' reference
banana.greet(); //Output -> Hello, I'm Banana and my color is Yellow
Things to Note:
- When the constructor function 'Fruit' is invoked with the 'new' keyword, a new object will be created.
- The 'this' keyword will naturally reference the newly created object during the assignment.
Lexical Binding
Lexical binding is probably easier explained through a code example.
//A Constructor Function that accepts 'name' & 'hobby' array
const Person = function (name, hobbies) {
this.name = name;
this.hobbies = hobbies;
this.display = function () {
//hobbies array iterated through the map function
this.hobbies.map(function (hobby) {
//Inside this anonymous function, the 'this.name' cannot be found
//Reason -> A new scope gets created within anonymous function
console.log(`My name is ${this.name} & my hobby is ${hobby}`);
});
};
};
const hobbies = ['Sports', 'Music'];
const me = new Person('Skay', hobbies);
me.display();
/** Output
* My name is & my hobby is Sports
* My name is & my hobby is Music
*/
Let us look at the above code example in detail:
- The 'Person' is a constructor function that accepts both 'name' and 'hobbies' array.
- We are invoking the constructor function with the name 'Skay' and the 'hobbies' array 'Sports' and 'Music'.
- When the 'display' function is invoked, the hobbies.map function is invoked.
- There's an inner anonymous function within the hobbies.map function which creates its own scope and hence when '${this.name}' is encountered, it tries to search for 'name' within the new context and does not find it.
- Hence the output does not print the 'name' field.
Ideally, the expectation would have been lookup to the parent scope and find the 'name' variable within the Person function. This was addressed when the ES6 arrow function was created.
However, we can still address this problem by using the 'bind' keyword as shown in the code example below.
Using bind
The example is exactly the same as above, but we are using the 'bind' keyword to explicitly bind the 'this' keyword to the anonymous function. If you run the function, you should see the 'this.name' would be properly referenced and the output printing the name as 'Skay'
const Person = function (name, hobbies) {
this.name = name;
this.hobbies = hobbies;
this.display = function () {
this.hobbies.map(
function (hobby) {
console.log(`My name is ${this.name} & my hobby is ${hobby}`);
//By 'explicit' binding the 'this' object the 'this.name' will be referenced
}.bind(this)
);
};
};
const hobbies = ['Sports', 'Music'];
const me = new Person('Skay', hobbies);
me.display();
/** Output
* My name is Skay & my hobby is Sports
* My name is Skay & my hobby is Music
*/
ES6 - Arrow Function
The same code can be simply addressed by using an Arrow Function. This is recommended and preferred and is compatible with all modern browsers.
We just removed the 'function' keyword and replaced it with the 'arrow' symbol to make it an arrow function.
const Person = function (name, hobbies) {
this.name = name;
this.hobbies = hobbies;
this.display = function () {
this.hobbies.map((hobby) => {
console.log(`My name is ${this.name} & my hobby is ${hobby}`);
});
};
};
const hobbies = ['Sports', 'Music'];
const me = new Person('Skay', hobbies);
me.display();
/** Output
* My name is Skay & my hobby is Sports
* My name is Skay & my hobby is Music
*/
Window Binding
Finally, we've come to the window binding which is probably also referred to as the global context.
function displayFruit() {
console.log(`Hello ${this.fruit}`);
}
displayFruit();
//Output -> Hello Undefined
In the above code example, there's no dot operator and hence no implicit binding happened. Also, since the call, apply, bind was used to explicitly bind the function with an object, the 'this' keyword gets bound to the global object which is the 'window' object.
If you define the 'fruit' variable to the window object and run the above code, it'll print the name of the fruit as shown below.
function displayFruit() {
console.log(`Hello ${this.fruit}`);
}
window.fruit = 'Peach';
displayFruit();
//Output -> Hello Peach
Strict Mode
If you have the “strict mode” enabled, then the JavaScript will not allow the reference of the 'this' keyword to default to the window object. It will just keep “this” as undefined as shown in the code example below.
'use strict'
function displayFruit() {
console.log(`Hello ${this.fruit}`);
}
window.fruit = 'Peach';
displayFruit();
//Output -> Hello undefined
Conclusion
That's it, folks! We've covered all that's there to find out how to identify the 'this' object in JavaScript.
So, whenever you see a 'this' keyword inside a function, then you'll apply the following rules:
- Check where the function was invoked.
- If there is an object to the left of the dot, then, that's what the “this” keyword is referencing. If not, continue to the next step.
- If the function was invoked with a “call”, “apply”, or “bind” keyword, then it’ll explicitly state what object the “this” keyword is referencing. If not, continue to the next step.
- Was the function invoked using the “new” keyword? If so, the “this” keyword is referencing the newly created object. If not, continue to the next step.
- Is “this” inside of an arrow function? If so, its reference may be found lexically in the enclosing (parent) scope. If not, continue to the next step.
- If you are in 'strict mode', then “this” keyword is undefined. If not, continue to the next step.
- The “this” keyword defaulted to the global “window” object.
Hope you enjoyed this article! Don't forget to subscribe to my newsletter and connect with me on Twitter @skaytech