ES6 Cool stuffs — A big fat Arrow
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 theFunction
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 //OKlet sumUp = (a, b)
=> a + b //NOT OKlet 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 ofreturn
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 = 1WaitAndSee.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 = () => {} //OKWaitAndSee() => {} //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 😺.
More on ES6:
- Set vs Array — What and when?
- Map vs Object — What and when?
- (…) syntax in depth
- var, let, const in depth
- Destructuring me, plz!
- A new JS string with template literals
- Let’s divide our phones into Classes
More on Data Structures:
If you like this post, don’t forget to give me a 👏 below ⏬️ . It will surely motivate me a lot 😊
If you love to read more from me, feel free to check out my articles.
If you’d like to catch up with me sometimes, follow me on Twitter | Facebook or simply visit my portfolio website.