All Aboard the ES6 JavaScript Train

The sixth ECMAScript specification has been finalized last june, and there’s a flurry of JavaScript features coming our way. You can start using these features today, provided that you know the right set of tools for the job. Today I’ll be covering the most exciting new features as well as a couple of different ways how you can get started using them right away.

Improve this article
Nicolás Bevacqua
| 12 minute read | 5

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 learning ES6. To date, no browser has 100% coverage on ES6. The specification is still fresh, and implementations are under active development. Relying solely on JavaScript engines of today means that you’ll find many ES6 features work improperly, are only partially implemented, or are completely missing. The solution is to use a transpiler.

Transpilation is just a fancy way of saying compilation between 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! As 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.

What about ES6 and Node.js?

On the server-side, you (as opposed to your customers when it comes to browsers) are in charge of picking the runtime version. The most recent versions of Node.js have aggressively updated their dependency on v8, the JavaScript engine that powers Google Chrome, to the latest and greatest. That means if you’re using one of the latest versions of Node.js (v4.x.x at the time of this writing), you have all the same ES6 goodies that are available in your Google Chrome browser.

Sometimes you want to keep your existing version of Node.js for most projects but also want to use a recent version because of ES6. In those cases, you can use nvm to manage multiple versions of Node.js, allowing you to keep your existing projects intact while adopting the latest ES6 features in your newer projects. It’s easy to use nvm, you just open up a command-line, type commands such as nvm install $VERSION or nvm use $VERSION, and you’re set.

Juggling many different versions of node may prove to be too much of a hassle to you. An alternative may be to use babel-node, a CLI utility that transpiles your ES6 code before executing it through node. You can install babel-node, along with the standalone Babel transpiler, using the command shown below.

js npm install babel --global

While babel-node works great in development, it’s not recommended for production as it can introduce significant lag in startup time. A better approach might be to precompile your modules before every deployment. That way your application won’t need to recompile itself at startup.

A babel-node CLI demonstration
A babel-node CLI demonstration

Using babel-node can add ES6 support to older versions of Node.js. Upgrading to the latest version of Node.js, where possible, is always recommendable.

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 favorites: the most practical.

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 that they use backticks as a delimiter instead of single or double quotes. All the strings found 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 multiline 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

Template literals also let you add variables into the mix by wrapping them in an interpolation, as seen below.

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'

Let’s move onto a some other practical features.

Block Scoping, let, and const

The let statement is one of the most well-known features in ES6. It works like a var statement, but it has different scoping rules. JavaScript has always had a complicated ruleset when it came to scoping, driving many programmers crazy when they were first trying to figure out how variables work in the language. 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 JavaScript in ES6, but it’s fairly commonplace in other languages, like Java or C#.

Let’s look at some of the differences. If you had to declare a variable using var in a code branch for an if, your code would look like in the following snippet.

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

That represents a problem because – as we know – the process known as hoisting means that the declaration for temp will be “pulled” to the top of its scope. Effectively, our code behaves as if we wrote the snippet below. 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 using 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 behavior you may not expect 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”. Most often than not, this will catch errors in user code rather than represent a problem, though. Attempts to access variables before they were declared usually lead to unexpected behavior 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';

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 will result in syntax errors, as the compiler is able to tell that you’re trying to assign to a const variable.

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

Note that const only means that the declaration is a constant reference, but it doesn’t mean that 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 without the parenthesis, but you’ll need them for methods with zero, two, or more parameters. Note that you can still use the parenthesis when you have a single parameter, but it’s more concise to omit them.

[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 spend 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 example below 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 we 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.

Further Reading

Here are some resources that will help you start taking advantage of ES6 today.

ES6 in Depth

I wrote a series of over 20 articles that will give you a comprehensive understanding of ES6. Each article covers a specific feature or aspect of the language that changes in ES6, and they’re easy to navigate with tons of examples and practical considerations. These articles cover everything we’ve discussed so far in this article, in addition to promises, rest and spread, iterators, generators, proxies, collections, changes to Math, Number, Object, and String, classes, symbols and reflection. It’s a great way to dive head first into ES6.

Screenshot of article headlines on Pony Foo
Screenshot of article headlines on Pony Foo

The articles on Pony Foo in the ES6 series cover topics, ranging from getting started to mastering Proxies and Generators.

If in-depth series are your thing, you might also want to check out these two resources.

  • Axel Rauschmayer’s Blog Posts – While he doesn’t have a formal series on ES6, he wrote dozens of detailed articles describing the technical depths of ES6.
  • ES6 in Depth by Mozilla Hacks – Written by several authors affiliated with Mozilla, it covers many of the same topics as the series on Pony Foo.

ECMAScript 6 Compatibility Table

When you get started with ES6, you’ll quickly realize no browser quite supports ES6 100% in their stable distribution and without any flags. In the meantime, you can leverage these compatibility tables to understand what features are implemented across what browsers. Alas, these tables are mostly useful for research as your best bet when it comes to using ES6 today is to rely on a transpiler like Babel.

Quick Start

If you want to get started right away, your best bet might be to hop into the Babel REPL and start toying around with it. They also have a great article for learning the basics about ES6 language features and syntax.

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments (5)

Jun Shen wrote

An important aspect of arrow functions is that they are lexically scoped. That means you can kiss your var self = this statements goodbye. The example below 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 we mentioned earlier.

lexical scope doesn’t mean the bind of “this” in a function. The definition of lexical scope is an inner function can also access the variables defined in the outer function, that’s it, nothing involves the context of the function.

So saying because of lexical scope, the inner function’s “this” now points to the same context as the outer function, this is wrong. So the feature is merely the inner arrow function automatically inherit the context of the outer function.

Nicolas Bevacqua wrote

Jun, while what you describe might be more precise, pointing that out would’ve unnecessarily had complicated an otherwise very introductory article.

Thanks for the suggestion!

Greg wrote

Great article! I find a huge barrier to using ES6 the build tools. Webpack is crazy complex configuration to setup, and if you need to change any setting it’s difficult. I wish there was a simple gulp starter with babel 6, ES2015 preset, and I guess Browserify to transform Babel’s output for the browser.

Lex wrote

Thanks for great article. By the way you have typo in word “wrap” at the next place: “you could wrpa the right-hand part”

Nicolas Bevacqua wrote

Oops! Fixed.