ECMAScript

Introduction

Whenever you read about JavaScript you’ll inevitably see one of these terms:

  • ES3
  • ES5
  • ES6
  • ES7
  • ES8
  • ES2015
  • ES2016
  • ES2017
  • ECMAScript 2017
  • ECMAScript 2016
  • ECMAScript 2015

What do they mean?

They are all referring to a standard, called ECMAScript.

ECMAScript is the standard upon which JavaScript is based, and it’s often abbreviated to ES.

Beside JavaScript, other languages implement(ed) ECMAScript, including:

  • ActionScript (the Flash scripting language), which is losing popularity since Flash will be officially discontinued in 2020
  • JScript (the Microsoft scripting dialect), since at the time JavaScript was supported only by Netscape and the browser wars were at their peak, Microsoft had to build its own version for Internet Explorer

but of course JavaScript is the most popular and widely used implementation of ES.

Why this weird name? Ecma International is a Swiss standards association who is in charge of defining international standards.

When JavaScript was created, it was presented by Netscape and Sun Microsystems to Ecma and they gave it the name ECMA-262 alias ECMAScript.

This press release by Netscape and Sun Microsystems (the maker of Java) might help figure out the name choice, which might include legal and branding issues by Microsoft which was in the committee, according to Wikipedia.

After IE9, Microsoft stopped stopped branding its ES support in browsers as JScript and started calling it JavaScript (at least, I could not find references to it any more)

So as of 201x, the only popular language supporting the ECMAScript spec is JavaScript.

Current ECMAScript version

The current ECMAScript version is ES2017, AKA ES8

It was released in June 2017.

When is the next version coming out?

Historically JavaScript editions have been standardized in June, so we can expect ECMAScript 2018 (named ES2018 or ES9) to be released in June 2018, but this is just speculation.

What is TC39

TC39 is the committee that evolves JavaScript.

The members of TC39 are companies involved in JavaScript and browser vendors, including Mozilla, Google, Facebook, Apple, Microsoft, Intel, PayPal, SalesForce and others.

Every standard version proposal must go through various stages, which are explained here.

ES Versions

I found it puzzling why sometimes an ES version is referenced by edition number and sometimes by year, and I am confused by the year by chance being -1 on the number, which adds to the general confusion around JS/ES 😄

Before ES2015, ECMAScript specifications were commonly called by their edition. So ES5 is the official name for the ECMAScript specification update published in 2009.

Why does this happen? During the process that led to ES2015, the name was changed from ES6 to ES2015, but since this was done late, people still referenced it as ES6, and the community has not left the edition naming behind - the world is still calling ES releases by edition number.

This table should clear things a bit:

Edition Official name Date published
ES8 ES2017 June 2017
ES7 ES2016 June 2016
ES6 ES2015 June 2015
ES5.1 ES5.1 June 2011
ES5 ES5 December 2009
ES4 ES4 Abandoned
ES3 ES3 December 1999
ES2 ES2 June 1998
ES1 ES1 June 1997

ES Next

ES.Next is a name that always indicates the next version of JavaScript.

So at the time of writing, ES8 has been released, and ES.Next is ES9

ES2015 aka ES6

ECMAScript 2015, also known as ES6, is a fundamental version of the ECMAScript standard.

Published 4 years after the latest standard revision, ECMAScript 5.1, it also marked the switch from edition number to year number.

So it should not be named as ES6 (although everyone calls it as such) but ES2015 instead.

ES5 was 10 years in the making, from 1999 to 2009, and as such it was also a fundamental and very important revision of the language, but now much time has passed that it’s not worth discussing how pre-ES5 code worked.

Since this long time passed between ES5.1 and ES6, the release is full of important new features and major changes in suggested best practices in developing JavaScript programs. To understand how fundamental ES2015 is, just keep in mind that with this version, the specification document went from 250 pages to ~600.

The most important changes in ES2015 include

Each of them has a dedicated section in this article.

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 foo = function foo() {
  //...
}

to

const foo = () => {
  //...
}

And if the function body is a one-liner, just:

const foo = () => doSomething()

Also, if you have a single parameter, you could write:

const foo = param => doSomething(param)

This is not a breaking change, regular functions will continue to work just as before.

A new this scope

The this scope with arrow functions is inherited from the context.

With regular functions this always refers to the nearest function, while with arrow functions this problem is removed, and you won’t need to write var that = this ever again.

Promises

Promises (check the full guide to promises) allow us to eliminate the famous “callback hell”, although they introduce a bit more complexity (which has been solved in ES2017 with async, a higher level construct).

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, reject) => {
  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.

The code decides that it has to wait, so it lets other code “in the queue” to run, and keeps the right to resume its operations “when the thing it’s waiting for” is done.

All this is done with a single, simple keyword: yield. When a generator contains that keyword, the execution is halted.

A generator can contain many yield keywords, thus halting itself multiple times, and it’s identified by the *function keyword, which is not to be confused with the pointer dereference operator used in lower level programming languages such as C, C++ or Go.

Generators enable whole new paradigms of programming in JavaScript, allowing:

  • 2-way communication while a generator is running
  • long-lived while loops which do not freeze your program

Here is an example of a generator which explains how it all works.

function *calculator(input) {
    var doubleThat = 2 * (yield (input / 2))
    var another = yield (doubleThat)
    return (input * doubleThat * another)
}

We initialize it with

const calc = calculator(10)

Then we start the iterator on our generator:

calc.next()

This first iteration starts the iterator. The code returns this object:

{
  done: false
  value: 5
}

What happens is: the code runs the function, with input = 10 as it was passed in the generator constructor. It runs until it reaches the yield, and returns the content of yield: input / 2 = 5. So we got a value of 5, and the indication that the iteration is not done (the function is just paused).

In the second iteration we pass the value 7:

calc.next(7)

and what we got back is:

{
  done: false
  value: 14
}

7 was placed as the value of doubleThat. Important: you might read like input / 2 was the argument, but that’s just the return value of the first iteration. We now skip that, and use the new input value, 7, and multiply it by 2.

We then reach the second yield, and that returns doubleThat, so the returned value is 14.

In the next, and last, iteration, we pass in 100

calc.next(100)

and in return we got

{
  done: true
  value: 14000
}

As the iteration is done (no more yield keywords found) and we just return (input * doubleThat * another) which amounts to 10 * 14 * 100.

let and const

var is traditionally function scoped.

let is a new variable declaration which is block scoped.

This means that declaring let variables in a for loop, inside an if or in a plain block is not going to let that variable “escape” the block, while vars are hoisted up to the function definition.

const is just like let, but immutable.

In JavaScript moving forward, you’ll see little to no var declarations any more, just let and const.

const in particular, maybe surprisingly, is very widely used nowadays with immutability being very popular.

Classes

Traditionally JavaScript is the only mainstream language with prototype-based inheritance. Programmers switching to JS from class-based language found it puzzling, but ES2015 introduced classes, which are just syntactic sugar over the inner working, but changed a lot how we build JavaScript programs.

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.'
  }
}

var tomCruise = new Actor('Tom Cruise')
tomCruise.hello()

(the above program prints “Hello, I am Tom Cruise. I am an actor.”)

Classes do not have explicit class variable declarations, but you must initialize any variable in the constructor.

Constructor

Classes have a special method called constructor which is called when a class is initialized via new.

Super

The parent class can be referenced using super().

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, there were at least 3 major modules competing standards, which fragmented the community:

  • AMD
  • RequireJS
  • CommonJS

ES2015 standardized these into a common format.

Importing modules

Importing is done via the import ... from ... construct:

import * from 'mymodule'
import React from 'react'
import { React, Component } from 'react'
import React as MyLibrary from 'react'

Exporting modules

You can write modules and export anything to other modules using the export keyword:

export var foo = 2
export function bar() { /* ... */ }

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 interpolating the values, by using the ${a_variable} syntax:

const var = 'test'
const string = `something ${var}` //something test

You can perform more complex expressions as well:

const string = `something ${1 + 2 + 3}`
const string2 = `something ${foo() ? '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'

See this post for an in-depth guide on template literals

Default parameters

Functions now support default parameters:

const foo = function(index = 0, testing = true) { /* ... */ }
foo()

The spread operator

You can expand an array, an object or a 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]

This works for objects as well. Clone an object with:

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 = (foo, bar) => {}
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]
[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.

ES2016 aka ES7

ES7, officially known as ECMAScript 2016, was finalized in June 2016.

Compared to ES6, ES7 is a tiny release for JavaScript, containing just two features:

  • Array.prototype.includes
  • Exponentiation Operator

Array.prototype.includes()

This feature introduces a more readable syntax for checking if an array contains an element.

With ES6 and lower, to check if an array contained an element you had to use indexOf, which checks the index in the array, and returns -1 if the element is not there.

Since -1 is evaluated as a true value, you could not do for example

if (![1,2].indexOf(3)) {
  console.log('Not found')
}

With this feature introduced in ES7 we can do

if (![1,2].includes(3)) {
  console.log('Not found')
}

Exponentiation Operator

The exponentiation operator ** is the equivalent of Math.pow(), but brought into the language instead of being a library function.

Math.pow(4, 2) == 4 ** 2

This feature is a nice addition for math intensive JS applications.

The ** operator is standardized across many languages including Python, Ruby, MATLAB, Lua, Perl and many others.

ES2017 aka ES8

ECMAScript 2017, edition 8 of the ECMA-262 Standard (also commonly called ES2017 or ES8), was finalized in June 2017.

Compared to ES6, ES8 is a tiny release for JavaScript, but still it introduces very useful features:

  • String padding
  • Object.values
  • Object.entries
  • Object.getOwnPropertyDescriptors()
  • Trailing commas in function parameter lists and calls
  • Async functions
  • Shared memory and atomics

String padding

The purpose of string padding is to add characters to a string, so it reaches a specific length.

ES2017 introduces two String methods: padStart() and padEnd().

padStart(targetLength [, padString])
padEnd(targetLength [, padString])

Sample usage:

padStart()
‘test’.padStart(4) ‘test’
‘test’.padStart(5) ’ test’
‘test’.padStart(8) ’ test’
‘test’.padStart(8, ‘abcd’) ‘abcdtest’
padEnd()
‘test’.padEnd(4) ‘test’
‘test’.padEnd(5) ‘test ‘
‘test’.padEnd(8) ‘test ‘
‘test’.padEnd(8, ‘abcd’) ‘testabcd’

Object.values()

This method returns an array containing all the object own property values.

Usage:

const person = { name: 'Fred', age: 87 }
Object.values(person) // ['Fred', 87]

Object.values() also works with arrays:

const people = ['Fred', 'Tony']
Object.values(people) // ['Fred', 'Tony']

Object.entries()

This method returns an array containing all the object own properties, as an array of [key, value] pairs.

Usage:

const person = { name: 'Fred', age: 87 }
Object.entries(person) // [['name', 'Fred'], ['age', 87]]

Object.entries() also works with arrays:

const people = ['Fred', 'Tony']
Object.entries(people) // [['0', 'Fred'], ['1', 'Tony']]

getOwnPropertyDescriptors()

This method returns all own (non-inherited) properties descriptors of an object.

Any object in JavaScript has a set of properties, and each of these properties has a descriptor.

A descriptor is a set of attributes of a property, and it’s composed by a subset of the following:

  • value: the value of the property
  • writable: true the property can be changed
  • get: a getter function for the property, called when the property is read
  • set: a setter function for the property, called when the property is set to a value
  • configurable: if false, the property cannot be removed nor any attribute can be changed, except its value
  • enumerable: true if the property is enumerable

Object.getOwnPropertyDescriptors(obj) accepts and object, and returns an object with the set of descriptors.

In what way is this useful?

ES2015 gave us Object.assign(), which copies all enumerable own properties from one or more objects, and return a new object.

However there is a problem with that, because it does not correctly copies properties with non-default attributes.

If an object for example has just a setter, it’s not correctly copied to a new object, using Object.assign().

For example with

const person1 = {
    set name(newName) {
        console.log(newName)
    }
}

This won’t work:

const person2 = {}
Object.assign(person2, person1)

But this will work:

const person3 = {}
Object.defineProperties(person3,
  Object.getOwnPropertyDescriptors(person1))

As you can see with a simple console test:

person1.name = 'x'
"x"

person2.name = 'x'

person3.name = 'x'
"x"

person2 misses the setter, it was not copied over.

The same limitation goes for shallow cloning objects with Object.create().

Trailing commas

This feature allows to have trailing commas in function declarations, and in functions calls:

const doSomething = (var1, var2,) => {
  //...
}

doSomething('test2', 'test2',)

This change will encourage developers to stop the ugly “comma at the start of the line” habit.

Async functions

Check the dedicated post about async/await

ES2017 introduced the concept of async functions, and it’s the most important change introduced in this ECMAScript edition.

Async functions are a combination of promises and generators to reduce the boilerplate around promises, and the “don’t break the chain” limitation of chaining promises.

Why they are useful

It’s a higher level abstraction over promises.

When Promises were introduced in ES2015, they were meant to solve a problem with asynchronous code, and they did, but over the 2 years that separated ES2015 and ES2017, it was clear that promises could not be the final solution. Promises were introduced to solve the famous callback hell problem, but they introduced complexity on their own, and syntax complexity. They were good primitives around which a better syntax could be exposed to the developers: enter async functions.

A quick example

Code making use of asynchronous functions can be written as

function doSomethingAsync() {
    return new Promise((resolve) => {
        setTimeout(() => resolve('I did something'), 3000)
    })
}

async function doSomething() {
    console.log(await doSomethingAsync())
}

console.log('Before')
doSomething()
console.log('After')

The above code will print the following to the browser console:

Before
After
I did something //after 3s

Multiple async functions in series

Async functions can be chained very easily, and the syntax is much more readable than with plain promises:

function promiseToDoSomething() {
    return new Promise((resolve)=>{
        setTimeout(() => resolve('I did something'), 10000)
    })
}

async function watchOverSomeoneDoingSomething() {
    const something = await promiseToDoSomething()
    return something + ' and I watched'
}

async function watchOverSomeoneWatchingSomeoneDoingSomething() {
    const something = await watchOverSomeoneDoingSomething()
    return something + ' and I watched as well'
}

watchOverSomeoneWatchingSomeoneDoingSomething().then((res) => {
    console.log(res)
})

Shared Memory and Atomics

WebWorkers are used to create multithreaded programs in the browser.

They offer a messaging protocol via events. Since ES2017, you can create a shared memory array between web workers and their creator, using a SharedArrayBuffer.

Since it’s unknown how much time writing to a shared memory portion takes to propagate, Atomics are a way to enforce that when reading a value, any kind of writing operation is completed.

Any more detail on this can be found in the spec proposal, which has since been implemented.