Arrow functions — Yet Another Primer (YAP!)
Arrow functions were introduced six years ago. Most modern browsers support arrow functions now. Most developers think that arrow functions are just syntactic sugar for functions. Even after six years a lot of front end developers are not yet clearly aware of whats and whys of arrow functions.
So here is a primer on Arrow Functions.
To begin with, arrow functions have following syntax
parameters => expression
Expression may or may not be dealing with parameters passed. Here are variations of above syntax.
Concise body. No parenthesis needed for a single parameter. No curly braces or return keyword needed for single line expression.
() => console.log("Hello world in Hindi is नमस्ते दुनिया!");
No parameter. Just one expression. And if a parameter was needed, parenthesis are optional. Only for one parameter.
name => console.log(`Greetings ${name}!`);
In my projects I prefer to have a concise body with parenthesis for parameters, even if it is just one parameter.
(name) => console.log(`Greetings ${name}!`);
ESLint helps me enforce the above. Readable code FTW!
More than one parameter needs parenthesis.
(name, age) => console.log(`Greetings ${name}! Looks like you are ${age>18 ? "an adult." : "still not an adult."}`);
Parameters can have defaults.
(name = "earthling", age) => console.log(`Greetings ${name}! Looks like you are ${age > 18 ? "an adult.": "still not an adult."}`);
As one might have noticed the expression was just one line, bit convoluted may be, still a single line. And as soon as your expression goes multiline you move to block body structure.
(name = "earthling", age) => {
const adultOrNot = age > 18 ? "an adult.": "still not an adult.";
console.log(`Greetings ${name}! Looks like you are ${adultOrNot}`);
}
Since in the above example we are just console logging a return is not needed. However, in the following very “complex” way of finding a leap year a return is used.
(year) => {
const totallyDivisibleByFour = !Boolean(2016 % 4);
if(totallyDivisibleByFour){
return true;
}else{
return false;
}
}
There are times when you want to return more than boolean values, you want to return objects in just one expression.
() => ({answer: [{life: 42}, {universe: 42}, {everything: 42}]});
And maybe you need it to be executed right away, IIFE?
(() => ({answer: [{life: 42}, {universe: 42}, {everything: 42}]}))();
And obviously, it doesn’t have to be all on one line
(
() => (
{ answer: [{ life: 42 }, { universe: 42 }, { everything: 42 }] }
)
)();
By now, it should be clear that arrow functions are anonymous functions.
An arrow function has no name.
However you can name them. All of the above statements can be converted into the following pattern.
const nameForYourArrowFunction = parameters => expression;
Which means
const greet = () => console.log("Hello world in Hindi is नमस्ते दुनिया!");
greet();
And you can pass arrow functions to other functions as arguments or callbacks.
Apart from syntax, arrow functions also differ from regular functions. Let us get the simpler things first.
Arrow functions do not have arguments
keyword, hence following will not work.
((a, b, c) => console.log(arguments))();
Uncaught ReferenceError: arguments is not defined
However, anonymous functions have arguments.
(function(a, b, c){ console.log(arguments)})();
Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ]
In case of arrow functions you can rely on rest parameters and following pattern should help
((…arguments) => console.log(arguments))();
Arrow functions can not be used as constructors. Following will throw an error
const g = new greet();
Uncaught TypeError: greet is not a constructor
The new.target
keyword is not available. Since arrow functions can not be used as constructors this makes sense as well. And no prototype
property either. You can not use yield
inside the arrow function’s body either.
And the biggest one now, arrow functions do not have their own this
or super
keyword. They take this from their parents. And in case the parent is an arrow function it will look for the nearest non arrow parent function. Here is an example
document.querySelectorAll("a").forEach((anchor) => {
console.log(`This here points to ${this}`);
anchor.addEventListener("click", (event) => {
event.preventDefault();
console.log(`This here points to ${this}`);
if (confirm(`Do you really want to visit ${anchor.innerText}?`))
{
window.location = anchor.href;
}
});
});
Above code is using DOM APIs. You can just copy paste and run this JavaScript console of any window or tab in the browser. And the top reference to this will be a global window object. And the next reference will also be a global window
object. Let us rewrite it using anonymous functions
document.querySelectorAll("a").forEach(function (anchor) {
console.log(`This here points to ${this}`);
anchor.addEventListener("click", function (event) {
event.preventDefault();
console.log(`This here points to ${this}`);
if (confirm(`Do you really want to visit ${anchor.innerText}`)) {
window.location = anchor.href;
}
});
});
Now the first this will be pointing to the global window
object. However the second this will be pointing to the anchor
object. Moving this to a step further
document.addEventListener("DOMContentLoaded", (event) => {
console.log(`This here points to ${this}`);
document.querySelectorAll("a").forEach((anchor) => {
console.log(`This here points to ${this}`);
anchor.addEventListener("click", (event) => {
event.preventDefault();
console.log(`This here points to ${this}`);
if (confirm(`Do you really want to visit ${anchor.innerText}?`)) {
window.location = anchor.href;
}
});
});
});
All the references to this will be pointing to the global window
. There isn’t any non arrow parent function at any point. Let us introduce a small change
document.addEventListener("DOMContentLoaded", function (event) {
console.log(`This here points to ${this}`);
document.querySelectorAll("a").forEach((anchor) => {
console.log(`This here points to ${this}`);
anchor.addEventListener("click", (event) => {
event.preventDefault();
console.log(`This here points to ${this}`);
if (confirm(`Do you really want to visit ${anchor.innerText}?`)) {
window.location = anchor.href;
}
});
});
});
Now, the “this
” inside the click handler is pointing to HTMLDocument
and not to window
object.
All this means arrow functions may not work as intended for following situations:
- Creating object methods
- Depending on scope using
call
,apply
orbind
- Using
prototype
- The logic needs access to
arguments
object - Event handlers with dynamic context.
There are ways to overcome most of the above. For example, for event handlers using arrow functions, rather than referring to this object pass event as argument. Infact, always pass events to your event handlers. You get better control.
On a closing note, the way arrow functions handle this
has simplified writing JavaScript. Browser JavaScript engines know what this
will resolve to and can do optimisations. Like most of the things in software engineering, arrow functions are not silver bullets. Use them wisely and you can make your code not just readable and less error prone but optimised as well.