ECMAScript 6 Arrow Functions and Function Scope

Arrow functions, a feature of ECMAScript 6, are the latest style for declaring functions as first class entities in JavaScript. The brevity of the arrow function syntax is often their first cited advantage. Instead of declaring a function like this:

1
function(arg) { /* ...do something */ }

…the arrow function declaration looks like this:

1
(arg) => { /* ...do something */ }

If the arrow function takes just one argument, the enclosing parentheses can be skipped. If the body of the function is just one expression, the enclosing braces there can be skipped. A simple arrow function that takes one numeric argument and returns its value modulo 3 would look like this:

1
n => (n % 3)

Defining a range of numbers in an array and applying the “mod 3” arrow function to it produces the following:

1
2
3
4
5
> [...Array(10).keys()]
(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 
> [...Array(10).keys()].map(n => n % 3)
(10) [0, 1, 2, 0, 1, 2, 0, 1, 2, 0]

While they’re new, cool and different, there’s more to arrow functions than brevity. Arrow functions have a different way of managing scope that smooths over a long standing issue with how functions have often had to be coded in the past.

It’s very common to have a method of a Javascript class that uses an anonymous function within it. To provide access to data in the class object from the anonymous function, an extra variable, often called “self” or “that” by convention, has to be assigned to the “this” variable in the outer object, because functions always got a new scope. Having a new scope in a javascript function is often a useful thing, but sometimes awkward adjustments like this have to be made to get access to the right data in the right place.

Arrow functions eliminate the need to make these “self” declarations because they do not define their own scope. Instead, they are given the scope (the “this”) of the enclosing scope. This is a major new feature that eliminates the extra work of the “self” declaration and avoids the possibility of runtime execution errors when “self” is not set up.

Here’s a slightly contrived example showing how scope is used in an old style anonymous function and a new style arrow function (the “monthsArray” could be directly assigned from names.split(‘,’), but the use of forEach() provides a way to demonstrate execution of an anonymous function):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class MonthsOfYear {
    constructor() {
        this.monthsArray = [];
    }
    setMonthsFunc(names) {
        /* Parse names using an old-style anonymous function */
        var self = this;        names.split(',').forEach(function(name, i) {
            // 'this' is the scope of the anonymous function
            self.monthsArray.push(name);        });
    }
    setMonthsArrow(names) {
        /* Parse names using a new-style arrow function */
        names.split(',').forEach((name, i) => {
            // 'this' is the scope of the setMonthsArrow() method
            this.monthsArray.push(name);        });
    }
    getMonth(monthNumber) {
        return this.monthsArray[monthNumber - 1];
    }
}
 
const monthNames = 'January,February,March,April,May,June,' +
    'July,August,September,October, November,December';

Some test code:

1
2
3
4
5
6
7
8
9
10
11
// Run with old style anonymous function
let monthsOfYear = new MonthsOfYear();
monthsOfYear.setMonthsFunc(monthNames);
console.log("Month 1: " + monthsOfYear.getMonth(1));
console.log("Month 12: " + monthsOfYear.getMonth(12));
 
// Run with new style arrow function
monthsOfYear = new MonthsOfYear();
monthsOfYear.setMonthsArrow(monthNames);
console.log("Month 1: " + monthsOfYear.getMonth(1));
console.log("Month 12: " + monthsOfYear.getMonth(12));

The console output is (not surprisingly):

Month 1: January
Month 12: December
Month 1: January
Month 12: December

Managing Scope with “Let”

The “let” keyword, new in ECMAScript 6, does the reverse of what arrow functions do for scope: it adds a scope to restrict access to a variable. It eliminates the hoisting of a variable to the enclosing scope of a code block in a “for” loop as well as in “if” and “while” statements.

1
2
3
4
5
6
7
// With "let", the index variable is not defined outside the "for" statement
for(let i=0; i < 10; i++) { /* do nothing, just for demo */ }
console.log("typeof i " + typeof i);  // output: undefined
 
// With "var", the index variable appears outside the "for"
for(var i=0; i < 10; i++) { /* do nothing, just for demo */ }
console.log("i = " + i);  // output: 10

TL;DR

ECMAScript 6 arrow functions offer more concise syntax and eliminate the need “self=this” kinds of declarations when using anonymous functions in the context of object methods in Javascript; “this” can be used directly in the arrow function, and refers to the scope surrounding the arrow function.

Versions

Chrome desktop browser version 65.0.3325.181 (Official Build) (64-bit)
Safari desktop browser version 11.0.2 (11604.4.7.1.4)
Firefox desktop browser 59.0.1 (64-bit)

References

Arrow functions – JavaScript | MDN

Add a Comment