ES6 Series. Part 1. Fat Arrow ES6 Functions vs Plain Old ES5 Functions

Although the ES6 standard is no longer a something exotic or future standard of the language and it was even finalized in the mid 2015, yet there are lots of developers who don’t know what the key new features are or not use it because they don’t understand it. Let’s try to learn it one by one: fortunately there are very interesting and useful topics to discuss.

Why OR What’s wrong with traditional function(){}?

We all know that to make a function in plain old ES5 all you need to do is to declare it, like this:

function doubleNumber(x) {
return x * 2;
}

And yes, functions could be used as methods, constructors and non-method functions. Really, if you want to create the object, all you need to do is:

var Storm = function(name){
this.name = name;
};

Then, when you want your object to be able to perform some action, you just add the new function to the object prototype.

Storm.prototype.addSiblings = function(){
console.log('Just add another sibling...');
};

The hell comes when you decide to make the object methods access data from the object itself. Let’s make the makeWave() output the string with the name of the Storm:

var Storm = function(name){
this.name = name;
};

Storm.prototype.addSiblings = function(stormList){
'use strict';
return stormList.forEach(function(name){
this.name += ' with sibling #' + name + ' ';
});
};

var newStorm = new Storm('#1');
newStorm.addSiblings([12, 14]);

What do you expect to get for the name of the newStorm? Something like: Storm #1 with sibling #12 with sibling #14. Never, what you will see: TypeError: Cannot read property ‘name’ of undefined.

Why so? Well, the problem is with that this — it is undefined for non-method functions. This is a very common problem and there are three known workarounds:

#1 — Add new variable with reference to this (var self = this)

Storm.prototype.addSiblings = function(stormList){
'use strict';

var self = this;

return stormList.forEach(function(name){
self.name += ' with sibling #' + name + ' ';
});
};

Now, it is working properly:

> newStorm.addSiblings([12,14])
> newStorm.name
> "#1 with sibling #12 with sibling #14 "

#2 — Bind to this

Storm.prototype.addSiblings = function(stormList){
'use strict';
return stormList.forEach(function(name){
this.name += ' with sibling #' + name + ' ';
}.bind(this));
};

Now, it is working properly:

> newStorm.addSiblings([12,14])
> newStorm.name
> "#1 with sibling #12 with sibling #14 "

#3 — Specify a value for this (if possible)

Some functions allow specifying the context (or this), e.g. forEach method does it — you need to pass it as the third argument.

Storm.prototype.addSiblings = function(stormList){
'use strict';
return stormList.forEach(function(name){
this.name += ' with sibling #' + name + ' ';
}, this);
};

Now, it is working properly:

> newStorm.addSiblings([12,14])
> newStorm.name
> "#1 with sibling #12 with sibling #14 "

Arrow functions overview

The new standard (ES6) proposes the new solution for this problem — as you might guess, it is Fat Arrow Functions. Well, the naming is quite interesting: it was called so because the -> is called an arrow, so if you use equals=> it is then “fat”. Basically the previous example could be rewritten in the following way:

class Storm {
constructor(name) {
this.name = name;
}

addSiblings(stormList) {
return stormList.forEach(x => this.name += ' with sibling #' + name + ' ');
}
}

let newStorm = new Storm('#1');
newStorm.addSiblings([12, 14]);

As you can guess, the Arrow Function gets the this of the surrounding function. To be 100% precise, there are two types of this: lexical and dynamic. So, the dynamic is determined by the way the function was called — that’s why the traditional ES5 methods had different this depending on the context. Lexical this is taken by the function from the surrounding scope. So, it is exactly what arrow functions do.

Arrow functions syntax

Let’s analyse the syntax of the Arrow Functions — they look really nice and much shorter — in JS we were always struggling for expressive and laconic solutions, uh? Let’s see:

() => {...body...} // for zero or any number of arguments
x => {...body...} // brackets are omitted for the single argument

Beauty comes also when your function body is the single return statement, then you can omit curly brackets:

x => x * 2 // all brackets are omitted

Also, it is a very common thing that you return the object from the Arrow Function. As curly braces denote the function block, you need to wrap the returned object with braces:

x => ({id: 12, name: x})

It would be an error to split arguments of the Arrow Function and the arrow in several lines (at least the closing argument bracket and the arrow should be on one line):

(x,y) // error: Unexpected token
=>x*y

(x,y // OK
) =>x*y
(
x,
y // OK
) =>x*y

Conclusion

To keep a long story short, Arrow Functions are neat when you need to deal with context and want the callback to access the this of the outer scope without any tricks. In all other cases, Arrow Functions are just nicely written anonymous functions is JS which let you save space and increase readability of the code.

I would recommend you reading some other resources if you need more depth:

  1. Chapter 13. Arrow Functions from book “Exploring JS” by Dr. Axel Rauschmayer;
  2. Understanding ES6 features by Nickolas Zakas;
  3. All ES6 features compared to ES5 in one short list;
A single golf clap? Or a long standing ovation?

By clapping more or less, you can signal to us which stories really stand out.