All about this keyword in Javascript
this in Global Context:
If we use run this statement in browser’s console, it would return true.
this === window //true
"use strict"
this === window //true
Bascially, in browser, this keyword when not used inside any execution context(function), will refer to window as it is the global object.
Now what about in nodejs run-time environment? It will still be refering to the global object, but in node the global object is literally named global.
this === global //true
However, that is only true within the node REPL(Read-evaluate-print-loop). Let’s execute the same line of code within a node module. Suddenly, we get false. This is because, in the top-level code of a node module, this is equivalent to module.exports.
// Custom Node module
console.log(this === global) //false
console.log(this === module.exports) //true
The node engine runs each module code inside of a REPL function. That REPL function is invoked with a this value set to module.exports.
this in Function Call:
Most of the times, the value of a function’s this argument is determined by when the function is called. That means, this value may be different each time the function is executed. If we’re not in strict mode, a plain function call sets the function’s this value to the global object.
function func() {
console.log(this === global); //true
}
func();
If we’re in strict mode, a plain function call sets the function’s this value to undefined. It doesn’t matter whether are not the cause side is in strict mode. It all depends and whether or not the function is in strict mode.
"use strict";
function func() {
console.log(this === undefined); //true
}
func();
Let us now see, why it makes sense for the this value to be undefined in strict mode. Here is a simple person function that accepts two parameters and initializes two properties. Note that this piece of code is not in strict mode. If we invoke the person function using a plain function call, the result probably isn’t what we intended. The person variable holds the value undefined.
Since we’re not in strict mode, this within the function refers to the global object. Therefore, we assigned first name and last name to global. This is unfortunate, because it’s almost never the desired behavior.
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const person = Person("Jason", "Tom");
console.log(person); //undefined
console.log(global.firstName); //Jason
console.log(global.lastName); //Tom
Let’s contrast this with strict mode where the this value within the function is set to undefined. Now, we get an error when invoking the person function using a plain function call, because we cannot assign properties to undefined. That prevents us from accidentally creating global variables, which is a good thing. The uppercase function name is a hint to us. That was supposed to call it as a constructor. We do that using the new operator.
const person = new Person("Jason", "Tom");
console.log(person); //{firstName: Jason, lastName: Tom}
console.log(global.firstName); //undefined
console.log(global.lastName); //undefined
Now, we correctly construct a new person, and we also no longer pollute the global object with the first name and last name properties.
this in Constructor Call:
A constructor is a function that creates an instance of a class which is typically called an “object”. In JavaScript, a constructor gets called when you declare an object using the new keyword. When a function is executed with new, it does the following steps:
- A new empty object is created and assigned to
this
. - The function body executes. Usually it modifies
this
, adds new properties to it. - The value of
this
is returned.
function Person(firstName, lastName) {
// this = {}; (implicitly)
// add properties to this
this.firstName = firstName;
this.lastName = lastName;
// return this; (implicitly)
}
const person = new Person("Jason", "Tom"); //{firstName: Jason, lastName: Tom}
Methods in constructor:
Using constructor functions to create objects gives a great deal of flexibility. The constructor function may have parameters that define how to construct the object, and what to put in it. Of course, we can add to this
not only properties, but methods as well.
function Person(name) {
this.name = name;
this.sayHi = function() {
alert( "My name is: " + this.name );
};
}
let adam= new Person("Adam");
adam.sayHi(); // My name is: Adam
this in Method Call:
When a function is called as a method of an object, that function’s this argument is set to the object, on which the method is called on. Here, we’re calling person.sayHi, and therefore this value within the sayHi method refers to person.
const person = {
firstName: "John",
sayHi() {
console.log(`Hi, my name is ${this.firstName}!`);
}
};
person.sayHi(); //"Hi, my name is John!"
We say that person is the receiver of the method call. This receiver mechanism is not affected by where the function was defined. For example, we could have defined the function separately, and could have later attached it to person. We’re still writing person.sayHi, and therefore person will still be the receiver of the method call.
One of the most common problems developers face is when a method loses its receiver. Consider our example again. If we store a reference to the sayHi method in a variable, and later call that variable as a function, our intended receiver is lost. Within the sayHi function, this will refer to the global object, and not to person.
This is because we now have a plain function call, and we’re not in strict mode. Losing a receiver this way usually happens when we pass a method as a callback to another function. For example, setTimeout. setTimeout will call our function with this set to the global object, which is probably not what we intended here.
let person = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000); // Hello, undefined!
One solution to this problem is to add a wrapper function. That way, person.sayHi is still invoked as a method, and doesn’t lose its intended receiver. Another solution is the bind method, which allows us to tie our this to a specific object.
Hard-Bind a Function’s this Value with the .bind() Method
When we try to pass a method as a call back to another function, we often times lose the intended receiver of the method. The time out cause the call back with the this argument set to the global object, which is not what we intended.
We can solve this problem by using the bind method. Bind will create a new sayHi function and permanently set as this value to person. This mechanism is sometimes referred to as hard binding. We can then pass the hard bound function to set time out.
const person = {
firstName: "John",
sayHi() {
console.log(`Hi, my name is ${this.firstName}!`);
}
};
setTimeout(person.sayHi, 1000); //"Hi, my name is undefined!"
setTimeout(person.sayHi.bind(person), 1000); //"Hi, my name is John!"
Now, sayHi is called with the correct this argument. Even if we extract our bound function into a variable, and invoke that variable as a function, the this argument is still tied to person. Once the function is bound, it’s this argument can no longer be changed, not even by call or apply.
What does bind return? Well, it returns another function. Finally, bind allows us to fix a number of arguments ahead of time when we bind the original function. When our new function is invoked, the two argument lists are combined. We are effectively doing partial application here.
Specify this using .call() or .apply()
How would we call sayHi with person as a receiver explicity? We can do this using the call method. Call is defined on the function prototype, and therefore available on every function.
function sayHi(lastname) {
console.log(`Hi, my name is ${this.firstName}, ${lastname}!`);
}
const person = {
firstName: "Jane"
};
sayHi.call(person, 'Adams'); // "Hi, my name is Jane, Adams!"
As we can see this within this sayHi method, refers to the value we provide it explicitly we have the first argument. This argument is often called this arg. Alternatively, we can use the apply method which is also defined on the function prototype.
function sayHi(lastname) {
console.log(`Hi, my name is ${this.firstName}, ${lastname}!`);
}
const person = {
firstName: "Jane"
};
sayHi.apply(person, ['Adams']); // "Hi, my name is Jane, Adams!"
What’s the difference between call and apply? In addition to the this arg, we can also specify arguments that we want to pass to the function. Call and apply accept this arguments in a slightly different way. The first argument is this arg and all arguments after that are the arguments that we want to pass to the function. We simply list them separated by comma. Instead of using call, we could’ve also use apply.
In case of apply method, the first argument is the this arg. Now, the second argument is an array like object that contains all the arguments that we want to pass to the function. In the end, all of the above select the same slice from the array.
Unfortunately, there is a problem, if you are using call or apply outside of strict mode. If you set the this arg to null or undefined, the JavaScript engine will ignore that value and use the global object instead. In strict mode, that doesn’t happen.
this with an Arrow Function:
When it comes to the “this” argument, arrow functions behave differently from function declarations or function expressions. An arrow function does not have its own “this.” Instead, it uses the “this” from its enclosing execution context.
The “this” binding of an arrow function cannot be set explicitly. If we try to pass a “this” arg to an arrow function using call, apply, or bind, it will be ignored. Put differently, it doesn’t matter how we call an arrow function. It’s “this” argument will always refer to the “this” value that was captured when the arrow function was created.
We also cannot use an arrow function as a constructor. In a constructor, we usually assign properties to “this.” It wouldn’t make sense to construct an arrow function just to have it assign properties to the “this” of the enclosing execution context.
The transparent “this” binding of arrow functions is particularly useful when we want to access “this” within a callback. Consider this Counter object. Let’s say we want to increment the count property by one every second. We could use setInterval() and provide a callback which increments this.count.
const counter = {
count: 0,
incrementPeriodically() {
setInterval(function () {
console.log(++this.count); //null null null
}, 1000);
}
};
counter.incrementPeriodically();
However, if we run this program, we’ll see that it doesn’t work. The “this” binding within our function expression doesn’t refer to our Counter object. It refers to the global object because that’s how setInterval() works. We’re trying to increment the count property of the global object.
Let’s convert our function expression to an arrow function. Now, our callback uses the “this” binding from the increment periodically method. Since we invoked increment periodically using the method syntax, “this” is set to counter. Everything works out.
const counter = {
count: 0,
incrementPeriodically() {
setInterval(() => {
console.log(++this.count); // 1 2 3 4 5
}, 1000);
}
};
counter.incrementPeriodically();