Arrow functions — Yet Another Primer (YAP!)

Understand ES6 Arrow functions in detail

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 or bind
  • 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.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Kumar Chetan Sharma

Kumar Chetan Sharma

C0d3🐵 from 🇮🇳. selfTaught. 😨 of CS, Algos & DS. World famous 👨‍🍳 at home. Pro Level Pro-Crastinator. Man child. Dad of (✿◠‿◠)(◠‿◠✿)