JavaScript Tutorial for Programmers - This

I still remember the days of debugging CORS problem when I put together some projects using JavaScript (& ajax), “a very particular programming language” in my first impression. Recently I got a great opportunity. The new role uses JS, the browser-side script that is now winning in all sides, as the major language. So I tooke it as a good chance to learn JS more systematically, and this series will be part of the outcome of my study. As the name implies, I will not cover primary level such as “if, else” (condition), “for” (or any kinds of loops), or basic OOP concepts. Instead, I will focus only on differences so you can learn this versatile language like reviewing a pull request, and use it the next day in your next awesome project.

In this post, I will discuss this, the legendary JavaScript pitfall countless programmers trod upon.

What is this

Example:

1
2
3
4
5
6
7
8
9
10
var hero = {
name :"Hulk",
motto :"Rahhhhh!!",
speak:function () {
alert(this.name + ":" + this.motto);
}
}
hero.speak();

Result:

1
Hulk:Rahhhhh!!

this‘ runtime value

What showed above is a classic OOP example, in which we use this to refer to the object that calls the function. Yet JavaScript is not strictly object-oriented and the behavior of this is different from all the rest. To be more specific, this is assigned with the reference of the execution context by the runtime. What does that mean, we look at another example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var name = "Iron man", motto = "Right in the feels!";
function speak() {
alert(this.name + ":" + this.motto);
}
var hero = {
name :"Hulk",
motto :"Rahhhhh!!",
speak:speak //here we assign the function to the member method
}
hero.speak();
speak(); // implicitly reads the two variables in the global context(window)
window.speak(); // we call speak from windows to verify

Results:

1
2
3
Hulk:Rahhhhh!!
Iron man:Right in the feels!
Iron man:Right in the feels!

In the code above, the first speak() is called in the context of hero, and the value of this is set accordingly. Albeit not obvious, we can know that the caller of second speak() is in fact window, by comparing the output of the second function call and that of the third. In both cases, the members of window (global variable name = "Iron man") can be dereferenced using this.***, inside the implementation of speak().

The confusion shown above can not be counted as a real issue (rather, it is more like a puzzle game) since no one will program in this way. Next, we look at some problems that are relevant in practice.

Problem 1 - callback

The most common way to incur the “context mess up” (I made this one up as I really do not want to use the term “context switch”) is through a callback assigned to a variable, in most cases, a function parameter. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var name = "Iron man", motto = "Right in the feels!";
function thinkBeforeSpeak(callback) {
callback();
}
var hero = {
name :"Hulk",
motto :"Rahhhhh!!",
speak :function speak() {
alert(this.name + ":" + this.motto);
}
}
setTimeout(function(){ thinkBeforeSpeak(hero.speak); }, 3000);

Result:

1
Iron man:Right in the feels!

In the example, the function in use is assigned to other variables and the context of the function is changed to window …… and again, Iron man feels not well.

Solution 1, bind()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var name = "Iron man", motto = "Right in the feels!";
function thinkBeforeSpeak(callback) {
callback();
}
var hero = {
name :"Hulk",
motto :"Rahhhhh!!",
speak :function speak() {
alert(this.name + ":" + this.motto);
}
}
setTimeout(function(){ thinkBeforeSpeak(hero.speak.bind(hero)); }, 3000);

Result:

1
Hulk:Rahhhhh!!

As you may have noticed, the magic is the following line:

1
hero.speak.bind(hero);

which fixate the this value to the designated object, the Hulk.

Solution 2, apply()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var name = "Iron man", motto = "Right in the feels!";
function thinkBeforeSpeak(callback) {
callback.apply(hero, []);
}
var hero = {
name :"Hulk",
motto :"Rahhhhh!!",
speak :function speak() {
alert(this.name + ":" + this.motto);
}
}
setTimeout(function(){ thinkBeforeSpeak(hero.speak); }, 3000);

Result:

1
Hulk:Rahhhhh!!

apply() works similar to bind() while it takes an extra array argument that represents the arguments required for the real, workhorse function.

I will not further discuss another similar method, call() that takes variable arguments instead of an array.

Problem 2 - inner function

Another commonly encountered pitfall is using this inside an inner function (function defined within another function, yes, you can do that in JavaScript), in which case this IS NOT the object that the outer method belongs to.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var name = "Iron man", motto = "Right in the feels!";
var hero = {
name :"Hulk",
motto :"Rahhhhh!!",
speak :function speak() {
function innervoice() {
alert(this.name + ":" + this.motto);
}
innervoice();
}
}
hero.speak();

Result:

1
Iron man:Right in the feels!

So do I.

The this becomes window again. Like whenever the JavaScript runtime is not sure, it set this to window.

Solution, =>

We can use => (ES6) to define a inner function, in which the this value is fixated automatically.

1
2
3
4
5
6
7
8
9
10
11
12
13
var name = "Iron man", motto = "Right in the feels!";
var hero = {
name :"Hulk",
motto :"Rahhhhh!!",
speak :function speak() {
var fn = () => { alert(this.name + ":" + this.motto); }
fn();
}
}
hero.speak();

Result:

1
Hulk:Rahhhhh!!

This time, runtime got it right.

BTW, => is called an arrow method and ()=>{...;} means an arrow method with no argument.

What? The problem can be solved using that as well?! NaN.

Problem 3 - constructor

In practice, (a misused) constructor is the last common place you can encounter the unpredictable this. However, it requires further understanding how JavaScript handles class & object, so I will cover this problem and the solutions in later posts of this series.

Today we have learned some techniques to save Iron man. And I hope the next time when you encounter an undefined caused by the messed up this, you can smash the problem like the Hulk.