How to use the top ES6 features today

Nicolas Bevacqua walks through the first steps of ES6, the latest standardised JavaScript language features.

ES6: partial support

No browser fully supports ES6 yet. Click through for an in-depth guide.

The sixth ECMAScript specification was finalised in June 2015, and there is now a flurry of JavaScript features coming our way. You can start using these features today, provided you know the right tools for what you want to do. In this tutorial I'll be covering the most exciting new features, as well as a couple of different ways you can start using them in your projects right away.

Understanding the new language features, and how they can be used, will help you decide whether to adopt ES6 or stay in the ES5 camp. You may want to be on the bleeding edge and consider ES6 a critical addition to the productivity of your team, but it comes at the cost of you and your team taking the time to learn ES6.

To date, no browser has 100 per cent coverage on ES6. The specification is still fresh, and implementations are under active development. Relying solely on today's JavaScript engines means you'll find many ES6 features don't work, are only partially implemented, or are completely missing.

Transpilers

The solution to the patchy browser support is to use a transpiler. Transpilation is just a fancy way of saying the compilation of similar languages. By using a transpiler to transform ES6 code into ES5, browsers (which have great ES5 support nowadays) can interpret ES6 without complaining, since you feed them the compiled ES5 code.

Transpilers provide a consistent baseline for your ES6 programs, but they aren't perfect. Proxies, for example, are very hard to represent in ES5 code, and are unavailable to transpilers.

You can use the online Babel REPL to try out the examples in this article. You can write ES6 code on the left, and see the transpiled version change in real time on the right. It's a treat when it comes to learning ES6. For developing entire applications in ES6, your best bet is to pair the Babel transpiler with webpack or Browserify to handle multiple interconnected small modules.

ES6: Babel transpiler

If you want to start using ES6 right now, you’ll need to use a JavaScript transpiler like Babel

The latest version of the language includes features such as arrow functions, template literals, block scoping, collections, native promises, and many more. I can't possibly cover all of it in one go, but we'll go over my favourites, and the most practical features.

Template Literals

In ES6 we get templates that are similar to those in Mustache, but native to JavaScript. In their purest form, they are almost indistinguishable from regular JavaScript strings, except they use backticks (`) as a delimiter, instead of single or double quotes.

All the strings below are equivalent:

"The quick brown fox jumps over the lazy dog"
'The quick brown fox jumps over the lazy dog'
`The quick brown fox jumps over the lazy dog`

Backticks aren't convenient just because they rarely appear in text. These strings can be multi-line without any extra work on your behalf. That means you no longer need to concatenate strings using +, or join them using an array.

var text = `This is
a multi-line
string.`;

Template literals also let you add variables into the mix by wrapping them in an interpolation:

var color = 'brown';
var target = 'lazy dog';
`The quick ${color} fox jumps over the ${target}`;
// <- 'The quick brown fox jumps over the lazy dog'

You're not limited to variables; you can interpolate any expression in your templates.

var input = -10;
var modifier = 2;
`The result of multiplying ${input} by ${modifier} is ${input * modifier}`;
// <- 'The result of multiplying -10 by 2 is -20'
`The absolute value for ${input} is ${Math.abs(input)}`;
// <- 'The absolute value for -10 is 10'

Block scoping and let

ES6: block scoping

Function declarations are block scoped in ES6, meaning they won’t be able to leak secrets placed in block-scoped variable declarations

Let's move on to some other practical features. The let statement is one of the most well-known features in ES6. It works like a var statement, but has different scoping rules. JavaScript has always had complicated scoping rules, which drove many programmers crazy when they were first trying to figure them out.

Declarations using var are function-scoped. That means var declarations are accessible from anywhere in the function they were declared in.

On the other hand, let declarations are block.scoped. Block scoping is new to JS in ES6, but it's fairly commonplace in languages like Java or C#.

Let's look at some differences between the two. If you had to declare a variable using var in a code branch for an if, your code would look like this:

function sortCoordinates (x, y) {
if (y > x) {
var temp = y;
y = x;
x = temp;
}
return [x, y];
}

That represents a problem because the process known as hoisting means the declaration for temp will be 'pulled' to the top of its scope. Effectively, our code behaves as if we've written the following snippet. For this reason, var is ineffective when dealing with variables that were meant to be scoped to code branches.

function sortCoordinates (x, y) {
var temp;
if (y > x) {
temp = y;
y = x;
x = temp;
}
return [x, y];
}

The solution to that problem is to use let. A let declaration is also hoisted to the top of its scope, but its scope is the immediate block (denoted by the nearest pair of brackets), meaning that hoisting won't result in unexpected behaviour or variables getting mixed up.

Even though let declarations are still hoisted to the top of their scope, attempts to access them in any way before the actual let statement is reached will throw. This mechanism is known as the 'temporal dead zone'.

However, rather than being a problem, this feature will in fact often help users catch errors in their code. Attempts to access variables before they were declared usually led to unexpected behaviour when using var, so it's a good thing that let prevents it entirely.

console.log(there); // <- runtime error, temporal dead zone
let there = 'dragons';

Const declarations

In addition to let declarations, we can also observe const declarations being added to the language. In contrast with var and let, const declarations must be assigned to upon declaration.

const pi = 3.141592653589793;
const max; // <- SyntaxError

Attempts to assign to a different value to a const variable result in syntax errors, as the compiler can tell you're trying to assign to a const variable.

const max = 123;
max = 456; // <- SyntaxError

Note that const only means the declaration is a constant reference – it doesn't mean the referenced object becomes immutable. If we assign an object to a const variable client, we won't be able to change client into a reference to something else, but we will be able to change properties on client like we're used to with other declaration styles.

const client = getHttpClient('http://ponyfoo.com');
client.maxConcurrency = 3;
// works because we're not assigning to client

Arrow functions

These are probably the best known feature in ES6. Instead of declaring a function using the function keyword, you can use the _"arrow"_ notation, as seen below. Note how the return is implicit, as well as the parenthesis around the x parameter.

[1, 2].map(function (x) { return x * 2 }); // ES5
[1, 2].map(x => x * 2); // ES6

Arrow functions have a flexible syntax. If you have a single parameter you can get away with dropping the parenthesis (although you can include it if you want), but you'll need it for methods with zero, two, or more parameters.

[1, 2].map((x, i) => x * 2 + i); // <- [2, 5]

If your method does more than return the results of evaluating an expression, you could wrap the right-hand part of the declaration in a block, and use as many lines as you need. If that's the case, you'll need to add the return keyword back again.

x => {
// multiple statements
return result
}

An important aspect of arrow functions is that they are lexically scoped. That means you can kiss your var self = this statements goodbye.

The following example increases a counter and prints the current value, every second. Without lexical scoping you'd have to .bind the method call, use .call, .apply, or the self hack that I mentioned earlier.

function count () {
this.counter = 0;
setInterval(() => console.log(++this.counter), 1000);
}
count(); // <- 1, .. 2, .. 3, ...

Arrow functions are recommended for short methods, like those typically provided to .map and .filter iterators. Using arrow functions may take you a day or two to get used to, but you'll quickly fall in love with the new syntax.

Note that you probably shouldn't use arrow functions everywhere. Instead, consider them for each case. For example, an arrow function might be a good replacement of anonymous functions, but not so much for named functions.

Conclusion

ES6: Ponyfoo

The articles on Pony Foo in the ES6 series cover topics, ranging from getting started to mastering proxies and generators

Now you've learned about Babel, template literals, block scoping, let, const and arrow functions, you're ready to give ES6 a shot for the first time. Head on to the Babel REPL and start trying out code.

Words: Nicolas Bevacqua

Nicolás Bevacqua is a JavaScript and web performance consultant. This article originally appeared in issue 276 of net magazine.

Liked this? Read these!