In a previous post we took a first look at arrow functions in ES6. This post will demonstrate another feature of arrow functions, as well as some additional scenarios where you might find arrow functions to be useful.
Every JavaScript programmer knows that the this pointer is a bit slippery. Consider the code in the following class, particularly the code inside of doWork.
class Person { constructor(name) { this.name = name; } doWork(callback) { setTimeout(function() { callback(this.name); }, 15); } }
The anonymous function the doWork method passes into setTimeout will execute in a different context than doWork itself, which means the this reference will change. When the anonymous function passes this.name to a callback, this won’t point to a person object and the following test will fail.
var person = new Person("Scott"); person.doWork(function(result) { expect(result).toBe("Scott"); // fail! done(); });
Since developers can never rely on JavaScript’s this reference when asynchronous code and callbacks are in the mix, a common practice is to capture the value of this in a local variable and use the local variable in place of this. It’s common to see the local variable with the name self, or me, or that.
doWork(callback) { var self = this; setTimeout(function() { callback(self.name); }, 15); }
The above code will pass, thanks to self capturing the this reference.
If we use an arrow function in doWork, we no longer need to capture the this reference!
doWork(callback) { setTimeout(() => callback(this.name), 15); }
The previous test will pass with the above code in place because an arrow function will automatically capture the value of this from its outer, parent function. In other words, the value of this inside an arrow function will always be the same as the value of this in the arrow’s enclosing function. There is no more need to use self or that, and you can nest arrow functions arbitrarily deep to preserve this through a series of asynchronous operations.
If you embrace the arrow function syntax, you might find yourself using the syntax with every few lines of code you write. JavaScript’s affinity for asynchronous operations has created an enormous number of APIs requiring callback functions. However, as JavaScript is a solid functional programming language, more and more synchronous APIs have also appeared with function arguments.
For example, ES5 introduced forEach and map methods on array objects.
let result = [1,2,3,4].map(n => n * 2); expect(result).toEqual([2,4,6,8]); let sum = 0; [1,2,3,4].forEach(n => sum += n); expect(sum).toBe(10);
There are also many libraries like underscore and lodash that use higher order functions to implement filtering, sorting, and grouping of data. With ES5 the code looks like the following.
let characters = [ { name: "barney", age: 36, blocked: false }, { name: "fred", age: 40, blocked: true }, { name: "pebbles", age: 1, blocked: false } ]; let result = _.find(characters, function(character) { return character.age < 40; });
With arrow functions, the result calculation becomes more expressive.
let result = _.find(characters, character => character.age < 40); expect(result.name).toBe("barney");
You can even write a Jasmine test using an arrow function.
it("can work with Jasmine", () => { var numbers = [1,3,4]; expect(numbers.length).toBe(3); });
Many of the examples shown in this section, like the examples using forEach, map, and _, all work with the concept of collections. Other feature of ES6, like iterators, will make working with collections noticeably more flexible and reliable. We’ll turn out attention to iterators next.