Getting Started with Modern JavaScript — Arrow Functions

Getting Started with Modern JavaScript — Arrow Functions

One of the most popular new features in ECMAScript 2015 is arrow functions. Is it because of the new cleaner syntax or the sharing of this with the parent scope? Maybe both. Let’s look into them in more detail.

Syntax

The first factor that influenced the introduction of arrow functions was a shorter function syntax. The syntax can look a bit different depending on the function. Let’s see some options!

Here is a function written in ES5 syntax:

var sum = function(a, b) {
  return a + b;
}

sum(1, 2) // -> 3

And here is the same function as an arrow function:

const sum = (a, b) => a + b;

sum(1, 2) // -> 3

As we can see the arrow function makes the code much more concise when we have one-line expressions. Due to implicit return we can omit the curly braces and return keyword.

If we have multiple lines in the function we always have to use the curly braces and return:

const printSum = (a, b) => {
  const result = a + b;
  console.log('Answer: ' + result);

  return result;
}

const sum = printSum(1, 2); 
// -> Answer: 3

Parentheses are optional when only one parameter is present:

const square = n => n * n;

square(3); // -> 9

Arrow functions are sometimes called fat arrow functions, because of the token => that resembles a fat arrow.

A common use case for arrow functions is when using array methods. Here we create a new array with the ages of the players by mapping through the player array:

const players = [
  { name:'Michael', age:44},
  { name:'Karl', age:33},
  { name:'Lisa', age:37}
];

const ages = players.map(player => player.age); 
// [44, 33, 37]

Array functions really shine here!


This

We have seen how nice the new syntax can be but there is a another better reason to use arrow functions. And that is the sometimes confusing behavior of the this keyword in JavaScript. It doesn’t depend on where it’s declared but on how and where the function is called, the execution context.

In the global scope, value of this is always the window object.

Regular Functions

Functions have their own execution context and with it comes their own this. Understanding these concepts is not easy but they are important.

Some example code can hopefully make it clearer. To show the difference we use setTimeout, a native JavaScript function which calls a function after a set delay in milliseconds.

// ES5
function Age() {
  this.age = 42;
  setTimeout(function() {
    console.log(this.age); // -> undefined
  }, 1000);
}

var a = new Age();

If we run this code we get undefined logged to the console because the function inside setTimeout has its own execution context. So we are not accessing this.age of the Age function but of the one inside the function where we make the call. And that hasn’t assigned a value to age.

To access the context of the parent function developers often reassign this of the outer function to a variable commonly named that or self.

// ES5
function Age() {
  var that = this;
  that.age = 42;
  setTimeout(function() {
    console.log(that.age); // -> 42
  }, 1000);
}

var a = new Age();

In this way we can access the this of the parent function but you can see how it can be confusing.

Another option is to use function{}.bind(this).

Arrow Functions

Arrow functions however share the lexical scope with their parent. This means that it uses this from the code that contains the arrow function.

// ES6
function Age() {
  this.age = 42;
  setTimeout(() => {
    console.log(this.age); // -> 42
  }, 1000);
}

const a = new Age();

So, we can see how arrow functions help to make the code cleaner and easier to read.


Should we always use Arrow Functions?

Arrow functions are great but there is still use for regular functions. Let’s see some examples when we should not use arrow functions.

Methods

Arrow functions are best suited for non-method functions. Let’s see what happens when we try to use them as methods. In this example, we create an object blog with a method like.

const blog = {
  likes: 0,
  like: () => {
    this.likes++;
  }
}

One would think that every time we call blog.like() the attribute blog.likes increases by one. However, sadly the value of likes will remain at zero.

This time the parent scope is the window object and not the blog object. So invoking the method like() would attempt to increment the property likes on the window object.

If instead, we use the traditional syntax it will work as expected:

const blog = {
  likes: 0,
  like: function() {
    this.likes++;
  }
}

Even better, another new feature in ES6 is shorthand method syntax:

const blog = {
  likes: 0,
  like() {
    this.likes++;
  }
}

Notice here that we can omit the function keyword and colon.

Constructor

An arrow function cannot be used as a constructor. It will throw an error when used with new:

const PrintDouble = (x) => console.log(2 * x);

new PrintDouble(2);
// -> TypeError: PrintDouble is not a constructor

We can instead use a function expression:

const PrintDouble = function(x) {
  console.log(2 * x);
};

new PrintDouble (2); // -> 4

Conclusion

Arrow functions are a great new addition ES6 that brings us a syntactically compact alternative to a regular function. An arrow function does not have its own this. The this value of the enclosing lexical scope is used. Arrow functions are ill suited as methods, and they cannot be used as constructors.

Did you find this article valuable?

Support Michael Karén by becoming a sponsor. Any amount is appreciated!