Published: September 03, 2018
As a new(ish) developer, a lot of concepts are overwhelming until I get ahold of my emotions and attempt to truly understand what is in front of me. This could be looking at a new error, digging through a coworker’s code, or learning a new concept such as arrow functions. The first time I saw arrow functions, I was like “what is this sorcery”, and dismissed it as something to learn at a later date. Little did I know how easy it would make my life as I took a dive into React. Part of being effective at using arrow functions is understanding how the keyword this
differs between the two and how to effectively leverage this
to write more effective code.
TL:DR- You should really read all the way through this. It's a very important concept.
Es5 functions have some distinct differences from Arrow functions.
Let’s look at the following es5 function. You could use the .forEach() method or a for loop within the showStuff function, but I chose map (for no particular reason):
var dog = {
name: 'rufus',
age: 8,
likes: ['running', 'jumping', 'barking'],
showStuff: function() {
this.likes.map(function(item) {
console.log(this.name + 'likes ' + item)
console.log(this);
})
}
}
dog.showStuff();
likes running
likes jumping
likes barking
What happened to this.name? Let’s explore that. If you enter console.log(this)
inside the function before the map statement, your console.log(this)
will look like the following:
{name: "rufus", age: 8, likes: Array(3), showStuff: ƒ}
age: 8
likes: (3) ["running", "jumping", "barking"]
name: "rufus"
showStuff: ƒ ()
Now, move the console.log(this)
inside the map statement (before or after the original console.log is fine). Notice the result of the console.log(this)
:
likes running
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
likes jumping
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
likes barking
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
Why did the scope for this
change inside of the map statement? When this
is inside of the object’s method, as seen here:
showStuff: function() {
console.log(this);
this.likes.map(function(item) {
console.log(this.name + 'likes to ' + item)
})
}
, the owner of the function is the object dog
and this
is bound to the dog
object. When the console.log(this)
is moved to the inside of the map statement, we see this
changed to window. The inner map function now moves the scope away from the dog
object to the parent object of dog
, which is Window
.
You may be saying “that’s obvious, but why does that happen?”. The answer is that it is by design of the Javascript language. Javascript is very loosely typed, which results in quirks such as this. While you can’t avoid this quirk, you can use a couple different methods to work around this
binding in es5 syntax.
The first method involves declaring a variable in the method’s outer function, and setting the variable to this
, as seen below:
showStuff: function() {
var correctBinding = this;
this.likes.map(function(item) {
console.log(correctBinding.name + ' likes ' + item)
console.log(this);
})
}
You will see the result of correctBinding.name
in the console.log change from blank to the following:
rufus likes running
-> Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
rufus likes jumping
-> Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
rufus likes barking
-> Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
Pretty cool, huh? The next method involves using a .bind() method, which creates a new function that binds this
to any arguments placed before it. An example can be seen here:
showStuff: function() {
this.likes.map(function(item) {
console.log(this.name + 'likes ' + item)
console.log(this);
}.bind(this))
}
rufus likes running
-> {name: "rufus", age: 8, likes: Array(3), showStuff: ƒ}
rufus likes jumping
-> {name: "rufus", age: 8, likes: Array(3), showStuff: ƒ}
rufus likes barking
-> {name: "rufus", age: 8, likes: Array(3), showStuff: ƒ}
If you are noticing the same thing I am, you see that the console.log(this)
is different between the two binding methods. The reason is because the .bind() method is binding the entire outer method function for showStuff. The variable declaration of correctBinding= this
is only set when called, and is set on the inner function of the map statement, not the outer function of showStuff. If you would like to read more about the .bind() method, have a look here MDN Bind Reference
Phew!! That was intense. Ready to see an easier way to deal with this
?
When es6 was released, it presented a wonderful new way to deal with the this
keyword. It changed the this
binding to a pattern called lexical scoping. The simplest way to describe lexical scoping is that the inner function (or any nested functions) have access to variables which are declared in it’s outer scope. In our examples, the outer function for the showStuff
key in the dog
object is holding the binding of this to the dog
object. Now any nested functions (i.e. the map statement’s function) inherit the outer function’s this
binding.
Here’s an example of the above code in es6:
const dog = {
name: 'rufus',
age: 8,
likes: ['running', 'jumping', 'barking'],
showStuff() {
this.likes.map(item => {
console.log(this.name + ' likes ' + item)
console.log(this);
})
}
}
dog.showStuff();
rufus likes running
-> {name: "rufus", age: 8, likes: Array(3), showStuff: ƒ}
rufus likes jumping
-> {name: "rufus", age: 8, likes: Array(3), showStuff: ƒ}
rufus likes barking
-> {name: "rufus", age: 8, likes: Array(3), showStuff: ƒ}
The es6 function, a.k.a. “fat arrow function” has lexical scoping which binds the this
of the inner function to dog
and not Window
. How cool is that? No worrying about Window
anymore with this style of nested functions. It's really not more complicated than that, but takes a concentrated effort as a new developer to not get overwhelmed and fully grasp what is happening between the es5 and es6 code. If you would like to read more about lexical scoping, see the lexical scoping sections in this MDN closures reference: MDN Closures Reference