this keyword has lead to a lot of bugs and troubles over the years. Nowadays there are a lot of post trying to explain how this works but I'll try to give my bit on this topic.
Before enter the good stuff we need to review a little of concepts to understand how JavaScript manages this depending on the context, so...
What we need to know before? π€
If you are familiar with the following terms maybe you can skip this section:
- Arrow functions
- Nested functions
- Methods
Arrow functions πΉ
Arrow functions are a new type of functions introduced in ES6. Basically they are a shortcut way of writing functions.
Here we can see their syntax:
const square = (a, b) => a * b;
And the same with regular functions
const square = function(a, b) { return a * b };
Nested functions π
Nested functions are nothing more than functions inside another functions.
This functions can access the variables defined inside the parent function and are only visible within the scope of the parent function.
function parent(){
function nestedFunction(){
console.log('Hey! I am a nested function');
}
nestedFunction();
}
Methods π¦
Methods are functions that are declared inside an object. This means that method are functions that need to be executed with an object as context.
const obj = {
x: 0,
y: 0,
square: function() {
return this.x * this.y;
},
}
Here we have a function that uses the object properties to get the square of these numbers. The function cannot work on its own.
Let's go with this keyword πͺ
We'll classify how this works like in the previous section.
Regular functions
Functions by default are bound to the global object.
function writeGlobal(){
console.log(this);
}
So this keyword in the previous example references to the global object.
We would bind this to another value (in these examples to a string object) using the following methods:
Bind
let sayHello = function() {
console.log(`Hello ${this}!`);
}
sayHello = sayHello.bind('Alex');
//Prints 'Hello Alex!'
sayHello();
Note: We have to assign sayHello function again because bind returns the function bound, not modifies the original function object
Call
let sayHello = function() {
console.log(`Hello ${this}!`);
}
//Prints 'Hello Alex!'
sayHello.call('Alex');
Apply
let sayHello = function() {
console.log(`Hello ${this}!`);
}
//Prints 'Hello Alex!'
sayHello.apply('Alex');
Arrow functions
When we use arrow functions this doesn't have the global object bound, instead it inherits the this value of the context within they are. Let's see some code.
let sayHello = function() {
const getHelloPhrase = () => {
return `Hello ${this}!`;
}
console.log(getHelloPhrase());
}
//Prints 'Hello Alex!'
sayHello.call('Alex');
Here, the arrow function uses the this value to generate a hello string. So when we bound the this value of sayHello function to 'Alex', the arrow function also has the this value bound to 'Alex'
Nested functions
Nested functions works a little bit different as the previous example even though it looks pretty similar. Let's see some code before explaining.
let sayHello = function() {
const getHelloPhrase = function() {
return `Hello ${this}!`;
}
console.log(getHelloPhrase());
}
//Prints 'Hello [object global]!'
sayHello.call('Alex');
Here we have a pretty similar code like before but we get a different result π
Why?
Unlike arrow functions, nested functions don't inherit the this value of the context they are declared.
Like we saw before, with regular functions the value is bound to the global object, and since nested functions are regular functions inside an others, they have the same rules.
So if we want nested functions to have the same behavior as arrow functions we need to take the following approach.
let sayHello = function() {
let getHelloPhrase = function() {
return `Hello ${this.toString()}!`;
}
getHelloPhrase = getHelloPhrase.bind(this);
console.log(getHelloPhrase());
}
//Prints 'Hello Alex!'
sayHello.call('Alex');
Now we have the same behavior because we are binding the nested function to the this value of the parent function (like arrow functions).
Methods
Earlier I've said that regular functions has this value as the global object by default, but there is an exception as we are going to see right now. So considering the following code.
let person = {
name: 'Alex',
sayHello: function() {
console.log(`Hello ${this.name}!`);
}
}
//Prints 'Hello Alex!'
person.sayHello();
You may think that this would print Hello undefined! since the global object doesn't have a property called name, but with objects we have a different behavior.
When we declare a function inside an object (a method) JavaScript automatically binds the object as the context of the function. So when we refer to this inside a method we are referring to the object that has the method. That explains why we cat get the property name of the person object with this.name.
The following code show a similar of what JavaScript does under the hood.
let person = {
name: 'Alex'
}
function sayHello() {
console.log(`Hello ${this.name}!`);
}
//Prints 'Hello Alex!'
sayHello.call(person);
So... If we use arrow functions instead of regular functions they behaves the same ? π€
Wrong β
You should't use arrow functions as member functions. Why?
As seen earlier, arrow functions behaves a little different to regular functions. They inherits this from the scope within they are declared.
See this page for more details.
Conclusion
The behavior of this keyword may appear a little bit hard to learn at first but if you break down the different behaviors depending on the context maybe you'll understand better how this works.
This is the way I understood the behavior and I hope that maybe this post will help you to understand too.
Top comments (0)