Course: JavaScript

Progress (0%)

  1. Foundation

  2. Numbers

  3. Strings

  4. Conditions

  5. Loops

  6. Arrays

  7. Functions

  8. Objects

  9. Exceptions

  10. HTML DOM

  11. CSSOM

  12. Events

  13. Drag and Drop

  14. opt Touch Events

  15. Misc

  16. Project: Analog Clock

JavaScript Arrow Functions

Chapter 36 24 mins

Learning outcomes:

  1. A quick review of regular anonymous functions in JavaScript
  2. What are arrow functions
  3. Syntax of arrow functions
  4. Example of arrow functions
  5. Arrow functions close over this
  6. Arrow functions don't create their own arguments

Regular anonymous functions

JavaScript has supported anonymous functions from day one of its inception.

As you saw in the JavaScript Functions — Basics chapter, an anonymous function is simply a function in JavaScript without a name of its own. An anonymous function is always an expression.

Anonymous functions are really handy when we want to pass on quick and simple functions to other functions as callbacks. For example, consider the task of mapping an array of integers to an array of the square of those integers.

Well, using the array map() method and a very basic anonymous function, it's really easy to accomplish this task.

Here's the solution code:

var ints = [1, 2, 3, 4, 5];
var squares = ints.map(function(int) { return int ** 2; });

console.log(squares);
[1, 4, 9, 16, 25]

Notice the anonymous function provided to map(). It would've been an extra step if we had to first name the function and then use the name in map() — the provision of an anonymous function in JavaScript extremely simplifies working with functions in this manner, i.e. passing them around.

JavaScript supports the functional programming paradigm and so it intrinsically suits to JavaScript to have functional capabilities in it. Part of this involves providing a structure to create anonymous functions on-the-go, in a very convenient and 'functional' manner.

Fortunately, regular anonymous functions have stood up to the first expectation, i.e. they allow us to...well...create anonymous functions. But they still aren't that convenient or 'functional' if we contrast them with similar anonymous functions from other languages like Python, or C#.

Don't get us wrong though — anonymous functions are verily more convenient than first creating a named function and then using the name of that function.

But they aren't that much convenient as they can be, in some use cases. (As soon as you discover examples of arrow functions below, this will become clear).

So what is that much convenient? And here enters arrow functions.

What are arrow functions?

To begin with the most square definition:

An arrow function is a conciser way to define an anonymous function, with slightly different semantics as well.

Arrow functions are also used to define anonymous functions in JavaScript, albeit in a much more terse syntax. And as on can guess, arrow functions represent expressions too (since they are anonymous), not statements.

Arrow functions behave slightly differently than regular anonymous functions in JavaScript as we shall see shortly below.

If you're wondering why they're called arrow functions, that's because they include the symbol => in their syntax which acts like an arrow (more on the syntax in the next section).

Good fancy naming at work here.

The inspiration of arrow functions in JavaScript

Arrow functions in JavaScript were inspired by functions in CoffeeScript and lambda functions in C#.

They were introduced with ECMAScript 6 into JavaScript, just like many other features had their origin in this phenomenal, revolutionary version of JavaScript.

Anyways, let's now see how to define arrow functions.

Syntax of arrow functions

There are multiple variations of defining arrow functions, depending on the number of parameters and the nature of the function's body.

In particular, there are four different variations, some with gotchas in them. Let's carefully go through each of these variations, one-by-one and understand its use case.

Starting with the all-encompassing syntax, we have a parameter list, followed by =>, followed by the function's body:

(param1, param2, ..., paramN) => {
   // Function body
}

This is the most generic variation of an arrow function.

Akin to functions, the pair of curly braces ({}) here doesn't denote a block statement; instead, it's part of the syntax of this first variation. This is a very subtle detail to keep in mind.

If the parameter list contains only one parameter, we can simplify the parameter list by omitting the parentheses (()).

Something as follows:

param => {
   // Function body
}

As you'll see, this syntax is recommended to be used when we're dealing with one parameter, simply because of its compactness.

If the parameter list is empty, obviously we couldn't omit the parentheses because that would otherwise lead to ambiguity.

If the function body is just comprised of one single statement, which is a return statement, the curly braces and the return keyword can be omitted, as follows:

(param1, param2, ..., paramN) => expression

When we don't have {} following the => in an arrow function, the body is treated as an expression that's implicitly returned by the function.

The expression can't be an object literal!

To simplify the grammar of arrow functions, the expression in this variation can NOT be an object literal.

This is because an object literal is also denoted via a pair of curly braces ({}), so allowing object literals would've meant allowing ambiguity to cripple into the syntax of arrow functions.

Or even if it was not about ambiguity, it would've been quite a strenuous work for JavaScript parsers to determine whether the curly braces ({}) represent a function body block or an object literal.

Of course, this variation and the previous one can be combined together to give us the fourth variation where we have just one parameter and an expression as the function's body.

This is generalized as follows:

param => expression

See how simple and compact this is over the equivalent regular anonymous function definition:

function (param) {
   return expression;
}

Anonymous functions are more than just amazing!

Alright, with this, it's time to consider some examples of arrow functions to get a hands-on experience of them.

Examples of arrow functions

We'll start off with the rewriting the first example presented at the start of this chapter.

Restating the problem, we have an array of integers, and want to map it to an array of the squares to those integers.

Using a single parameter

Here's the code rewritten using an arrow function:

var ints = [1, 2, 3, 4, 5];
var squares = ints.map(int => int ** 2);

console.log(squares);
[1, 4, 9, 16, 25]

Take note of the variation of the arrow function we've used here. Since we have just one parameter to work with, we omit the pair of parentheses (()) around the parameter.

Moreover, since we need to right away return the value int ** 2, we omit the curly braces ({}) and the return statement.

Let's consider another example.

Using multiple parameters

Suppose we have the same ints array as before but this time we have to filter out all those elements that have an even index (i.e. 0, 2, 4, and so on).

Hmm. Seems like we'll be needing the second argument of the callback provided to the array filter() method, which is the index of the current element under inspection.

Let's get to the code:

var ints = [1, 2, 3, 4, 5];
var evenIndexedInts = ints.filter((int, i) => i % 2 === 0);

console.log(evenIndexedInts);
[1, 3, 5]

Since we are using two parameters here int (the current element) and i (the index of the current element), we have to wrap the parameter list inside parentheses (()).

The logic of the function is fairly simple — i is checked for even parity; if it's even, we let the corresponding element into the filtered set of elements.

The int parameter is declared but not used. Regardless, we have to name the first parameter something, as we need to access the second parameter; we can't just leave off the first parameter!

Now, let's consider yet another example.

Using the verbose syntax

Suppose we want to filter out every number from an array based on the condition that the number is smaller than the previous number or if that the number is the first element of the array.

For instance, given the array [2, 1, 0, 2], we seek for the array [2, 1, 0]. Here's how we get this filtered array:

2 is the first element so it gets filtered out; the second item 1 is smaller than the previous one (i.e. 2) so it gets filtered out as well; the same applies to 0 (it's smaller than 1); but the last element is not smaller than the previous one (2 is not smaller than 0) and so it's ignored.

Fortunately, the task is pretty straightforward. Let's get to it.

We'll suppose that we want to perform the filtration on the same array as demonstrated above. Here's our beginning ground:

var nums = [2, 1, 0, 2];

We'll create the filtered array and then log it to see whether it turns out to be [2, 1, 0]:

var nums = [2, 1, 0, 2];
var filteredNums = nums.filter((num, i) => {
   if (i === 0) {
      return true;
   }
   return nums[i] < nums[i - 1];
});

console.log(filteredNums);

Firstly note that since the logic of the arrow function is a little complex, we've used the verbose, full-form syntax of defining an arrow fucntion.

The reason of using if here is to check whether the current element is the first element of the array, and then consider it in the final set of filtered elements if it is (as asked in the task statement).

If we leave off this conditional, the expression nums[i] < nums[i - 1] will resolve to nums[0] < nums[-1] which would yield false (since nums[-1] would be undefined and, likewise, be coerced into NaN), ultimately throwing away the first element from consideration in the set of filtered elements.

Anyways, let's see the console output produced by the code:

[2, 1, 0]

Voila! Our output indeed matches the expected value [2, 1, 0]. Great job!

Returning an object literal

Let's now review an important fact we stated earlier in the discussion above. That is, when the return value of an arrow function is an object literal, we can't use the param => expression syntax as it is.

Keep in mind that JavaScript always treats the curly braces ({}) as part of the function's body, NOT as a part of the object literal. This behavior is designed for simplifying the grammar of arrow functions.

For example, let's say we want to create an arrow function to return the object {x: 10}. We could do this as follows if we are to go with the verbose form of an arrow function:

var f = () => {
   return {x: 10};
};

console.log(f());
{x: 10}

The following, however, won't produce an expected outcome:

var f = () => {x: 10};

console.log(f());
undefined

{x: 10} here is treated as a function body (i.e. denoted by the curly braces) containing the code x: 10. So what exactly is x: 10 as a statement?

Well, x: 10 is a label statement in JavaScript that could be referred to in a break or continue statement — the control would then flow over to the given label statement.

Anyways, since the curly braces act as part of the function's body, and because the function doesn't have an explicit return, its return value is undefined, which is exactly what gets logged in the console.

Does this mean that we have to always use the verbose variant of an arrow function if we wish to return an object literal? Well, no.

We could use a simple trick to make this compact code work. And that's by encapsulating the object with parentheses (()).

The parentheses would clearly indicate that the contained code is an expression, and when {} appears as part of an expression, they are always taken to represent an object literal. Simple.

Here's the correct code:

var f = () => ({x: 10});

console.log(f());
{x: 10}

Parentheses are amazing, what do you say?

Arrow functions close over this

The first semantic difference between regular functions and arrow functions is the treatment of this inside the function. We've already seen how this works in regular functions, but now it's time to see it in arrow functions.

In particular, arrow functions do NOT have their own this.

End of discussion.

The value of this inside an arrow function doesn't depend on how the function is called, or whether it's bound to a particular this by virtue of calling bind() or call().

Instead, the value of this inside an arrow function comes from the value of this from the outer lexical environment (the place where the function is defined).

We say that an arrow function closes over this from its outer environment. In other words, this is also a part of an arrow function's closure.

Regular anonymous functions don't include this in their closures; as we stated earlier, they have their own this binding.

Now, what this has to do with the way arrow function are used?

Well, one of the motivating factors behind arrow function was to simplify working with this inside the function. Traditionally we were required to save the outer this inside a variable and then access that variable inside the function, given that we wanted to work with the outer this.

An example follows:

var counter = {
   i: 0,

   init() {
      var _this = this;
      setTimeout(function() {
         _this.i += 1;
         console.log(_this.i);
      }, 1000);
   }
}

counter.init();

Here's the log made after 1s:

1

But with arrow functions, we no longer need to bring in local variables just for the sake of passing down an outer thisthis itself is put in the closure of an arrow function.

Here's the code above rewritten using an arrow function:

var counter = {
   i: 0,

   init() {
      setTimeout(() => {
         this.i += 1;
         console.log(this.i);
      }, 1000);
   }
}

Notice how we're using this directly inside the callback provided to setTimeout() — this is because the callback is an arrow function, likewise it doesn't have its own this and instead uses this from its outer environment.

This simply means that the this inside the callback arrow function is the this in init(), which we know binds to the counter object.

Working with this simplified, indeed.

This behavior of arrow functions is frequently used in object-oriented programming when we want to use the this of a method inside an inner function nested inside the method.

This happens quite often when using array methods inside class methods that require callbacks or using setTimeout()/setInterval() that again require callbacks.

We'll learn more about setTimeout() and setInterval() in the JavaScript Timers chapter.

No own arguments object

Besides no own this, the second difference between regular functions and arrow functions is that the latter does NOT its own arguments object.

Here's an illustration:

var f = () => {
   console.log(arguments);
};

f();
Uncaught ReferenceError: arguments is not defined

Here we get an error because arguments turns out be an undefined variable in the function. (Remember that arguments is also a local variable created implicitly by JavaScript.)

Before anything else, this raises a genuine question in our minds: Why do arrow functions not have their own arguments object?

Well, it shouldn't be very difficult to think about this.

Why do arrow functions not have their own arguments object?

Recall that arguments is a pretty old feature of JavaScript, and it isn't really an array. If we want to perform an array method on arguments, we have to first convert it into an array or use some other trick (such as the apply() function method).

Rest parameters were introduced into ECMAScript 6 to address this problem. They can be used to obtain an array of arguments — a real array.

Since arrow functions are also an ES6 feature — which encourages the usage of modern and better features of JavaScript — it makes sense for arrow functions to strictly let go off creating arguments.

Arrow functions implicitly ask us to use rest parameters if we want to make them variadic.

Now there is a hidden fact to notice here. If we nest an arrow function inside a regular function, calling arguments in the arrow function would be bound to the arguments of the regular function.

Here's an example:

function f() {
   var f2 = () => {
      console.log(arguments);
   };
   f2();
}

f(10, 20, 30);
Arguments [10, 20, 30]

As you can see, logging arguments from within the arrow function returns the same arguments object that's crafted for the outer f() function.

We can even confirm this using ===:

function f() {
   var fArguments = arguments;
   var f2 = () => {
      console.log(arguments == fArguments);
   };
   f2();
}

f(10, 20, 30);
true

See, arguments inside the arrow function is indeed the same arguments object that's owned by f().