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)