Some Pitfalls of Arrow Funtions

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

In a separate post Understanding JavaScript Arrow Functions, arrow functions, its syntax, major features and some use case examples were discussed. Since arrow functions were added in ES6 as (i) shorter and concise syntax alternatives to autonomous functions , and (ii) to write functions without their own this keyword binding (this retains value of the enclosing lexical context‘s this), however arrow functions are not appropriate in some conditions and their use should be avoided.

In this learning-note post, some pitfalls of arrow function are discussed and highlighting the use of our classic function expression.

Revisiting Functions vs Arrow Functions

Revisiting the differences between the functions and arrow functions from Arrow Function post and 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“.

In this learning-note post, some of the major pitfalls of using arrow functions are discussed.

Use of ‘this’ in Dynamic Context

In JS use of this keyword is confusing yet very powerful feature. Value of this changes depending on its calling context. Zell Liew explains that this in JavaScript changes its value in six different ways.

In arrow function, the value of this is defined when the function is created and remains static to its declaration context. Its value can’t be be made dynamic even using call(), bind(), apply() functions.

The following example (adopted from Dmitri Pavlutin’s post) a click handler ..

A button click event is an common task attached eventListener to DOM elements. When button is clicked, the click event triggers event handler function with this as target element thus changing its context (to dynamic context).

//
const button = document.getElementById('myButton');  
button.addEventListener('click', () => {  
  console.log(this === window); // => true
  this.innerHTML = 'Clicked button';
});

In the example above, this refers to window (line 4) in an arrow function that is defined as global context. When button is clicked, the click event triggers the browser to invoke handler function with button context. But in arrow function, value of this defined in global context can’t be changed and thus this.innerHTML refering to window.innerHTML and throws TypeError.

In such a dynamic context, normal function expression allows to change the value of this depending target element, as shown below.

//
const button = document.getElementById('myButton');  
button.addEventListener('click', function() {  
  console.log(this === button); // => true
  this.innerHTML = 'Clicked button';
});

In the example above, a button is clicked value of this in hander function is button (line 10). Thus the value of this.innerHTML is equivalent to button.innerHTML (line 11) and thus returning expected output.

Using as Methods

The arrow functions are not suitable to use as methods. Pitfalls of using arrow function as method is demonstrated in the following MDN example:

'use strict';
//function expression
let vehicle = {
  year: 2018,
  //method with arrow function
  b: () => console.log(this.year, this),
  // method with classic function
  c: function() {
    console.log(this.year, this);
  }
}

//access arrow function method b
vehicle.b(); 
//OUTPUT
undefined
Window { ... } //global object
//access classic function method c
vehicle.c();
//OUTPUT
2018 {year: 2018, b: ƒ, c: ƒ} // refers vehicle object

The above example demonstrates that because arrow functions don’t have its own this, it refers to global window object thus not suitable to use as function method.

Constructors

Arrow functions can’t be used as constructors because they don’t have a prototype property and therefore can’t be used use a new constructor as shown in the following example.

//create an empty arrow function
let helloWorld = () => {};
//access 
helloWorld(); //OUTPUT => undefined

//create an new instance
let helloWold = new helloWorld();
//OUTPUT
TypeError: helloWorld is not a constructor

//access prototype object
console.log(helloWorld.prototype); //OUTPUT => undefined

In the example above, the helloWorld() is defined as an empty arrow function (line 2) and when accessed  as helloWorld() returns unidentified (line 4). When a new constructor function instance of helloWorld() is created using new keyword as new helloWorld() (line 7) it throws TypeError (line 9). Access to helloWorld.prototype object returns undefined (line 12) confirming arrow functions can’t be used as constructor.

However using classic function expression, a new instance of constructor function can be created and we get expected return as shown in the example below:

//define hello with function expression
let hello = function(msg) {  
  this.msg = msg;
};
//create an new instance of hello()
let helloWorld = new hello('Hello World!');  
console.log(helloWorld.msg);
//OUTPUT
Hello World!

Generators

JS generator function (an ES6 feature) is defined on MDN as “the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*" target="_blank" rel="noopener">function*</a> declaration (function keyword followed by an asterisk) defines a generator function, which returns a Generator object “.

As stated on the MDN Documentation, arrow functions can’t be used in generators because the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield" target="_blank" rel="noopener">yield</a> keyword can’t be used in arrow function body. Using yield keyword throws an error.

Tip: JS generators and generator functions will be discussed in more detail in a separate post.

Arguments Objects

The argument objects is an Array-like object corresponding to the arguments passed to a function. Arrow functions don’t have their own arguments object. The following example, no binding of arguments from the MDN documentation:

// define arg array
let arguments = [1, 2, 3];
//create arrow function
let arr = () => arguments[0];
//access arr()
arr(); // OUTPUT => 1
arr(2); // OUTPUT => 1 .. same value

//create a nested function
function myFunc(n) {
  let myArg = () => arguments[0] + n; 
  return myArg();
}
//invoke myFunc with 5 as arg number
myFunc(5): //OUTPUT => 10

In the example above, the arguments[0] in line 4 refers to arguments 1 from arguments variable (line 2). When access arr() function output 1 (line 6). When the arr() function is passed 2 as argument value as arr(2) (line 7) it still refers to the first 0 position argument and return 1, same as arguments[0].

Likewise the arguments[0] in myArg() function (line 11) refers to argument n of myFunc() function (line 10). When myFunc() function is accessed with a argument value of 5, it returns an expected value of 10 (line 15).

Using JS rest parameters, the above no argument binding problem with the arrow function can be fixed and is a good alternative. The following example from the MDN :

//create a nested function
//arrow function with rest parameters
function myFunc(n) {
  let myArg = (...args) => args[0] + n;  // with ( ..arg)
  return myArg(15);
}
// invoke with 10 as arg
myFunc(10); //OUTPUT => 25

Using rest parameters syntax (with ... prefix on the last argument) we got the expected return (line 8).  Because rest parameters are array instances methods like sort, map, forEach or pop can be applied on it directly.

Tip: The rest parameters, which allows to represent an indefinite number of arguments as an array, will be discussed in separate learning-note post.

Wrapping Up

In this learning-note post, some pitfalls of an arrow function are highlighted. Use of arrow functions should be avoided when a dynamic context is required, defining methods, create objects with constructors, get the target from this when handling events. In these situations, classic function expression should be used.

Related Post: Understanding JavaScript Arrow Functions

Useful Resources & Links

While preparing this post, I have referred posts by Demetri Pavlutin & Wes Bos extensively. Visit original link for additional information.