In JavaScript, arrow functions provide a concise syntax for anonymous function expressions stripped off of their OOP baggage. They are a syntactic sugar on a subset of the function capabilities. Both can be used as closures capturing variables of the outer scope.
Arrow functions are part of the ECMAScript 2015 standard also known as ES6. We will unpack variations of the arrow function ES6 syntax to their analogous function implementation and discuss the differences.
The article assumes familiarity with the traditional functions and builds on the prior knowledge by drawing parallels between the two language mechanisms.
Syntax
The "fat arrow" syntax =>
is dedicated to arrow functions, hence the name.
Arrow function declaration
(arg1, arg2, ..., argN) => expression
Equivalent anonymous function:
(function (arg1, arg2, ..., argN) {
return expression;
}).bind(this)
There's a lot going on here: omitted keywords, the implicit return
statement, this
context binding. Each aspect is discussed separately below.
Semantics
Return Expression
Unlike ordinary functions (anonymous or otherwise), arrow functions implicitly return an evaluated expression without having to use the return
statement.
Arrow function:
(arg1, arg2, ..., argN) => expression
Equivalent anonymous function:
function (arg1, arg2, ..., argN) {
return expression;
}
There's a lot going on here: omitted keywords, the implicit return
statement, this
context binding. Each aspect is discussed separately below.
Semantics
Return Expression
Unlike ordinary functions (anonymous or otherwise), arrow functions implicitly return an evaluated expression without having to use the return
statement.
Arrow function:
(arg1, arg2, ..., argN) => expression
Equivalent anonymous function:
function (arg1, arg2, ..., argN) {
return expression;
}
Once you get used to the syntax, you'll appreciate how much shorter the code becomes and would never want to go back.
Block Statement
The short return expression syntax cannot represent sequence of statements. That's where the familiar block statement {}
comes in. Within the curly braces you'd have to explicitly return
result of the function.
Arrow function:
(arg1, arg2, ..., argN) => {
let result = doSomething();
doDependentThing(result);
return result;
}
Equivalent anonymous function:
function (arg1, arg2, ..., argN) {
let result = doSomething();
doDependentThing(result);
return result;
}
The functions look more alike now, don't they?
Object Expression
Functions often return newly constructed objects. There's a catch: the object declaration notation {}
is indistinguishable from the block statement syntax. The solution is to surround the inline object with ()
to make it an expression.
Arrow function:
(arg1, arg2, ..., argN) => ({
prop1: value1,
prop2: value2,
...,
propN: valueN
})
Equivalent anonymous function:
function (arg1, arg2, ..., argN) {
return {
prop1: value1,
prop2: value2,
...,
propN: valueN
};
}
Single Argument
There's an extra syntactic sugar for a special case of an arrow function having only one argument. You can omit the parentheses ()
around the argument.
Arrow function:
arg => expression
Equivalent anonymous function:
function (arg) {
return expression;
}
No Arguments
An arrow function w/o arguments is just an edge case of empty parentheses. Unlike the single argument syntax, the parentheses are required here.
Arrow function:
() => expression
Equivalent anonymous function:
function () {
return expression;
}
Context Binding
Let's talk about the elephant in the room – the this
context. Arrow functions aside, this (pun intended) has always been a confusing topic in JavaScript.
Functions have access to a special variable this
holding the context assigned in runtime. The problem is that the value varies depending on how the function is called which is error-prone and often undesirable.
With callbacks being the primary use case, in most cases you'd want access to this
context defined at a declaration time, not at invocation.
You'd find yourself sprinkling your code with the following closure boilerplate:
let self = this;
let callback = function () {
self.doSomething();
};
or the re-binding to avoid self
in the callback:
let callback = function () {
this.doSomething();
};
callback = callback.bind(this);
In contrast, arrow functions provide no this
context of their own and instead inherit the current "lexical" scope. They're naturally suited for inline callbacks.
Equivalent arrow function:
let callback = () => void this.doSomething();
The void operator discards the result returned by this.doSomething()
, if any. In practice, passing the result though is often okay and void
can be omitted. The block statement {}
is another (perhaps better) way to ignore the result.
Class Methods
Arrow functions come in handy in classes due to the nature of this
context. Ordinary methods are prone to losing class context when called from outside of class methods. Arrow methods are immune to this issue.
The arrow method syntax is nothing but a class property declaration with an arrow function assigned in place of the value. Note the class properties are introduced in the ECMAScript 2017 specification.
Arrow method (arrow function property):
class Example {
constructor(arg) {
this.arg = arg;
}
callback = () => {
console.log(this.arg);
}
}
Equivalent ES6 class method:
class Example {
constructor(arg) {
this.arg = arg;
this.callback = this.callback.bind(this);
}
callback() {
console.log(this.arg);
}
}
Examples
Loop Refactoring
Single argument is quite common in array method callbacks, such as map()
and its cousins, that iterate over items.
Loop over array of items:
let ids = [];
for (let i = 0; i < items.length; i++) {
ids.push(items[i].id);
}
return ids;
Equivalent traditional function implementation:
let ids = items.map(function (item) {
return item.id;
});
Equivalent arrow function implementation:
let ids = items.map(item => item.id);
This example vividly demonstrates the level of code compression provided by arrow functions without sacrificing readability and even improving it.
Enjoy the utility of arrow functions in your modern JavaScript code!
Top comments (0)