The ES6 Guide
By Flavio Copes
Discover the major features added in ES6 (ES2015): arrow functions, promises, classes, let and const, modules, destructuring, and the spread operator.
- Arrow Functions
- Lexical
this - Promises
- Generators
letandconst- Classes
- Modules
- Template Literals
- Default parameters
- The spread operator
- Destructuring assignments
- Enhanced object literals
for...ofloop- Map and Set
- New String methods
- New Object methods
ECMAScript 2015, also known as ES6, is a fundamental version of the ECMAScript standard.
It introduced arrow functions, classes, promises, modules, let, const, destructuring, and many other features that modern JavaScript uses every day.
ES6 and ES2015 refer to the same language edition. ES6 is the edition number, while ES2015 is the year-based name.
This article describes its most important additions. You can also read the ECMAScript 2015 specification.
Arrow Functions
Arrow functions since their introduction changed how most JavaScript code looks (and works).
Visually, it’s a simple and welcome change, from:
const something = function something() {
//...
}
to
const something = () => {
//...
}
And if the function body is a one-liner, just:
const something = () => doSomething()
Also, if you have a single parameter, you could write:
const something = param => doSomething(param)
This is not a breaking change, regular functions will continue to work just as before.
Lexical this
Arrow functions don’t create their own this value. They inherit this from the surrounding scope.
Regular functions get their this value from how they are called. This makes arrow functions useful for callbacks, but usually a poor choice for object methods.
See the MDN arrow function reference for the other differences.
Promises
Promises (check the full guide to promises) help us avoid deeply nested callbacks. ES2017 later added async and await as a cleaner syntax for consuming them.
Promises have been used by JavaScript developers well before ES2015, with many different libraries implementations (e.g. jQuery, q, deferred.js, vow…), and the standard put a common ground across differences.
By using promises you can rewrite this code
setTimeout(function() {
console.log('I promised to run after 1s')
setTimeout(function() {
console.log('I promised to run after 2s')
}, 1000)
}, 1000)
as
const wait = () => new Promise(resolve => {
setTimeout(resolve, 1000)
})
wait().then(() => {
console.log('I promised to run after 1s')
return wait()
})
.then(() => console.log('I promised to run after 2s'))
Generators
Generators are a special kind of function with the ability to pause itself, and resume later, allowing other code to run in the meantime.
See the full JavaScript Generators Guide for a detailed explanation of the topic.
let and const
var is traditionally function scoped.
let is a new variable declaration which is block scoped.
This means a let variable declared in a loop, an if, or a plain block does not escape that block. A var declaration is available throughout its containing function.
const is block scoped like let, but its binding cannot be reassigned.
The value itself is not necessarily immutable. You can still change the properties of an object stored in a const variable. The MDN const reference explains this distinction.
In JavaScript moving forward, you’ll see little to no var declarations any more, just let and const.
My advice is to use const by default, and use let when you need to reassign the variable.
Classes
JavaScript uses prototype-based inheritance. Developers coming from class-based languages can find this unfamiliar.
ES2015 introduced class syntax built on top of JavaScript’s prototype system.
Now inheritance is very easy and resembles other object-oriented programming languages:
class Person {
constructor(name) {
this.name = name
}
hello() {
return 'Hello, I am ' + this.name + '.'
}
}
class Actor extends Person {
hello() {
return super.hello() + ' I am an actor.'
}
}
const tomCruise = new Actor('Tom Cruise')
tomCruise.hello()
(the above program prints “Hello, I am Tom Cruise. I am an actor.”)
ES2015 classes commonly initialize instance properties in the constructor. Public class fields were added to JavaScript later.
Constructor
Classes have a special method called constructor which is called when a class is initialized via new.
Super
Call super() in a derived constructor to run the parent constructor. Inside a method, use super.methodName() to call a parent method.
Getters and setters
A getter for a property can be declared as
class Person {
get fullName() {
return `${this.firstName} ${this.lastName}`
}
}
Setters are written in the same way:
class Person {
set age(years) {
this.theAge = years
}
}
Modules
Before ES2015, JavaScript used several competing module formats:
- AMD
- CommonJS
RequireJS is a loader that implements AMD, not a separate module format.
ES2015 added a standard module syntax. In browsers, a file must be loaded as a module, for example with <script type="module">.
Importing modules
Importing is done via the import ... from ... construct:
import myDefault from './my-module.js'
import * as myModule from './my-module.js'
import { value, doSomething } from './my-module.js'
import { doSomething as run } from './my-module.js'
See the MDN import reference for every supported form.
Exporting modules
You can write modules and export anything to other modules using the export keyword:
export const number = 2
export function doSomething() {
//...
}
Template Literals
Template literals are a new syntax to create strings:
const aString = `A string`
They provide a way to embed expressions into strings, effectively inserting the values, by using the ${a_variable} syntax:
const joe = 'test'
const string = `something ${joe}` //something test
You can perform more complex expressions as well:
const string = `something ${1 + 2 + 3}`
const string2 = `something ${doSomething() ? 'x' : 'y' }`
and strings can span over multiple lines:
const string3 = `Hey
this
string
is awesome!`
Compare how we used to do multiline strings pre-ES2015:
var str = 'One\n' +
'Two\n' +
'Three'
Default parameters
Functions now support default parameters:
const someFunction = function(index = 0, testing = true) { /* ... */ }
someFunction()
The spread operator
You can expand an iterable, such as an array or string, using the spread operator ....
Let’s start with an array example. Given
const a = [1, 2, 3]
you can create a new array using
const b = [...a, 4, 5, 6]
You can also create a copy of an array using
const c = [...a]
Object spread looks similar, but it was added after ES2015. You can use it to make a shallow copy:
const newObj = { ...oldObj }
Using strings, the spread operator creates an array with each char in the string:
const hey = 'hey'
const arrayized = [...hey] // ['h', 'e', 'y']
This operator has some pretty useful applications. The most important one is the ability to use an array as function argument in a very simple way:
const f = (arg1, arg2) => {}
const a = [1, 2]
f(...a)
(in the past you could do this using f.apply(null, a) but that’s not as nice and readable)
Destructuring assignments
Given an object, you can extract just some values and put them into named variables:
const person = {
firstName: 'Tom',
lastName: 'Cruise',
actor: true,
age: 54, //made up
}
const {firstName: name, age} = person
name and age contain the desired values.
The syntax also works on arrays:
const a = [1,2,3,4,5]
const [first, second] = a
This statement creates 3 new variables by getting the items with index 0, 1, 4 from the array a:
const [first, second, , , fifth] = a
Enhanced object literals
In ES2015 Object Literals gained superpowers.
Simpler syntax to include variables
Instead of doing
const something = 'y'
const x = {
something: something
}
you can do
const something = 'y'
const x = {
something
}
Prototype
A prototype can be specified with
const anObject = { y: 'y' }
const x = {
__proto__: anObject
}
super()
const anObject = { y: 'y', test: () => 'zoo' }
const x = {
__proto__: anObject,
test() {
return super.test() + 'x'
}
}
x.test() //zoox
Dynamic properties
const x = {
['a' + '_' + 'b']: 'z'
}
x.a_b //z
for...of loop
ES5 back in 2009 introduced forEach() loops. While nice, they offered no way to break, like for loops always did.
ES2015 introduced the for-of loop, which combines the conciseness of forEach with the ability to break:
//iterate over the value
for (const v of ['a', 'b', 'c']) {
console.log(v)
}
//get the index as well, using `entries()`
for (const [i, v] of ['a', 'b', 'c'].entries()) {
console.log(i, v)
}
Map and Set
Map and Set (and their respective garbage collected WeakMap and WeakSet) are the official implementations of two very popular data structures.
New String methods
Any string value got some new instance methods:
repeat()repeats the string a specified number of times:'Ho'.repeat(3) //HoHoHocodePointAt()handles retrieving the Unicode code of characters that cannot be represented by a single 16-bit UTF-16 unit, but need 2 instead
New Object methods
ES6 introduced several static methods under the Object namespace:
Object.is()determines if two values are the same valueObject.assign()used to shallow copy an objectObject.setPrototypeOf()sets an object’s prototype
Related posts about js: