JavaScript ES6: Arrays and Arrow Functions

Let’s explore working with Arrays in a functional style using JavaScript ES6.

Map an Array with an arrow function

Say you want to transform each element of an Array. Take each value, pass it to a function, and get a new value. These values go into a new Array. In ECMAScript 5, you would use the map method, like so:

var input = [1, 2, 3];
var output = input.map(function (x) {
return x * x;
});

That function (x)… syntax is clunky. It’s redundant to type out function and return every time you want to write a function that simply returns a new value, with no side-effects. In ECMAScript 6, a call to map looks like this:

const input = [1, 2, 3];
const output = input.map(x => x * x);

This code is clear and concise. We replaced the old function block with an arrow function, which takes the form arguments => expression. An arrow function is a concise declaration of a value transformation.

Arrow functions improve readability

An arrow function is ideal when passing a function as an argument to another function like map. The syntax is shorter and often fits elegantly between enclosing parentheses on a single line.

More importantly, an arrow function does not reassign this within its scope. Inside of a traditional JavaScript function block, the value of this may vary. This matters when working with functions in the context of a class instance.

Within a scope where this points to a class instance, a new function will reassign this within its own new scope, like so:

var Foo = function (number) {
this.number = number;
// this === the new instance of Foo, as expected
}
Foo.prototype.bar = function (array) {
return array.map(function (x) {
return x * this.number;
// this === a new instance of something we DON'T WANT! BAD!
// this.number === undefined (!!!)
});
};

Sure, you could fix bar like so:

Foo.prototype.bar = function (array) {
var self = this;
return array.map(function (x) {
return x * self.number;
// self === the instance of Foo, as expected
// self.number === something expected
});
};

This is not elegant. We’re working around a language feature that we don’t want to be using in the first place. Let’s rewrite class Foo using ES6:

class Foo {
constructor(number) {
this.number = number;
}
bar(array) {
return array.map(x => x * this.number);
}
}

Throughout the class definition, this points to exactly what a reader of the code naively expects—the instance of Foo—and we haven’t had to write any extraneous code like the infamous var self = this. Now, we’re working with the language, rather than against it.

Arrow function blocks

An arrow function can also imply an imperative block of statements rather than an expression, like so:

const foo = (array, number) => {
const bar = number * number;
return array.map(x => x * bar);
};

Note that a JavaScript function (arrow or otherwise) is not required to explicitly return a value. (Of course, all statements return a value in JavaScript, so your “void” functions still implicitly return undefined).

Conclusion

JavaScript Array methods like filter, map, and reduce marry harmoniously with ES6 arrow functions. Used together they are a robust, expressive way to manipulate data.

In general, start with expression-based arrow functions first, and expand your expressions into blocks when necessary. Avoid “dead-end” functions that silently mutate values and return nothing. Instead, start by writing functions that transform values with no side-effects, and compose these functions together like tiny, powerful code bricks, to produce the data you want. Separate your data computation logic from the necessary side-effects that consume it. (After all, your program should eventually do something…)

Keep it functional, and happy coding.