Learning ES6: Arrow Functions

right arrow sign

So far in the Learning ES6 series, we’ve looked at block scoping, default parameters, destructuring, rest & spread operators, for-of operator, and template literals. Today let’s continue the series to learn about arrow functions, a.k.a. “fat arrow” functions.

TL;DR

Arrow functions are more or less a shorthand form of anonymous function expressions that already exist in JavaScript. In ES6 this looks like:

let squares = [1, 2, 3].map(x => x * x);

Is equivalent to this in ES5:

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

As you can see a lot of the verbosity of old-style function expressions is removed and what’s left is the fat arrow (=>) joining the two main ingredients of the function: the arguments and function body.

You’ll find the greatest utility in arrow functions in places where functions take an anonymous callback function, like event handlers (such as onClick, $.ajax, etc.) and array processors (such as map, sort, etc.)

Interested in learning about arrow functions in more detail? Feel free to check out the arrow functions code examples page, which shows off the features in great detail. You can also try your hand at ES6 katas to see how much you’ve really learned.

Let’s keep going!

Arrow function syntax

Arrow functions can have several combinations of syntaxes depending on the needs of the function.

Arrow function arguments

When the arrow function has multiple arguments or no arguments at all, the syntax looks like:

// two or more arguments
var sum = [9, 8, 7].reduce((memo, value) => memo + value, 0);

// no arguments
var getRandom = () => Math.random() * 100;

Notice the parentheses surrounding the arguments. However, when the arrow function has a single argument, the parenthesis can be removed:

var valuesShallowCopy = [‘foo’, ‘bar’].map(x => x);

The full form of an arrow function body supports multiple statements within a block:

$('#deleteButton').click(event => {
	if (confirm('Are you sure?')) {
		clearAll();
	}
});

You can still include the parentheses even when there is a single argument.

Arrow function body

Notice in the previous example the curly braces surrounding the statement body block. However, when the arrow function only has one statement, the curly braces defining the block can be omitted:

var activeCompanies = companies.filter(company => company.active);

Omitting the curly braces also denotes a single expression which is implicitly returned. You do not need to include the return keyword. However, if you want to return an object, you must wrap that object in parenthesis otherwise it is interpreted as a code block:

// BUG! BUG! BUG!
// will return an array of undefined values since the code block
// is empty and returns nothing
var myObjects = myArray.map(value => {});
console.log(myObjects);

// Correct!
// will return an array of empty objects
var myObjects = myArray.map(puppy => ( {} ) );
console.log(myObjects);

// BUG! BUG! BUG!
// will return an array of undefined values since the code block
// looks like it has a label of “foo” and an expression of “x” that is
// NOT returned.
console.log([4, 5, 1].map(x => {foo: x} ));

// Correct!
// will return an array of objects with “foo” as key and number
// as value
console.log([4, 5, 1].map(x => ( {foo: x} ) ));

You’ll find that arrow functions come in most handy when used as an anonymous callback function. The various higher-order functional programming array methods that were introduced with ECMAScript 5 (like map, filter, forEach, reduce, etc.) work well with arrow functions. Arrow functions can also be used as callback functions for event handlers, but typically in an OOP world those end up being (private) methods on your class so that they can be properly unit tested. Arrow functions are not meant for prototype- or class-based methods. We’ll get into why in a bit.

Immediately-invoked arrow functions (IIAFs)

If you recall immediately-invoked function expressions (IIFEs), they allow you to define a function expression and call it immediately in order to shield the code from the rest of the program by scoping it within the function. Here’s a simplified example:

(function(message) {
	// print out each character of message
	for (let char of message) {
		console.log(char);
	}
}) ('hello');

The same can be done with arrow functions:

(message => {
	// print out each character of message
	for (let char of message) {
		console.log(char);
	}
}) ('hello');

The only thing to be cognizant of is the location of the parenthesis. They have to wrap the arrow function expression before the parentheses that cause the invocation (before the ('hello') part). With IIFEs, the parentheses could also go around the whole IIFE including the function invocation.

Lexical this

Arrow functions are not just syntactic sugar. Arguably the best thing about arrow functions, aside from their terse syntax, is that this uses lexical scoping; its value is always “inherited” from the enclosing scope.

Let’s look at a JavaScript coding problem with this that should help explain things:

var car = {
	speed: 0,
	accelerate: function() {
		this.accelerator = setInterval(
			function() {
				// BUG! _this_ is not what we expect.
				// In non-strict mode, _this_ is the
				// global object. In strict mode _this_
				// is undefined. In neither case is _this_
				// the car object we want.
				this.speed++;
				console.log(this.speed);
			},
			100
		);
	},
	cruise: function() {
		clearInterval(this.accelerator);
		console.log(`cruising at ${this.speed} mph`);
	}
};

car.accelerate();

setTimeout(function() { car.cruise(); }, 5000);

Every newbie JavaScript developer has run into this problem because they didn’t know any better. Every experienced developer has accidentally run into this problem even though they knew better. In ES3 the approach to fix this problem was to store a reference to this in a variable called self, that orm so that it was available in the scope of the anonymous function:

var car = {
	speed: 0,
	accelerate: function() {
		// store a reference to `this` in a variable that will be
		// be available for use within the anonymous function
		// callback
		var self = this;
		this.accelerator = setInterval(
			function() {
				self.speed++;
				console.log(self.speed);
			},
			100
		);
	},
	cruise: function() {
		clearInterval(this.accelerator);
		console.log(`cruising at ${this.speed} mph`);
	}
};

car.accelerate();

setTimeout(function() { car.cruise(); }, 5000);

Alternatively, with ES5, we could create a new function using the bind method that would pass the desired this context to the anonymous function:

var car = {
	speed: 0,
	accelerate: function() {
		this.accelerator = setInterval(
			// bind returns a new “cloned” function
			// such that _this_ within the function
			// matches _this_ outside of it by passing it
			// as the argument.
			(function() {
				this.speed++;
				console.log(this.speed);
			}).bind(this),
			100
		);
	},
	cruise: function() {
		clearInterval(this.accelerator);
		console.log(`cruising at ${this.speed} mph`);
	}
};

car.accelerate();

setTimeout(function() { car.cruise(); }, 5000);

With ES6, all of this nonsense is cleaned up nicely because arrow functions have implicit this binding:

var car = {
	speed: 0,
	accelerate: function() {
		this.accelerator = setInterval(
			() => {
				// _this_ is the same as it is outside
				// of the arrow function!
				this.speed++;
				console.log(this.speed);
			},
			100
		);
	},
	cruise: function() {
		clearInterval(this.accelerator);
		console.log(`cruising at ${this.speed} mph`);
	}
};

car.accelerate();

setTimeout(() => car.cruise(), 5000);

Now everyone’s happy. It’s worth noting that transpilers use the ES3 solution for transpiling the lexical this. The only difference is they use an auto-generated variable name (like $__1) instead of self, that or m. They do not use the ES5 bind method, making the transpiled code ES3 compatible. This means that the transpiled code will work in non-ES5 browsers such as IE8.

Identifying arrow functions

Although arrow functions look dramatically different in their syntax and use lexical scoping for this (as well as other constructs), they are still identified as functions. For example, typeof and instanceof both say arrow functions are functions:

console.log(typeof function() { });  // 'function'
console.log(typeof (() => {}));  // 'function'
console.log(function() { } instanceof Function);  // true
console.log((() => {}) instanceof Function);  // true

Arrow functions and default parameters

Since arrow functions are really just regular functions, they also can also be used with default parameters!

// 2nd parameter is `undefined`, triggers
// default of 100.
// output: 2, 200, 10
console.log(
	[1, undefined, 5].map(
		(x=100) => x * 2
	)
);

As learned earlier, when an arrow function just has one parameter that’s an identifier, we can omit the parentheses around it. However, that rule only applies when that parameter is only an identifier. If it has a default value, as in our example above, parentheses are needed.

Lexical arguments

Just like arrow functions do not define their own dynamic this, arrow functions also do not define their own dynamic arguments object either. For instance, you cannot have an arrow function that takes no parameters and then access the arguments object to gain access to any passed parameters like you can with formal functions.

Instead, the arguments object is “inherited” from the lexical scope of the containing function just like this. That arguments object is then available no matter where the arrow function is executed later on. Let’s take a look at a contrived example:

function genArrowReturningLexArgs() {
	// returns an arrow function expression
	// which itself returns the arguments used
	// when generating the arrow function
    return () => arguments;
}

var arrowFunction = genArrowReturningLexArgs(5, 'foo', [5,4,3]);

// log arguments object with
// 5, 'foo', and [5,4,3]
console.log(arrowFunction());

In order to gain access to the arguments of an arrow function, you either have to name all of the parameters you want access to or use other new ES6 function features like rest parameters.

JavaScript is playing catch-up

JavaScript didn’t invent function expressions or lambda functions. They were actually first introduced by Lisp way back in 1958. However, the explosion of highly-interactive, client-side programming in JavaScript with asynchronous event-handling function callbacks brought about a sort of lambda function renaissance. JavaScript and lambda functions became so popular that other long-standing programming languages started adopting lambda functions:

  • C#a => a > 0
  • Pythonlambda a: a > 0
  • Javaa -> a > 0
  • JavaScriptfunction(a) { return a > 0; }

As you can see, JavaScript ended up becoming the most verbose. ES6 arrow functions are now trimming the syntax fat.

Summary of arrow function differences

While we learned that arrow functions are in fact identified as a functions, there are important differences between them and traditional functions. Let’s summarize:

Lexical this binding. As we’ve covered, the value of this within an arrow function is determined by where the arrow function is defined and not by where it is used. This is lexical scoping.

Can’t touch this. The value of this inside of the function cannot be changed; it remains the same value throughout the entire lifecycle of the function. Arrow functions throw an error when you try to change this.

No arguments object. You cannot access arguments through the arguments object, you must use regular named parameters or other ES6 features such as rest parameters.

Not newable. Arrow functions do not have an internal [[Construct]] method and therefore cannot be used as prototype constructors. Arrow functions throw an error when used with new.

JavaScript engine support

According to the ECMAScript 6 compatibility table, all of the modern JavaScript engines support arrow functions except for Safari and iOS9. We of course know IE 11 and lower don’t support any of the ES6 features.

Additional Resources

You can check out the Learning ES6 examples page for the Learning ES6 Github repo where you will find all of the code used in this article running natively in the browser (for those that support arrow functions). There are also examples running through Babel and Traceur transpilation.

You can also practice everything you’ve learned about template literals and tagged templates on ES6 Katas. It uses a TDD (test-driven development) approach for you to implement ES6 features such that all of the tests pass. It’s really cool!

Other super helpful resources:

Coming up next…

We will be continuing the Learning ES6 series by looking at enhanced object literals. They are enhanced because we can write less code to build them! Until then…

FYI

This Learning ES6 series is actually a cross-posting of a series with the same name on my personal blog, benmvp.com. The content is pretty much the exact same except that this series will have additional information on how we are specifically leveraging ES6 here in Eventbrite Engineering when applicable. I’ll also be tackling the features in a different order than I did in my personal blog. The original arrow functions blog post can be found here.

One thought on “Learning ES6: Arrow Functions

  1. One of the most well written series of tutorial on ES6. Thank you & I intend to follow along with these articles.

Leave a Reply

Your email address will not be published. Required fields are marked *