Arrow Functions – Taking JavaScript in the Right Direction
The sixth major update of the ECMAScript specification has seen the introduction of many exciting new features including block scope, classes, template literals, default parameters and destructuring assignments, but the feature that seems to have piqued the most interest is the inclusion of the new arrow function syntax.
Show Me the Way
Arrow functions make use of the => operator to write functions in a more succinct fashion. As an example, let’s look at a simple function that takes two parameters, multiplies them together and returns the result. In ES5, we would write this as below:
var multiply = function(x, y) { return x * y; };
In ES6, using arrow functions we replace the function keyword with the arrow operator, which is placed after the arguments list, like so:
var multiply = (x,y) => {return x * y};
Now because in our example, the function consists of only a single statement, we don’t need the braces, and because with arrow functions the return is implicit, we can exclude that too. Our example can therefore be written as:
var multiply = (x,y) => x * y; multiply(2, 3); // 6
Note that the parentheses would also be optional if our function took only a single argument, so the following might be how we define a function to double a value:
var double = x => x * 2; double(2); // 4
Keep it Clean
Okay, so you might be thinking that there is not a massive difference here from a traditional function expression. And you’d be right, but these small changes can really help de-clutter our code. To demonstrate, let’s a take a quick dive into the world of functional programming.
Haskell Curry was an American mathematician, who not only gives his name to the Haskell programming language, but also to the process known as currying. Currying is a technique that we can use to break down the logic of a function taking multiple arguments, into a sequence of functions each taking a single argument. If we take our multiply function above, we can see that it takes two arguments, x and y, performs a computation to multiply the values together and returns the result. We can curry the function to produce a new function taking a single argument that returns another function, again taking a single argument… to return the multiplied value. Sounds complicated? Well not really. In ES5 we would achieve this as below:
var multiply = function(x) {
return function(y) {
return x * y;
};
};
multiply(3)(6); // 18
It’s a simple example, but it’s already becoming difficult to read. The boilerplate syntax only serves to obfuscate the intent. So how would this look if we used arrow functions?
var multiply = x => (y => x * y);
And because the arrow operator is right associative, we could even choose to write this without the parentheses as:
var multiply = x => y => x * y;
If you are used to ES5 it might take some time to adjust. We take a value x, which maps to a function taking a value y that multiplies the two values x and y together. Once you have become familiar with the syntax it can only be argued that using arrow functions produces demonstrably cleaner code. As an aside, you can see the power inherent in currying here. If we require a doubling function for example, we can simply partially apply our curried function:
var double = multiply(2); double(5); // 10
This and That
Arrow functions have another particularly well received feature; this is statically bound to the scope in which the function is declared. It's a subtle but important difference as the example below will illustrate:
var img = new Image();
this.message = "The image has been loaded successfully"; img.onload = function() { console.log(this.message); }; img.src = "http://www.cap-hpi.com/cap-hpi/img/svg/svg/logo-cap-hpi.svg";
When the image is loaded and the callback is invoked, the message that is logged is undefined. The this keyword is not bound by the same rules as other variables, and is now in the context of the Image object, which has no message property, hence the undefined log message. It can be a particularly confusing aspect of JavaScript programming, especially for beginners. To get around this in ES5, you would usually assign the current value of this to another variable that is lexically scoped and would be accessible within the closure.
var that = this;
img.onload = function() { console.log(that.message); };
Using arrow functions, we know that this is lexically scoped, so we don’t need to hold its state in another variable, and the source of confusion is gone.
img.onload = () => {
console.log(this.message);
};
Don’t Hold Back
As new features are introduced into the JavaScript language it can take a long time for all the browser vendors and platforms to support them. It can take an even longer time for your client base to upgrade to the platforms offering support. So what do we do? Traditionally we’ve coded to support the lowest common denominator; if your customers were stuck using IE8, your codebase was stuck at a level supported by IE8.
More recently, there has been a surge in transpiler technologies. Transpilers allow you to take code written in one language and produce code written in another. We can use them to take our ES6 JavaScript and produce ES5, or even ES3 compliant code. Integrate a transpiler into your build process and you never need worry about browser support again.