JavaScript: All about JS keyword this
Intro
As a frontend engineer, this is a keyword that you will see very often. It is a pretty fundamental concept, yet an extremely important onf if you want to become better at javascript, or as a frontend engineer. In this article, I am going to cover all the knowledge that is entailed to this.
global environment
For convenience, we'll refer global environment as ge below.
Basically, if you call this directly in ge, you will get a window object. And it is strictly equal.
this === window; // true
And of course, the code below remains true:
this.document === document;
Furthermore, if you try to add a new attribute to window directly by this, it is also feasible. For example:
this.myName = "Tris";
console.log(myName); // Tris
console.log(window.myName); // Tris
this.myName === window.myName; // true
this.myName === myName; // true
Even if you apply the strict mode by adding use strict at the top of file, it still behaves as if this basically points to the window object.
functional environment
For convenience, we'll refer functional environment as fe below.
In fe, if you call this, its reference depends on how you call this function. If you call the function directly, this will refer to the window object directly, this is called a Sample Call.
For example:
function fn() {
return this;
}
fn() === window; // true
However, most of the time when we're actually developing, we have a preference to avoid calling a function directly(Sample Call). Take an example of Vue developing:
const app = new Vue({
el: "#app",
data: {
myName: "Ray",
},
methods: {
getName() {
console.log(this.myName); // Ray
},
},
created() {
this.getName();
},
});
Looks just fine, right? But let's see what will happen by just changing the code a little bit:
const app = new Vue({
el: "#app",
data: {
myName: "Ray",
},
methods: {
getName() {
const array = [1, 2, 3];
array.forEach(function () {
console.log(this.myName); // undefined * 3 ,WTF?
});
},
},
created() {
this.getName();
},
});
Now, that's very weird right? Why does a simple forEach result in such bizarre outputs? Well, let's first take a look at forEach source code:
Array.prototype.forEach = function(callback) {
for(let index = 0; index < this.length(); index++> {
callback(this[index], index, this);
})
}
We can see that the function passed to forEach is sample called, resulting in a conflict of this, therefore the reason of this reference misdirect.
How will we deal with this problem, or if we put it in other words, what is a better way of programming in order to prevent and elude these strange confusing this reference problems?
The first way is to declare a variable to reserve this.
const app = new Vue({
el: "#app",
data: {
myName: "Ray",
},
methods: {
getName() {
const vm = this;
const array = [1, 2, 3];
array.forEach(function () {
console.log(vm.myName); // Ray * 3,is Good!
});
},
},
created() {
this.getName();
},
});
The second way, it to use the arrow function.
const app = new Vue({
el: "#app",
data: {
myName: "Ray",
},
methods: {
getName() {
const array = [1, 2, 3];
array.forEach(() => {
console.log(this.myName); // Ray * 3,is Good!
});
},
},
created() {
this.getName();
},
});
By either way, the problem seems to be solved. For the first way, by creating a variable to store this in the Vue component separates different this references under different enrivironment. But the second way seems somewhat confusing. Why do a simple arrow function takes care of the problem? Don't worry, everything will be explained later.
So basically, when you call a function directly, or even pass an anonymous function as a parameter, it is often a sample call, which might result in this problems.
const app = new Vue({
el: "#app",
data: {
myName: "Ray",
},
methods: {
getName() {
(function () {
console.log(this.myName); // undefined,WTF...
})();
},
},
created() {
this.getName();
},
});
object function
Another very common case is when we put this in a javascript object's function. This is also a very dangerous spot that may lead to a this reference misdirect problem.
var myName = "oh No!";
var obj = {
myName: "Tristan",
fn: function () {
console.log(this.myName);
},
};
obj.fn(); // Tristan
var fn = obj.fn;
fn(); // oh No!,WTF?
Notice that the output kind of makes no sense as you call the same function fn in object obj. Notice that we used var instead of let, const.
If we use let or const:
const myName = "oh No!";
const obj = {
myName: "Ray",
fn: function () {
console.log(this.myName);
},
};
obj.fn(); // Ray
var fn = obj.fn;
fn(); // undefined,WTF?
In this article we're not really gonna spend too much time explaining the differences between var, let and const, which we will in the future.
To find out this references in a js object is pretty simple though. You just need to look which object if this in when called. Take fn() above as an example. this is called under the object obj, hence this refers to obj.
new constructor
When we use new before the function that we are calling, in javascript, it treats the function as if it's an object, where this becomes a basic property of the object.
function fn(myName) {
this.myName = myName;
}
const newFn = new fn("Tris");
console.log(newFn.myName); // Tris
Just like an plain js object:
const obj = {
myName: "Tris",
};
console.log(obj.myName); // Tris
DOM
When you use this on DOM elements, when you use it with addEventListener, this will then refer to the DOM element itself on matter what.
For example:
<button type="button" class="btn">1</button>
<button type="button" class="btn">2</button>
<button type="button" class="btn">3</button>
<button type="button" class="btn">4</button>
<button type="button" class="btn">5</button>
const buttons = document.querySelectorAll(".btn");
buttons.forEach(function (button) {
button.addEventListener("click", function () {
console.log(this); // The DOM you click
});
});
When you declare an event listener by adding addEventListener on a specific DOM element, it actually is going to wait in the future at some point when you conduct a certain act, such as click in this case. Meantime, the callback function will be binded to the DOM element, so basically, you can think if the code above as below, if it helps to understand:
const click = {
button: '<button type="button" class="btn">1</button>',
addEventListener() {
console.log(this);
},
};
click.addEventListener();
However, when using this with DOM elements, sometimes you find this refering to strange things. Which is when you use it with arrow functions.
const buttons = document.querySelector(".btn");
buttons.forEach(function (button) {
button.addEventListener("click", () => {
console.log(this); // window ???
});
});
Well, you might remember that above you've just read about arrow functions. Actually, arrow functions indeed include some important points that has to do with this, so let's continue this this topic by taking a look at arrow functions.
arrow function expressions
Before we actually talk about arrow functions, we have to understand the differences between arrow functions and normal functions.
In MDN docs, it is said that an arrow function does not have its this, arguments, super and new.target. So, the question is, if arrow functions do not have their own this, then where does this refer to when using arrow functions.
It turns out that when using arrow functions, this will refer to its outer domain, the parent
domain.
Let's take a look again at an example which seems confusing at first:
const app = new Vue({
el: "#app",
data: {
myName: "Ray",
},
methods: {
getName() {
const array = [1, 2, 3];
array.forEach(() => {
console.log(this.myName); // Ray * 3,is Good!
});
},
},
created() {
this.getName();
},
});
In the code above, if we write is as array.forEach(function() {...}), the output would be undefined or window depending on the environment you're working with. The reason is that this results in a sample call as previously mentioned. However, by using arrow functions, due to the absent of this of arrow functions' inherit nature, it then follows its way up to refer to the nearest this, which turns out to be the vue instance in this case.
Top comments (0)