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.
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
string.`;
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.
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.
Comments