ECMAScript 6 (ES6): What’s New In The Next Version Of JavaScript

About The Author

Lars Kappert is a Dutch front-end solution architect & lead developer. He specializes in architecture, solutions, tooling, performance, and development of … More about Lars ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

You’ve probably heard about ECMAScript 6 (or ES6) already. It’s the next version of JavaScript, and it has some great new features. The features have varying degrees of complexity and are useful in both simple scripts and complex applications.

In this article, we’ll discuss a hand-picked selection of ES6 features that you can use in your everyday JavaScript coding. Please note that support for these new ECMAScript 6 features is well underway in modern browsers, although support varies. If you need to support old versions of browsers that lack many ES6 features, I’ll touch on solutions that might help you start using ES6 today.

Most of the code samples come with an external “Run this code” link, so that you can see the code and play with it.

Variables

let

You’re used to declaring variables using var. You can now use let as well. The subtle difference lies in scope. While var results in a variable with the surrounding function as its scope, the scope of a variable declared using let is only the block it is in.

if(true) {
   let x = 1;
}
console.log(x); // undefined

This can make for cleaner code, resulting in fewer variables hanging around. Take this classic array iteration:

for(let i = 0, l = list.length; i < l; i++) {
   // do something with list[i]
}

console.log(i); // undefined

Often one would use, for example, the j variable for another iteration in the same scope. But with let, you could safely declare i again, since it’s defined and available only within its own block scope.

const

There is another way to declare block-scoped variables. With const, you declare a read-only reference to a value. You must assign a variable directly. If you try to change the variable or if you don’t a set a value immediately, then you’ll get an error:

const MY_CONSTANT = 1;
MY_CONSTANT = 2 // Error
const SOME_CONST; // Error

Note that you can still change object properties or array members:

const MY_OBJECT = {some: 1};
MY_OBJECT.some = 'body'; // Cool

Arrow Functions

Arrow functions are a great addition to the JavaScript language. They make for short and concise code. We are introducing arrow functions early in this article so that we can take advantage of them in other examples later on. The next code snippet shows an arrow function, with the same function written in the familiar ES5 style:

let books = [{title: 'X', price: 10}, {title: 'Y', price: 15}];

let titles = books.map( item => item.title );

// ES5 equivalent:
var titles = books.map(function(item) {
   return item.title;
});

If we look at the syntax of arrow functions, there is no function keyword. What remains is zero or more arguments, the “fat arrow” (`=>`) and the function expression. The return statement is implicitly added.

With zero or more than one argument, you must provide parentheses:

// No arguments
books.map( () => 1 ); // [1, 1]

// Multiple arguments
[1,2].map( (n, index) => n * index ); // [0, 2]

Put the function expression in a block ({ ... }) if you need more logic or more white space:

let result = [1, 2, 3, 4, 5].map( n => {
   n = n % 3;
   return n;
});

Not only do arrow functions mean fewer characters to type, but they also behave differently from regular functions. An arrow function expression inherits this and arguments from the surrounding context. This means you can get rid of ugly statements like var that = this, and you won’t need to bind functions to the correct context. Here’s an example (note this.title versus that.title in the ES5 version):

let book = {
   title: 'X',
   sellers: ['A', 'B'],
   printSellers() {
      this.sellers.forEach(seller => console.log(seller + ' sells ' + this.title));
   }
}

// ES5 equivalent:
var book = {
   title: 'X',
   sellers: ['A', 'B'],
   printSellers: function() {
      var that = this;
      this.sellers.forEach(function(seller) {
         console.log(seller + ' sells ' + that.title)
      })
   }
}

Strings

Methods

A couple of convenience methods have been added to the String prototype. Most of them basically eliminate some workarounds with the indexOf() method to achieve the same:

'my string'.startsWith('my'); //true
'my string'.endsWith('my'); // false
'my string'.includes('str'); // true

Simple but effective. Another convenience method has been added to create a repeating string:

'my '.repeat(3); // 'my my my '

Template Literal

Template literals provide a clean way to create strings and perform string interpolation. You might already be familiar with the syntax; it’s based on the dollar sign and curly braces ${..}. Template literals are enclosed by backticks. Here’s a quick demonstration:

let name = 'John',
   apples = 5,
   pears = 7,
   bananas = function() { return 3; }

console.log(`This is ${name}.`);

console.log(`He carries ${apples} apples, ${pears} pears, and ${bananas()} bananas.`);

// ES5 equivalent:
console.log('He carries ' + apples + ' apples, ' + pears + ' pears, and ' + bananas() +' bananas.');

In the form above, compared to ES5, they’re merely a convenience for string concatenation. However, template literals can also be used for multi-line strings. Keep in mind that white space is part of the string:

let x = `1...
2...
3 lines long!`; // Yay

// ES5 equivalents:
var x = "1...\n" + 
"2...\n" +
"3 lines long!";

var x = "1...\n2...\n3 lines long!";

Arrays

The Array object now has some new static class methods, as well as new methods on the Array prototype.

First, Array.from creates Array instances from array-like and iterable objects. Examples of array-like objects include:

  • the arguments within a function;
  • a nodeList returned by document.getElementsByTagName();
  • the new Map and Set data structures.
let itemElements = document.querySelectorAll('.items');
let items = Array.from(itemElements);
items.forEach(function(element) {
    console.log(element.nodeType)
});

// A workaround often used in ES5:
let items = Array.prototype.slice.call(itemElements);

In the example above, you can see that the items array has the forEach method, which isn’t available in the itemElements collection.

An interesting feature of Array.from is the second optional mapFunction argument. This allows you to create a new mapped array in a single invocation:

let navElements = document.querySelectorAll('nav li');
let navTitles = Array.from(navElements, el => el.textContent);

Then, we have Array.of, which behaves much like the Array constructor. It fixes the special case when passing it a single number argument. This results in Array.of being preferable to new Array(). However, in most cases, you’ll want to use array literals.

let x = new Array(3); // [undefined, undefined, undefined]
let y = Array.of(8); // [8]
let z = [1, 2, 3]; // Array literal

Last but not least, a couple of methods have been added to the Array prototype. I think the find methods will be very welcome to most JavaScript developers.

  • find returns the first element for which the callback returns true.
  • findIndex returns the index of the first element for which the callback returns true.
  • fill “overwrites” the elements of an array with the given argument.
[5, 1, 10, 8].find(n => n === 10) // 10

[5, 1, 10, 8].findIndex(n => n === 10) // 2

[0, 0, 0].fill(7) // [7, 7, 7]
[0, 0, 0, 0, 0].fill(7, 1, 3) // [0, 7, 7, 7, 0]

Math

A couple of new methods have been added to the Math object.

  • Math.sign returns the sign of a number as 1, -1 or 0.
  • Math.trunc returns the passed number without fractional digits.
  • Math.cbrt returns the cube root of a number.
Math.sign(5); // 1
Math.sign(-9); // -1

Math.trunc(5.9); // 5
Math.trunc(5.123); // 5

Math.cbrt(64); // 4

If you want to learn more about the new number and math features in ES6, Dr. Axel Rauschmayer has you covered.

Spread Operator

The spread operator (...) is a very convenient syntax to expand elements of an array in specific places, such as arguments in function calls. Showing you some examples is probably the best way to demonstrate just how useful they are.

First, let’s see how to expand elements of an array within another array:

let values = [1, 2, 4];
let some = [...values, 8]; // [1, 2, 4, 8]
let more = [...values, 8, ...values]; // [1, 2, 4, 8, 1, 2, 4]

// ES5 equivalent:
let values = [1, 2, 4];
// Iterate, push, sweat, repeat...
// Iterate, push, sweat, repeat...

The spread syntax is also powerful when calling functions with arguments:

let values = [1, 2, 4];

doSomething(...values);

function doSomething(x, y, z) {
   // x = 1, y = 2, z = 4
}

// ES5 equivalent:
doSomething.apply(null, values);

As you can see, this saves us from the often-used fn.apply() workaround. The syntax is very flexible, because the spread operator can be used anywhere in the argument list. This means that the following invocation produces the same result:

let values = [2, 4];
doSomething(1, ...values);

We’ve been applying the spread operator to arrays and arguments. In fact, it can be applied to all iterable objects, such as a NodeList:

let form = document.querySelector('#my-form'),
   inputs = form.querySelectorAll('input'),
   selects = form.querySelectorAll('select');

let allTheThings = [form, ...inputs, ...selects];

Now, allTheThings is a flat array containing the <form> node and its <input> and <select> child nodes.

Destructuring

Destructuring provides a convenient way to extract data from objects or arrays. For starters, a good example can be given using an array:

let [x, y] = [1, 2]; // x = 1, y = 2

// ES5 equivalent:
var arr = [1, 2];
var x = arr[0];
var y = arr[1];

With this syntax, multiple variables can be assigned a value in one go. A nice side effect is that you can easily swap variable values:

let x = 1,
   y = 2;

[x, y] = [y, x]; // x = 2, y = 1

Destructuring also works with objects. Make sure to have matching keys:

let obj = {x: 1, y: 2};
let {x, y} = obj; // x = 1, y = 2

You could also use this mechanism to change variable names:

let obj = {x: 1, y: 2};
let {x: a, y: b} = obj; // a = 1, b = 2

Another interesting pattern is to simulate multiple return values:

function doSomething() {
   return [1, 2]
}

let [x, y] = doSomething(); // x = 1, y = 2

Destructuring can be used to assign default values to argument objects. With an object literal, you can actually simulate named parameters.

function doSomething({y = 1, z = 0}) {
   console.log(y, z);
}
doSomething({y: 2});

Parameters

Default Values

In ES6, meters is defining default values for function parapossible. The syntax is as follows:

function doSomething(x, y = 2) {
   return x * y;
}

doSomething(5); // 10
doSomething(5, undefined); // 10
doSomething(5, 3); // 15

Looks pretty clean, right? I’m sure you’ve needed to fill up some arguments in ES5 before:

function doSomething(x, y) {
   y = y === undefined ? 2 : y;
   return x * y;
}

Either undefined or no argument triggers the default value for that argument.

Rest Parameters

We’ve been looking into the spread operator. Rest parameters are very similar. It also uses the ... syntax and allows you to store trailing arguments in an array:

function doSomething(x, ...remaining) {
   return x * remaining.length;
}

As you can see, this saves us from the often-used fn.apply() workaround. The syntax is very flexible, because the spread operator can be used anywhere in the argument list. This means that the following invocation produces the same result:

let values = [2, 4]; doSomething(1, ...values);

We’ve been applying the spread operator to arrays and arguments. In fact, it can be applied to all iterable objects, such as a NodeList:

let form = document.querySelector('#my-form'),
   inputs = form.querySelectorAll('input'),
   selects = form.querySelectorAll('select');

let allTheThings = [form, ...inputs, ...selects];

Now, allTheThings is a flat array containing the

node and its and