Arrow Functions (Preview)
In JavaScript, functions are normally declared with the function
keyword, like so:
function helloWorld() { } var helloWorld = function() { };
However, you may have run into the following scenario before: you have an object with a function that calls something asynchronously– such as an AJAX request– that must call *another* function on that object once the asynchronous call completes. This is where we run into an issue with JavaScript’s scope behavior.
Dynamic Scope in JavaScript
There are a couple of ways to do this. First is to cache a reference to the this
variable before the asynchronous call:
class Dog { bark() { var self = this; request .get('/bark') .success(function(result) { self.finishBark(); }); } finishBark() { } }
The issue is, inside of the success callback, the variable this
actually is not an instance of the Dog class. In JavaScript the this
variable’s scope is dynamic. That is, what this
is actually depends on who is the caller of the function rather than where the function is actually defined in the code.
Another way to fix this is to use the bind
function:
class Dog { bark() { var self = this; request .get('/bark') .success(this.finishBark.bind(this)); } finishBark() { } }
Instead of defining a separate function for the success callback, we are calling bind
on the finishBark
function. When you call bind
on a function, a new function is returned with the scope bound to the parameter passed into the bind
function call. In this case, we are passing in the instance of the Dog class into the bind
call so that the code behaves similarly to our first example.
Arrow Functions
ES6 provides a convenient new way to define functions while simultaneously fixing the dynamic scope issue. The arrow function, also called a “fat arrow” function, can be defined without the function
keyword. Rather, it uses a set of parenthesis and the “fat arrow” to define the function:
function add(a, b) { return a + b; } // ... is the same as ... var add = (a, b) => { return a + b; } // ... which is also the same as ... var add = (a, b) => a + b
This may seem confusing at first, but it’s quite simple– we define the method signature with a simple set of parenthesis and the parameters enclosed within. In the above examples, the parameters for the function are “a” and “b”. Second, the function body is declared inside a set of curly braces.
Notice in the first example that we include both curly braces and the return
keyword. However, both of these are omitted in the second example. This is because arrow functions are designed to be more compact, and therefore have a couple of optional features.
If the function you declare is a single statement, you can omit both the curly braces and the return
keyword. Instead, you can place the statement directly after the fat arrow like so:
var add = (a, b) => a + b; var subtract = (a, b) => a - b; var multiply = (a, b) => a * b; var divide = (a, b) => a / b;
This is very convenient when you use the fat arrow functions as map, filter, or reduce functions. Compare the following implementations, for example:
[1, 2, 3, 4, 5] .map(function(number) { return number * 3; }) .filter(function(number) { return number % 2 == 1 }) .reduce(function(carry, number) { return carry + number; }, 0); [1, 2, 3, 4, 5] .map((number) => number * 3) .filter((number) => number % 2 == 1) .reduce((carry, number) => carry + number, 0);
As you can see, the arrow function notation is significantly easier to read and much more succinct.
Lexical Scope
We previously reviewed the concept of “dynamic scope” in JavaScript– the scope, or this
object, depends on what calls the function, not where the function is defined. Normally we do not care about this behavior, or even use it to our advantage, but there’s many places where you just want the scope of an inner function to not change.
The arrow functions will automatically bind their scope to that where the function is defined. In a way, this is very similar to making a call to bind
.
class Dog { bark() { request .get('/bark') .success(function(res) { this.finishBark(res); }.bind(this)); // ... is essentially equivalent to this ... request .get('/bark') .success((res) => { this.finishBark(res); }); } finishBark(result) { } }
Because the scope is bound automatically with the arrow function, we can get rid of the ugly call to bind
, which makes the code more concise and readable.