There is a lot of confusion around the this
keyword in JavaScript. The way this
is described in the official MDN web docs is not very intuitive. If you feel similarly, let's learn about this
keyword using some practical examples.
What is this
?
The this
keyword refers to the context where a piece of code should run. If we console log the following in our browser what do we get?
console.log(this)
The result of running the above code is the entire browser Window
object. If you run it in Node instead, you get the global object. Therefore, this
represents the context of where the code is defined.
Let's take a look at an example. Below we have an object called student
and inside the object we will define a function called testFunction
.
const student = {
name: "Jon",
phone: 415887231,
testFunction: function () {
console.log(this);
},
};
student.testFunction();
Output:
{
name: "Jon"
phone: 415887231
testFunction: ƒ testFunction()
}
We are console logging this
from inside the testFunction
. Since testFunction
is defined inside the student
object we get the entire student
object as an output. The student
object is the context where the function is defined.
this
and the strict
mode in Node
When you are using the strict mode in Node the behaviour of this
is different. The following example demonstrates the behaviour.
'use strict';
// Top level
console.log(this); // {}
function strictFunction() {
console.log(this); // undefined
}
strictFunction();
In the top level in a module this
is still the global object. However, inside a function this
is now undefined.
Why would this be useful? There are many use cases of the strict
mode. The following are some of them.
Avoiding accidental Globals:
'use strict';
x = 10; // ReferenceError: x is not defined
Assigning a value to an undeclared variable creates a global variable. Strict mode prevents this.
Duplicates in object literals:
'use strict';
var obj = {
prop: 1,
prop: 2 // SyntaxError: Duplicate data property in object literal
};
Strict mode throws an error for duplicate property names in object literals.
Catch silent errors:
In non-strict mode, assigning a value to a non-writable property does nothing silently. In strict mode, it throws an error.
// non strict mode
var obj = {};
Object.defineProperty(obj, 'x', { value: 42, writable: false });
obj.x = 9; // Fails silently, obj.x remains 42
console.log(obj.x); // 42
'use strict';
var obj = {};
Object.defineProperty(obj, 'x', { value: 42, writable: false });
obj.x = 9; // TypeError: Cannot assign to read only property 'x' of object '#<Object>'
How does this
behave inside regular functions and arrow functions?
Understanding how this
behaves in different scenarios is important for writing clean and predictable JavaScript code, especially when dealing with object methods and callbacks.
The following example shows the behaviour of this
in a regular callback function that is defined inside a function.
const student = {
name: "Jon Doe",
printAllCourses: function () {
const subjects = ["cs-101", "cs-202", "econ-101"];
subjects.forEach(function (sub) {
console.log(sub);
console.log("Student Name", this.name);
});
},
};
student.printAllCourses();
// outputs
/**
cs-101
Student Name undefined
cs-202
Student Name undefined
econ-101
Student Name undefined
**/
Notice the callback function for forEach
is trying to access the name
property but is returning undefined.
Now turn the callback function into an arrow function and run the code again.
const student = {
name: "Jon Doe",
printAllCourses: function () {
const subjects = ["cs-101", "cs-202", "econ-101"];
subjects.forEach((sub) => {
console.log(sub);
console.log("Student Name", this.name);
});
},
};
student.printAllCourses();
// outputs
/**
cs-101
Student Name Jon Doe
cs-202
Student Name Jon Doe
econ-101
Student Name Jon Doe
**/
Notice that this time it is able to access the name property as expected.
What’s actually happening here?
this
inside a regular function depends on how the function is called. When a regular function is used as a callback, only then this
refers to the global object (window
in browsers), which is the case in the above example. Alternatively, if the function is not called as a callback function it can access the parent objects' properties using this
. So, basically, a callback function is always treated as a function that is defined in the global context.
Arrow functions, on the other hand, do not have their own this
context. Instead, they lexically inherit this
from the surrounding code. This simply means that the arrow function has access to the properties of the object it is defined in. So this
inside an arrow function is the same as this
outside the arrow function.
const student = {
name: "Jon Doe",
printAllCourses: function () {
const subjects = ["cs-101", "cs-202", "econ-101"];
console.log("outside arrow func", this); // this here is the same this inside the callback arrow function below
subjects.forEach((sub) => {
console.log("inside arrow func", this); // same this as above
});
},
};
student.printAllCourses();
Hey if you are enjoying this post and want to see more like it don't forget to give me a follow on dev.to, X or LinkedIn
How does this
work in es6 class context?
In ES6 classes, this
keyword is more predictable and consistent compared to traditional JavaScript functions. When using this
inside methods of a class, it generally refers to the instance of the class on which the method was called.
Consider a simple ES6 class:
class Person {
constructor(name) {
this.name = name;
}
printName() {
console.log(this.name);
}
}
const person = new Person('Alice');
person.printName(); // Outputs: Alice
In the example this
inside the constructor
method refers to the new instance of the class being created.
this
inside the printName
method refers to the instance of the Person
class on which the printName
method was called.
Now let’s take a look at an example that shows potential issues when using class methods as callbacks and how to address them:
class Person {
constructor(name) {
this.name = name;
}
printName() {
console.log(this.name);
}
printNameDelayed() {
setTimeout(function() {
console.log(this.name);
}, 1000);
}
printNameDelayedWithArrow() {
setTimeout(() => {
console.log(this.name);
}, 1000);
}
printNameDelayedWithBind() {
setTimeout(function() {
console.log(this.name);
}.bind(this), 1000);
}
}
const person = new Person('Bob');
person.printName(); // Outputs: Bob
person.printNameDelayed(); // Outputs: undefined (or error in strict mode)
person.printNameDelayedWithArrow(); // Outputs: Bob
person.printNameDelayedWithBind(); // Outputs: Bob
Notice, in printNameDelayed
method the callback function passed to setTimeout
is a regular function, so this
inside it does not refer to the instance of Person
. Instead, it refers to the global object (or undefined
in strict mode).
Using an arrow function as the callback preserves the this
context from the enclosing method, so this
refers to the instance of Person
. The printNameDelayedWithArrow
is a possible solution to the callback problem.
Another way to solve this callback problem is through binding. In printNameDelayedWithBind
we explicitly bind the callback function to this
. It ensures that this
inside the callback refers to the instance of Person
.
Final words
I hope, this article gave you a better understanding of this
.
Think of this as JavaScript's mischievous trickster—always keeping you on your toes. But now, you're in on the joke. Keep experimenting and soon this will be your trusty sidekick, not your nemesis.
So next time someone says JavaScript is confusing, just wink and say, "I know exactly who this
is!" Go forth and conquer, and may the this
be with you!
Top comments (0)