ES6 Cool stuffs — A big fat Arrow

Yup, if input is a can of Cola, return value is a fat unicorn. Sounds logical, doesn’t it? 😆

Arrow function is considered as one of most popular ES6 features. Have you ever wonder why and how to use it? If so, you got to the right place. Let our journey of understanding arrow function expression begin.


As usual, let’s do a quick brief on old concepts about function in JS for the start.

Function in JS

Every function in JS (before ES6) can be declared with function operator syntax

function HelloWorld(){ console.log('Hello World');}
HelloWorld(); //'Hello World'

Or can be declared as non-method functions using function key word inside an expression

let sayHello = function() { console.log('Hi there, do you come here often? ')

Or by using Function constructor directly with new syntax (can’t do without)

let sayHello = new Function(`console.log('Hello World');`);
sayHello(); //'Hello World';

Reminder: Function is global-scoped object. In reality, using Function constructor to define function is totally not recommended, due to performance issues similar to eval Why so? Because

Each time eval or the Function constructor is called on a string representing source code, the script engine must start the machinery that converts the source code to executable code

Source: Opera Team

And that costs heavily! After all, we all know eval equals to evil, don’t we?

Moreover, function can be used as constructors to achieve object-oriented prototypical inheritance. Sound familiar? Indeed, that was how we did it until class is introduced.

In short, the classical way of defining/declaring function is quite straightforward. Hence comes the question — so why do we need another additional way to achieve this? Is it because we want to make JS more complicated to scare off the newbies?

😆 Well, absolutely not. In order to answer the question, let’s have a closer look on the new feature itself, shall we?

Arrow function expression

Basic

=> , or “fat” arrow expression, is a feature inspired from CoffeeScript’s arrow . It is a new, shorter and more concise syntax (less verbose) than the classical function expression, using the following basic format:

//Several parameters
(param1, param2, ..., paramN) => { //statements }
//No parameter
() => { //statements }

So our example above can be re-written as:

let HelloWorld = () => { console.log(‘Hello World’) }

Note:

  • No line break between parameter definition () and =>
let sumUp = (a, b) => a + b //OK
let sumUp = (a, b)
=> a + b //NOT OK
let sumUp = (a, b) =>
a + b //OK
  • If the function contains not more than one code line — we can make it even shorter by eliminating {}:
let HelloWorld = () => console.log('Hello World')
  • If it contains only return statement, we can get rid of return syntax also
let sayMyName = () => `Maya Shavin`
sayMyName() //"Maya Shavin"

BUT wait — there is an exception here, returning object literals need to be wrapped in parenthesis in order to indicate the return object as function body expression, not a block of code.

let getObject = () => { greet: function(){ console.log('hi') }}
getObject() //SyntaxError: Unexpected token '('
let getObject2 = () =>({ greet: function(){ console.log('hi') }})
getObject2().greet() //hi
  • If there is ONLY ONE parameter, we can absolutely say goodbye to ()
let printName = name => console.log(name)

Awesome. Now what else is interesting?

Binding 'this'

Let’s look at the following example:

let WaitAndSee = {
counter: 1,
wait: function(){
let numbers = [1,2,3,4]
numbers.map(function(x) {
this.counter++
})
},
see: function(){
console.log(`Counter: ${this.counter}`)
console.log(`Window counter: ${window.counter}`}
}

What will happen in the following code?

window.counter = 1
WaitAndSee.wait()
WaitAndSee.see()

Classic, isn’t it? In the wait call, this referred to either window global object (non-strict mode) or undefined (strict mode), but not to WaitAndSee. Hence in strict mode, there will be an TypeError thrown:

while in non-strict mode, only the property counter of window will be updated, which means:

Confusing ? Indeed. Because in classical function, this is determined dynamically by how the function is called. To overcome this problem requires kind-of-a trick — assign this to a variable, normally is named as self, that, etc… . Or we can also use Function.prototype.bind() to ensure this is bind correctly. However, neither way is pretty enough to make our developers’ life easier 😫.

Fortunately, arrow function doesn’t define its own this. The value of this is determined by the outer scope in which arrow functions are used. Thus if you wish to get the right this, just need to make sure:

  • Trigger non-arrow function by proper syntax in order to receive a meaningful this value from their caller.
  • Use arrow function for everything else

So the above example can be written as below and will yield the correct result:

let WaitAndSee = {
counter: 1,
wait: function(){
let numbers = [1,2,3,4]
numbers.map(x => this.counter++)
},
see: function(){
console.log(this.counter)
}
}
WaitAndSee.wait()
WaitAndSee.see() //5

Cool? Wait, there’s more

No ‘arguments’ object

Remember the almighty arguments parameter in function?

However, unfortunately (or fortunately?) there is no arguments defined explicitly for arrow function. That says

let showArguments = () => console.log(arguments)
showArguments() //ReferenceError: arguments is not defined

Instead, if you still wish achieve the same effect like arguments, using ... syntax as rest parameters is a good alternative.

let showArguments = (...arguments) => console.log(arguments)
showArguments() //[]

Important note: Rest parameters are not the same as arguments object, as it is an instance of Array with full array functionalities, while arguments is just array-like object.

OK, that’s clear. How about..

Using as constructor?

Arrow function is, by definition, type of function expression. Therefore, it can’t be used as function declaration:

function WaitAndSee(){...} //OK
let WaitAndSee = () => {} //OK
WaitAndSee() => {} //Wrong syntax

And it can’t be used as constructor for object-oriented implementation — yes, it also mean new syntax can not be used together with it.

And certainly, there is no prototype !

let WaitAndSee = function(){}
console.log(WaitAndSee.prototype) //{constructor: f}
let WaitAndSee2 = () => {}
console.log(WaitAndSee2.prototype) //undefined

Oh, and don’t even think of using super() inside arrow function. It simply won’t work as you expected 🙀!

Non-method function

Theoretically arrow function can be used as method function. However, it’s totally not recommended, due to the fact that it doesn’t have its own this. So don’t be surprised if using it as method function can result in unexpected behavior and cause unwanted 🐛, for instance:

let WaitAndSee = {
counter: 1,
wait: () => { this.counter++ },
see(){
console.log(this.counter)
console.log(window.counter)
}
}
window.counter = 1
WaitAndSee.wait()
WaitAndSee.see() // 1, 2 - window.counter is updated instead!

So far so good? Great. Lets get back to our original question — what is the benefits of using arrow function?

Pros

One of the most obvious benefits of using arrow function is shorter and less verbose syntax. Tell me honestly, did you ever mistype function to fucntion or even fucntoin as a result of fast typing? For me, I did 🐼. Hence arrow function is definitely a savior for me when I need to type a lot of code lines.

For example, instead of

let request = fetch("")
.then(function(response){...})
.then(function(result){...})
.then(function(data){...})
.catch(function(error){...})

it can be written as

let request = fetch("")
.then(response => {...})
.then(result => {...})
.then(data => {...})
.catch(error => {...})

Less code, less potential 🐛!

In addition, an important benefit is the lexical binding of this — value is decided depends on its surrounding scope, not by how it is called.

This improvement surely simplifies function scope, prevents a lot of unwanted bugs 🐛 and again, hereby reduces the amount of code needed for a workaround fix 😆.

After all, who wants to write Object.method.bind(this) while you have a better alternative?

OK. That’s pretty much the main benefits of ES6 Arrow function. What about the disadvantages/pitfalls? Is there any? Let’s find out.

Cons

As some developers may argue, sometimes less code doesn’t mean more readable. Thus being a shorter syntax is also, arguably, its disadvantage. Too short can cause confusing, for instance

let getMyName = () => `Maya Shavin`

In the first impression, it’s a bit difficult for developer who is not familiar with CoffeeScript to understand that this function will return a string. We are all used to return syntax when the function supposes to return something, aren’t we?

Secondly, arrow => expression is pretty loosely-bind — aka in case there is a conflict with other operators, normally => will lose first. Hence it requires developers to be more careful in using it.

Lastly, arrow => function can’t be used as constructor. Instead, we have class as proper replacement for classic function constructor.

Conclusion

As stated in my other articles, most of ES6 new features are just syntactic sugar for function expression. Arrow function is no exception and has mostly no significant improvement towards performance compared to classical function.

To my opinion, besides the short and more concise syntax, its most significant benefit is to provide a better solution regarding this scope binding. Finally developers can enjoy cleaner, more elegant and less workaround code 😄.

But again, don’t use it just because of its coolness or benefits. You always need to understand also what use cases and feature’s pitfalls are in order to write good code 😉. Happy coding and always remember KISS, everyone!

What do you think about =>? I’d love to hear your thought in the comments below 😺.