Understanding JavaScript Arrow Functions

Note: This post is work-in-progress learning-note and still in active development and updated regularly.

JavaScript (JS) functions are the building blocks of the program, as they allow use perform an operation many times by calling name of code block without repetition. In the previous posts, we discussed Understanding JavaScript Functions – The Basics, Function Declaration & Expression, Callback functions in detail.

In ECMAScript 2015 (ES6) standard added new features to JS functions and introduced Arrow functions expression with shorter syntax compared to function expression.

In this learning-note post, introduction to arrow function, converting an function to arrow function, its basic syntax, some use case examples will be discussed.

Arrow Functions

The Arrow functions are similar to anonymous function expression and defined with a new syntax that uses a fat “arrow” (=>). Arrow functions make code much simpler and shorter.

//initialize a sum function with two arg
let sum = function(a,b) {
  return a + b;
}
//access sum()
console.log(sum(15,25));//OUTPUT => 40

In the example above, the sum(), a function expression, is defined with with two arguments (line 2) and it returns sum of two numbers (line 6).

This type of functions are also known as anonymous functions (functions without name). Functions stored in a variables, such as expression function, do not need function name and can be invoked (accessed) using variable name.

In ECMAScript 6, this above sum function expression can also be written using arrow function as shown below:

//using arrow function syntax
let sum = (a,b) => a + b;
console.log(sum(15,25)); //OUTPUT => 40

//check typeof()
console.log(typeof sum); // function
//check instanceof
console.log(sum instanceof Function); // true

Using the sum arrow function is returns exactly same value (line 9) like in previous example (line 6).

The above sum arrow function, the typeof operator returns functions (line 12) indicating it’s a function. Like wise, the sum arrow function returns instanceof function type as true (line 14).

Writing Arrow Functions

An anonymous function, from above example (lines: 2-4) was converted to an arrow function (line 8) as follows:

  • First, delete the function keyword
  • Add a flat arrow like this =>
  • Remove parentheses from single parameters
  • No need to use return keyword in the body code block

Arrow functions are always anonymous, the name is an empty string and argument object is not available.

Arrow Function Syntax

The basic syntax of arrow functions is variable depending upon the parameters (or arguments) used. Basic syntax examples (from MDN)

//syntax with parameters
    () => { ... } // with no parameter
     x => { ... } // with one parameter
(x, y) => { ... } // with several parameters
  • The parameter list for a function with no parameters should be written with a pair of parentheses (line 2).
  • Parentheses are optional when there is only one parameter (line 3).
  • With multiple parameters, the parameters are separated by comma and must be enclosed in parentheses (line 4).
With Multiple Parameters
//ES5 syntax
let funcName = function(arg1, arg2, ...argN) {
  return expression;
}
//ES6 syntax
let funcName = (arg1, arg2, ...argN) => expression

//EXAMPLE with three arguments
//ES5 syntax
let add = function(num1, num2, num3) {
  return num1 + num2 + num3;
};
//ES6 syntax
let add =(num1, num2, num3) => num1 + num2 + num3;

In cases where there are more than one arguments, then they must be enclosed in parentheses (line: 6, 14 ). The add() function adds the two arguments (num1, num2) together and returns the results. Unlike in single arguments, the arguments are enclosed in parentheses and separated by commas (lines: 6, 14).

Another example with four parameters:

//ES5 syntax
let num = [9,12,6,32];
num.sort(function(x,y){ 
    return y - x; 
});
console.log(num); // OUTPUT => [ 32, 12, 9, 6 ]

//ES6 syntax
let num = [9,12,6,32];
num.sort((x,y) => y - x);
console.log(num); // OUTPUT => [ 32, 12, 9, 6 ]

In the example, the ES6 syntax is shorter and with the same results (line: 6 & 11).

With Single Parameters

With single parameter, use of parentheses are optional as shown below:

//single param basic syntax
(singleParam) => { statements }
singleParam => { statements }

// use case example
//ES5 syntax
let sum = function(x) { return x + 5 };
//ES6 syntax (can be written as)
let sum = x => x + 5;

Parenthesis are optional when there is only one parameter.

// SINGLE Parameter
//ES6 syntax
let cars = ['Toyota', 'Honda', 'BMW', 'Lexus'];
let stringLength = cars.map(car => car.length);

console.log(stringLength); // OUTPUT => [ 6, 5, 3, 5 ]
With No Parameters

The parameter list for a function with no parameters should be written with a pair of parentheses.

//basic syntax - with no argument
() => { statements }

//ES5 use case
let myCar = function car() { 
  console.log('Toyota');
  };
//access myCar()
myCar()//OUTPUT => Toyota

//ES6 use case
let myCar = () => { console.log('Toyota'); };
//access myCar()
myCar(); //OUTPUT => Toyota

Parenthesis is required if there are no parameters.

Object Literals Syntax

Because both the object and body block of a function use curly brackets { }, JS engine can’t distinguish between an object or block of codes.

// ES5 function expression
let myCar = function (make) {
    return {value: make}
};
// 
let carMake = myCar('Toyota');
console.log(carMake.value); //OUTPUT => Toyota

//with arrow function
let myCar = make => {value: make }; //logs undefined
//assign 
let carMake = myCar('Toyota');
console.log(carMake.make); 
//OUTPUT
Uncaught TypeError: Cannot read property 'make' of undefined

In the example above using ES5 function expression, myCar() function returns an object that has value property set to make argument (line 6). The same myCar() function with ES6 arrow function syntax (line 10) with object literal returns error (line 15). This is because, both the function code block and object literal use curly brackets { } and JS engine can’t distinguish them.

To fix this problem, the object literals codes should be enclosed in parentheses ( ) as shown below (line 17) which returns expected output (line 20). This is same result as in line 7.

//ES6 syntax with enclose brackets
let myCar = make => ({value: make}); //logs undefined
//function call
let carMake = myCar('Toyota');
console.log(carMake.value); //OUTPUT => Toyota

As shown above, the object literal must be enclosed in parentheses (line 17).

Line Breaks

Arrow functions syntax does not allow line break between the parameter definition and its fat arrow ( =>) as shown below:

//incorrect - throws error
let sum = (a,b)
           => a + b; 
//OUTPUT
SyntaxError: Unexpected token =>

//correct syntax
let sum = (x,y) => 
x + y;  //OUTPUT => undefined

//this is also correct
let sum = (
 x,
 y
) => 
x + y;  //OUTPUT => undefined

As shown above, line break between parameters (lines: 8-9) and lines (12-16) is allowed.

Arrow Function Body

Arrow function syntax can be written with concise body or block body as shown below:

// concise body syntax, implied "return"
var sum = a => a + 5;                  

// with block body, explicit "return" needed
var sum = (a, b) => { return a + b; };

Writing arrow function syntax with concise body syntax, expression is specified that serves as its return value (line 2). If block body is in arrow function,  curly brackets { } and explicit return keyword is required (line 5).

Arrow Function Features

Arrow function have two key features: shorter functions and no separate this keyword.

Shorter Syntax

One of the major reasons that the arrow function is most popular ES6 feature is because of its shorter syntax.  Using example from the MDN:

// function expression
let vehicles = [
  'BMW',
  'Lexus',
  'Honda',
  'Toyota'
];
//ES5 syntax
vehicles.map(function(vehicle ) { 
  return vehicle.length; 
}); //OUTPUT => [3, 5, 5, 6]

//ES6 arrow function syntax
vehicles.map(vehicle => {
  return vehicle.length;
}); //OUTPUT => [3, 5, 5, 6]
// alternative 
vehicles.map(vehicle => vehicle.length); // => [3, 5, 5, 6]
// alternative
vehicles.map(({ length }) => length); // => [3, 5, 5, 6]

The example snippets above demonstrates that ES6 arrow function syntax allows to write function expression (lines: 2-11) to much shorter (lines: 14-16) and even in single line (line: 18, 20) with the same output.

No Separate this Binding

The use of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this">this</a> keyword in JS confusing. Its value varies depending upon the execution context. In arrow functions, this retains the value of the enclosing lexical context‘s this. Lets review context and use of this in classic function first.

// function expression
let vehicle = {
  make: 'BMW',
  
  myCar: function() {
    console.log(this.make)
  }
}
//access 
vehicle.myCar(); //OUTPUT => BMW

In the example above, vehicle is an object which is calling myCar function and it’s myCar’s context and the value of myCar function is bound to the vehicle object.

//define vehiv=cle object
let vehicle = {
  make: 'BMW',
  // define myCar function expression
  myCar: function() { 
    console.log(this.make)  // outputs - BMW 
   // autonomous function
    setTimeout(function() {
      console.log(this.make) //outputs - undefined
    }, 1000)
  }
}
//access
vehicle.myCar() //OUTPUT => BMW .. then undefined

In the example above, value of this in myCar() refers to vehicle object loging myCar.make within the function (line 5) correctly prints BMW in the output (line 6). When a second function setTimeout is added (lines: 8-10), the context of this.make (line 9) is different here because this here refers to window object in the browser (instead of vehicle object we might have thought it meant to be). That is why it outputs undefined (line 14).

This can be fixed by assigning this to a variable which is usually named as self or that, which is in lexical scope of the callback function.

To fix this, you often assign the this to a variable that doesn’t shadow inside the anonymous callback function.

// create vehicle object
let vehicle = {
  make: 'BMW',
  //create myCar function
  myCar: function() { 
   let self = this; // assign to a var
    console.log(this.make)   
   //autonomous function
    setTimeout(function() {
      console.log(self.make)   //new variable
    }, 1000)
  }
}
//access
vehicle.myCar(); //OUTPUT => BMW ..then -- BMW

In the example above, this.make (line 10) in callback refers to the self variable of which the value is expected vehicle object.

With use of arrow function (which does not have its own this), this uses the value of enclosing lexical context (surrounding block).

//create vehicle object
let vehicle = {
  make: 'BMW',
  
  myCar: function() { 
    console.log(this.make)  
    //arrow function
    setTimeout(() => {
      console.log(this.make)
    }, 1000)
  }
}
//access
vehicle.myCar(); //OUTPUT => BMW .. then ..MBW

Arrow functions take their value of this from the lexical scope (meaning from its surrounding block). Arrow function captures this from its surrounding context instead of creating its own context. In other words, it refers where it is defined.

Other Features
  • <strong>Strict mode</strong> rules ignored: Because this refers to surrounding global (lexical context), the strict mode rules with regard to the this keyword are ignored as shown below (example from the MDN):
    let myFunc = () => { 
       'use strict'; 
       return this; 
      };
    //access myFunc
    myFunc() === window; 
    //OUTPUT
    true //global object

    In the example above, this (line 3) under strict mode rules is ignored as shown in the output true (line 8).

  • Use of new keyword. Arrow function can’t be used as new constructor object, if used throws an error as shown below:
    //arrow function
    let myFunc = () => {};
    //create new constructor instance
    let myFunc = new myFunc(); 
    // OUTPUT
    TypeError: Foo is not a constructor
  • Use of prototype property: Arrow functions don’t have their own prototype property (logs – undefined).
    //arrow function
    let myFunc = () => {};
    console.log(myFunc.prototype); // OUTPUT => undefined
  • Not suitable to use Object.defineProperty(): Arrow functions don’t have their own this, and suitable to use in Object.defineProperty() as shown below (adopted from the MDN):
    'use strict';
    //create
    let vehicle = {
      year: 2018
    };
    
    Object.defineProperty(vehicle, 'b', {
      get: () => {
        console.log(this.year, typeof this.year, this); 
        return this.year + 10; 
      }
    });
    //access output
    console.log(vehicle.b);
    //OUTPUT
    undefined "undefined" 
    Window {…} // global object
    NaN //represents window global object

    In the example above, this in (line 9) refers to global window object outputs undefined (line 16). Likewise, this in (line 10) refers again to global window object thus this.year returns undefined (line 18).

  • No binding of arguments: Arrow functions don’t have their own argument object and acts simply a reference to argument of adjacent scope, as demonstrated in the following example from the MDN:
    let arguments = [1, 2, 3];
    let arr = () => arguments[0];
    //access arr()
    arr(); // OUTPUT => 1
    
    //create a vehicles() with one argument
    function vehicles(x) {
      let myCar = () => arguments[0] + x; 
      return myCar();
    }
    //access vehicles()
    vehicles(3); //OUTPUT => 6

    In the example above, the arguments[0] in line 2 refers to arguments 1 from arguments variable (line 1). When access arr() function output 1 (line 4).

    Likewise the arguments[0] in myCar() function (line 10) refers to argument x of vehicles() function. When vehicles() function is accessed with a argument value of 3, it returns an expected value of 6 (line 12).

  • Not suited as Methods: Because arrow function don’t have their own this keyword, they are not suitable to use in non-method function. An example of its use as method is presented in another post.
Functions vs Arrow Functions

Arrow functions behave differently than than the normal function. Some of the differences, as outlined by Nicolas Zakas & Kyle Pennell, are listed below:

  • Use of this keyword: The this keyword works differently in arrow function. The value of this can’t be changed, which remains same entire the function life cycle.
  • Constructors: There is no built-in internal [[constructor]] method and can’t be used to create similar objects as in normal functions. Arrow function throws an error if used with new constructor.
  • No Prototype Property: Because new constructor can’t be used in arrow function, there is no prototype property.
  • No Argument Object: Unlike function, arrow function don’t have local variable arguments. The MDN defines arguments object as ” an Array-like object corresponding to the arguments passed to a function“.

Use Case Examples

Easier Array Mapping

Because the arrow function syntax is concise and commonly used to map an array as shown in the following example.

//create & initialize arrays
const myVehicles = [
  { make:'Toyota', price:'20K'},
  { make:'BMW', price:'28K' },
  { make:'Honda', price:'18K' }
];
// ES5
let prices = myVehicles.map(function(myVehicles) {
  return myVehicles.price;
})
console.log(prices); //OUTPUT => ["20K", "28K", "18K"]

//ES6 syntax with arrow function
const prices = myVehicles.map(myVehicles => myVehicles.price);
console.log(prices); //OUTPUT => ["20K", "28K", "18K"]

Using arrow function syntax, array map syntax (lines: 8-10) can be written in a in single line (line 14).

Another example using array filter method:

//create & initialise an array
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];

// ES5 - array filter divisible by 5
let newArray = array.filter(function (divFive){
  return divFive % 5 === 0;
});
console.log(newArray); //OUTPUT => [5, 10, 15]

//ES6 - with arrow function syntax
let newArray = array.filter(divFive => divFive % 5 === 0);
console.log(newArray); //OUTPUT => [5, 10, 15]

The array syntax are also commonly used in functions that accept callback like sort(), map(), reduce(), filter() to write simple code

Creating Immediate Invoking Function expression (IIFE)

The arrow functions are also commonly used in IIFE to create a closure() function as shown below.

// IIFE syntax
let car = function(make) {
    return {
        myCar: function() {
            return make;
        }
    };
}("Toyota");
//
console.log(car.myCar()); //OUTPUT => Toyota

//ES6 - IIFE with arrow function
let car = ((make) => {
    return {
        myCar: function() {
            return make;
        }
    };
})("Toyota");
//
console.log(car.myCar()); //OUTPUT => Toyota

In the example above (adopted from Nicolas Zakas) to create an myCar() method (lines: 2-8) using IIFE which uses an make argument as return value (a private object). The same function can be written with arrow function (lines: 13-19) by wrapping the arrow function in parentheses ( ), starting at (make) (line 13) and closing after curly bracket } (line 19).

Writing More Concise Promise Chains

JS promise and the then() method will be discussed in detail separately. The following example from the MDN demonstrate use of arrow function to write more concise promise chains:

var p1 = new Promise( (resolve, reject) => {
  resolve('Success!');
  // or
  // reject ("Error!");
} );

p1.then( value => {
  console.log(value); // Success!
}, reason => {
  console.log(reason); // Error!
} );

The following example example from Kyle Pennell post demonstrate use of allow function asynchronous callbacks function.

// ES5
aAsync().then(function() {
  returnbAsync();
}).then(function() {
  returncAsync();
}).done(function() {
  finish();
});

//ES6 syntax with arrow function
aAsync().then(() => bAsync()).then(() => cAsync()).done(() => finish);

The ES6 function syntax (lines: 2-7) can be written in a single line (line 11) and much easier to read.

Easier Parsing Order

The following Arrow function (without parameters) example (adopted from the MDN) showing easier visual parsing:

// using timtiming event
setTimeout( () => {
  console.log('I happen sooner');
  setTimeout( () => {
    // deeper code
    console.log('I happen later');
  }, 1); // unit - ms (millisecond)
}, 1);
//OUTPUT
I happen sooner
I happen later

The setTimeout() method is the function to be executed and the second parameter refers to the number of milliseconds before execution.

The setTimeout() and setInterval() are both methods of the HTML DOM Window object.

Where Not to Use Arrow Functions

Arrow function can’t replace the classic function and its use is limited. Arrow Function is commonly used in (a) callback function, (b) should be defined before using them, and (c) can’t be used as methods or constructors. Some highlights arrow function pitfalls are discussed in a separate post.

Tip: Arrow functions are best suited for callbacks, methods like map, forEach or reduce(). The classic function expression are best suited for object methods.

Wrapping Up

In this learning-post tutorial, arrow function definition, writing arrow functions, arrow function syntax with no or multiple parameters and some use case examples were discussed. Arrow function is one the most popular ES6 features, its use is very diverse. For more detail discussion on the arrow function can be found in the links listed below.

Since arrow functions is not suitable to use in some cases, highlights of some some arrow function pitfall is discussed in a separate post.

Related Post: Some Pitfalls of Arrow Functions

Useful Resources & Links

While preparing this post, I have referred the following references extensively. Visit original link for additional information.