ES5 to ESNext — here’s every feature added to JavaScript since 2015

Flavio Copes
Feb 13 · 40 min read

Table of Contents

Introduction to ECMAScript

Current ECMAScript version

What is TC39

ES Versions

let and const

var a = 0
var a //typeof a === 'undefined'
var a = 1
var a = 2
var a = 1, b = 2

Using let

Using const

const a = 'test'

Arrow Functions

const myFunction = function() {
//...
}
const myFunction = () => {
//...
}
const myFunction = () => doSomething()
const myFunction = (param1, param2) => doSomething(param1, param2)
const myFunction = param => doSomething(param)

Implicit return

const myFunction = () => 'test'myFunction() //'test'
const myFunction = () => ({ value: 'test' })myFunction() //{value: 'test'}

How this works in arrow functions

const car = {
model: 'Fiesta',
manufacturer: 'Ford',
fullName: function() {
return `${this.manufacturer} ${this.model}`
}
}
const car = {
model: 'Fiesta',
manufacturer: 'Ford',
fullName: () => {
return `${this.manufacturer} ${this.model}`
}
}
const link = document.querySelector('#link')
link.addEventListener('click', () => {
// this === window
})
const link = document.querySelector('#link')
link.addEventListener('click', function() {
// this === link
})

Classes

A class definition

class Person {
constructor(name) {
this.name = name
}
hello() {
return 'Hello, I am ' + this.name + '.'
}
}
const flavio = new Person('Flavio')
flavio.hello()

Class inheritance

class Programmer extends Person {
hello() {
return super.hello() + ' I am a programmer.'
}
}
const flavio = new Programmer('Flavio')
flavio.hello()

Static methods

class Person {
static genericHello() {
return 'Hello'
}
}
Person.genericHello() //Hello

Private methods

Getters and setters

class Person {
constructor(name) {
this._name = name
}
set name(value) {
this._name = value
}
get name() {
return this._name
}
}
class Person {
constructor(name) {
this._name = name
}
get name() {
return this._name
}
}
class Person {
constructor(name) {
this._name = name
}
set name(value) {
this._name = value
}
}

Default parameters

const doSomething = (param1) => {}
const doSomething = (param1 = 'test') => {}
const doSomething = (param1 = 'test', param2 = 'test2') => {}
const colorize = (options) => {
if (!options) {
options = {}
}
const color = ('color' in options) ? options.color : 'yellow'
...
}
const colorize = ({ color = 'yellow' }) => {
...
}
const spin = ({ color = 'yellow' } = {}) => {
...
}

Template Literals

const a_string = `something`

Multiline strings

const string =
'first part \
second part'
const string =
'first line\n \
second line'
const string = 'first line\n' + 'second line'
const string = `Hey
this
string
is awesome!`
const string = `First
Second`
First
Second
const string = `
First
Second`.trim()

Interpolation

const var = 'test'
const string = `something ${var}` //something test
const string = `something ${1 + 2 + 3}`
const string2 = `something ${foo() ? 'x' : 'y'}`

Template tags

const Button = styled.button`
font-size: 1.5em;
background-color: black;
color: white;
`
const query = gql`
query {
...
}
`
function gql(literals, ...expressions) {}
const string = `something ${1 + 2 + 3}`
const string = `something
another ${'x'}
new line ${1 + 2 + 3}
test`
;`something
another `
;`
new line `
;`
test`
const interpolated = interpolate`I paid ${10}€`
function interpolate(literals, ...expressions) {
let string = ``
for (const [i, val] of expressions) {
string += literals[i] + val
}
string += literals[literals.length - 1]
return string
}

Destructuring assignments

const person = {
firstName: 'Tom',
lastName: 'Cruise',
actor: true,
age: 54, //made up
}
const {firstName: name, age} = person
const a = [1,2,3,4,5]
const [first, second] = a
const [first, second, , , fifth] = a

Enhanced Object Literals

Simpler syntax to include variables

const something = 'y'
const x = {
something: something
}
const something = 'y'
const x = {
something
}

Prototype

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

//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(index) //index
console.log(value) //value
}

Promises

How promises work, in brief

Which JS API use promises?

Creating a promise

let done = trueconst isItDoneYet = new Promise((resolve, reject) => {
if (done) {
const workDone = 'Here is the thing I built'
resolve(workDone)
} else {
const why = 'Still working on something else'
reject(why)
}
})

Consuming a promise

const isItDoneYet = new Promise()
//...
const checkIfItsDone = () => {
isItDoneYet
.then(ok => {
console.log(ok)
})
.catch(err => {
console.error(err)
})
}

Chaining promises

Example of chaining promises

const status = response => {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
}
return Promise.reject(new Error(response.statusText))
}
const json = response => response.json()fetch('/todos.json')
.then(status)
.then(json)
.then(data => {
console.log('Request succeeded with JSON response', data)
})
.catch(error => {
console.log('Request failed', error)
})
.then((data) => {
console.log('Request succeeded with JSON response', data)
})

Handling errors

new Promise((resolve, reject) => {
throw new Error('Error')
}).catch(err => {
console.error(err)
})
// ornew Promise((resolve, reject) => {
reject('Error')
}).catch(err => {
console.error(err)
})

Cascading errors

new Promise((resolve, reject) => {
throw new Error('Error')
})
.catch(err => {
throw new Error('Error')
})
.catch(err => {
console.error(err)
})

Orchestrating promises

Promise.all()

const f1 = fetch('/something.json')
const f2 = fetch('/something2.json')
Promise.all([f1, f2])
.then(res => {
console.log('Array of results', res)
})
.catch(err => {
console.error(err)
})
Promise.all([f1, f2]).then(([res1, res2]) => {
console.log('Results', res1, res2)
})

Promise.race()

const promiseOne = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one')
})
const promiseTwo = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two')
})
Promise.race([promiseOne, promiseTwo]).then(result => {
console.log(result) // 'two'
})

Modules

The ES Modules Syntax

import package from 'module-name'
const package = require('module-name')
export default str => str.toUpperCase()
<script type="module" src="index.js"></script>
import toUpperCase from './uppercase.js'
toUpperCase('test') //'TEST'
import toUpperCase from 'https://flavio-es-modules-example.glitch.me/uppercase.js'
import { toUpperCase } from '/uppercase.js'
import { toUpperCase } from '../uppercase.js'
import { toUpperCase } from 'uppercase.js'
import { toUpperCase } from 'utils/uppercase.js'

Other import/export options

export default str => str.toUpperCase()
const a = 1
const b = 2
const c = 3
export { a, b, c }
import * from 'module'
import { a } from 'module'
import { a, b } from 'module'
import { a, b as two } from 'module'
import React, { Component } from 'react'

CORS

What about browsers that do not support modules?

<script type="module" src="module.js"></script>
<script nomodule src="fallback.js"></script>

Wrapping up modules

New String methods

repeat()

'Ho'.repeat(3) //'HoHoHo'

codePointAt()

"𠮷".charCodeAt(0).toString(16) //d842
"𠮷".charCodeAt(1).toString(16) //dfb7
"\ud842\udfb7" //"𠮷"
"𠮷".codePointAt(0) //20bb7
"\u{20bb7}" //"𠮷"

New Object methods

Object.is()

Object.is(a, b)

Object.assign()

const copied = Object.assign({}, original)
const original = {
name: 'Fiesta',
car: {
color: 'blue'
}
}
const copied = Object.assign({}, original)
original.name = 'Focus'
original.car.color = 'yellow'
copied.name //Fiesta
copied.car.color //yellow
const wisePerson = {
isWise: true
}
const foolishPerson = {
isFoolish: true
}
const wiseAndFoolishPerson = Object.assign({}, wisePerson, foolishPerson)
console.log(wiseAndFoolishPerson) //{ isWise: true, isFoolish: true }

Object.setPrototypeOf()

Object.setPrototypeOf(object, prototype)
const animal = {
isAnimal: true
}
const mammal = {
isMammal: true
}
mammal.__proto__ = animal
mammal.isAnimal //true
const dog = Object.create(animal)dog.isAnimal //true
console.log(dog.isMammal) //undefined
Object.setPrototypeOf(dog, mammal)dog.isAnimal //true
dog.isMammal //true

The spread operator

const a = [1, 2, 3]
const b = [...a, 4, 5, 6]
const c = [...a]
const newObj = { ...oldObj }
const hey = 'hey'
const arrayized = [...hey] // ['h', 'e', 'y']
const f = (foo, bar) => {}
const a = [1, 2]
f(...a)
const numbers = [1, 2, 3, 4, 5]
[first, second, ...others] = numbers
const numbers = [1, 2, 3, 4, 5]
const sum = (a, b, c, d, e) => a + b + c + d + e
const sum = sum(...numbers)
const { first, second, ...others } = {
first: 1,
second: 2,
third: 3,
fourth: 4,
fifth: 5
}
first // 1
second // 2
others // { third: 3, fourth: 4, fifth: 5 }
const items = { first, second, ...others }
items //{ first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }

Set

Initialize a Set

const s = new Set()

Add items to a Set

s.add('one')
s.add('two')

Check if an item is in the set

s.has('one') //true
s.has('three') //false

Delete an item from a Set by key

s.delete('one')

Determine the number of items in a Set

s.size

Delete all items from a Set

s.clear()

Iterate the items in a Set

for (const k of s.keys()) {
console.log(k)
}
for (const k of s.values()) {
console.log(k)
}
const i = s.entries()
console.log(i.next())
s.forEach(v => console.log(v))
for (const k of s) {
console.log(k)
}

Initialize a Set with values

const s = new Set([1, 2, 3, 4])

Convert the Set keys into an array

const a = [...s.keys()]// orconst a = [...s.values()]

A WeakSet

Map

Before ES6

const car = {}
car['color'] = 'red'
car.owner = 'Flavio'
console.log(car['color']) //red
console.log(car.color) //red
console.log(car.owner) //Flavio
console.log(car['owner']) //Flavio

Enter Map

const m = new Map()

Add items to a Map

m.set('color', 'red')
m.set('age', 2)

Get an item from a map by key

const color = m.get('color')
const age = m.get('age')

Delete an item from a map by key

m.delete('color')

Delete all items from a map

m.clear()

Check if a map contains an item by key

const hasColor = m.has('color')

Find the number of items in a map

const size = m.size

Initialize a map with values

const m = new Map([['color', 'red'], ['owner', 'Flavio'], ['age', 2]])

Map keys

Weird situations you’ll almost never find in real life

const m = new Map()
m.set(NaN, 'test')
m.get(NaN) //test
const m = new Map()
m.set(+0, 'test')
m.get(-0) //test

Iterate over map keys

for (const k of m.keys()) {
console.log(k)
}

Iterate over map values

for (const v of m.values()) {
console.log(v)
}

Iterate over map key, value pairs

for (const [k, v] of m.entries()) {
console.log(k, v)
}
for (const [k, v] of m) {
console.log(k, v)
}

Convert the map keys into an array

const a = [...m.keys()]

Convert the map values into an array

const a = [...m.values()]

WeakMap

Generators

function *calculator(input) {
var doubleThat = 2 * (yield (input / 2))
var another = yield (doubleThat)
return (input * doubleThat * another)
}
const calc = calculator(10)
calc.next()
{
done: false
value: 5
}
calc.next(7)
{
done: false
value: 14
}
calc.next(100)
{
done: true
value: 14000
}


Array.prototype.includes()

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

Exponentiation Operator

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


String padding

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

Object.values()

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

Object.entries()

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

Object.getOwnPropertyDescriptors()

In what way is this useful?

const person1 = {
set name(newName) {
console.log(newName)
}
}
const person2 = {}
Object.assign(person2, person1)
const person3 = {}
Object.defineProperties(person3,
Object.getOwnPropertyDescriptors(person1))
person1.name = 'x'
"x"
person2.name = 'x'person3.name = 'x'
"x"

Trailing commas

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

Async functions

Why were async/await introduced?

How it works

const doSomethingAsync = () => {
return new Promise(resolve => {
setTimeout(() => resolve('I did something'), 3000)
})
}
const doSomething = async () => {
console.log(await doSomethingAsync())
}

A quick example

const doSomethingAsync = () => {
return new Promise(resolve => {
setTimeout(() => resolve('I did something'), 3000)
})
}
const doSomething = async () => {
console.log(await doSomethingAsync())
}
console.log('Before')
doSomething()
console.log('After')
Before
After
I did something //after 3s

Promise all the things

const aFunction = async () => {
return 'test'
}
aFunction().then(alert) // This will alert 'test'
const aFunction = async () => {
return Promise.resolve('test')
}
aFunction().then(alert) // This will alert 'test'

The code is much simpler to read

const getFirstUserData = () => {
return fetch('/users.json') // get users list
.then(response => response.json()) // parse JSON
.then(users => users[0]) // pick first user
.then(user => fetch(`/users/${user.name}`)) // get user data
.then(userResponse => response.json()) // parse JSON
}
getFirstUserData()
const getFirstUserData = async () => {
const response = await fetch('/users.json') // get users list
const users = await response.json() // parse JSON
const user = users[0] // pick first user
const userResponse = await fetch(`/users/${user.name}`) // get user data
const userData = await user.json() // parse JSON
return userData
}
getFirstUserData()

Multiple async functions in series

const promiseToDoSomething = () => {
return new Promise(resolve => {
setTimeout(() => resolve('I did something'), 10000)
})
}
const watchOverSomeoneDoingSomething = async () => {
const something = await promiseToDoSomething()
return something + ' and I watched'
}
const watchOverSomeoneWatchingSomeoneDoingSomething = async () => {
const something = await watchOverSomeoneDoingSomething()
return something + ' and I watched as well'
}
watchOverSomeoneWatchingSomeoneDoingSomething().then(res => {
console.log(res)
})
I did something and I watched and I watched as well

Easier debugging

Shared Memory and Atomics



Rest/Spread Properties

const numbers = [1, 2, 3, 4, 5]
[first, second, ...others] = numbers
const numbers = [1, 2, 3, 4, 5]
const sum = (a, b, c, d, e) => a + b + c + d + e
const sum = sum(...numbers)
const { first, second, ...others } = { first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }first // 1
second // 2
others // { third: 3, fourth: 4, fifth: 5 }
const items = { first, second, ...others }
items //{ first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }

Asynchronous iteration

for await (const line of readLines(filePath)) {
console.log(line)
}

Promise.prototype.finally()

fetch('file.json')
.then(data => data.json())
.catch(error => console.error(error))
.finally(() => console.log('finished'))

Regular Expression improvements

RegExp lookbehind assertions: match a string depending on what precedes it

/Roger(?=Waters)//Roger(?= Waters)/.test('Roger is my dog') //false
/Roger(?= Waters)/.test('Roger is my dog and Roger Waters is a famous musician') //true
/Roger(?!Waters)//Roger(?! Waters)/.test('Roger is my dog') //true
/Roger(?! Waters)/.test('Roger Waters is a famous musician') //false
/(?<=Roger) Waters//(?<=Roger) Waters/.test('Pink Waters is my dog') //false
/(?<=Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //true
/(?<!Roger) Waters//(?<!Roger) Waters/.test('Pink Waters is my dog') //true
/(?<!Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //false

Unicode property escapes \p{…} and \P{…}

/^\p{ASCII}+$/u.test('abc')   //✅
/^\p{ASCII}+$/u.test('ABC@') //✅
/^\p{ASCII}+$/u.test('ABC🙃') //❌
/^\p{ASCII_Hex_Digit}+$/u.test('0123456789ABCDEF') //✅
/^\p{ASCII_Hex_Digit}+$/u.test('h') //❌
/^\p{Lowercase}$/u.test('h') //✅
/^\p{Uppercase}$/u.test('H') //✅
/^\p{Emoji}+$/u.test('H') //❌
/^\p{Emoji}+$/u.test('🙃🙃') //✅
/^\p{Script=Greek}+$/u.test('ελληνικά') //✅
/^\p{Script=Latin}+$/u.test('hey') //✅

Named capturing groups

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const result = re.exec('2015-01-02')
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';

The s flag for regular expressions

/hi.welcome/.test('hi\nwelcome') // false
/hi.welcome/s.test('hi\nwelcome') // true

ESNext

Array.prototype.{flat,flatMap}

['Dog', ['Sheep', 'Wolf']].flat()
//[ 'Dog', 'Sheep', 'Wolf' ]
['Dog', ['Sheep', ['Wolf']]].flat()
//[ 'Dog', 'Sheep', [ 'Wolf' ] ]
['Dog', ['Sheep', ['Wolf']]].flat(2)
//[ 'Dog', 'Sheep', 'Wolf' ]
['Dog', ['Sheep', ['Wolf']]].flat(Infinity)
//[ 'Dog', 'Sheep', 'Wolf' ]
['My dog', 'is awesome'].map(words => words.split(' '))
//[ [ 'My', 'dog' ], [ 'is', 'awesome' ] ]
['My dog', 'is awesome'].flatMap(words => words.split(' '))
//[ 'My', 'dog', 'is', 'awesome' ]

Optional catch binding

try {
//...
} catch (e) {
//handle error
}
try {
//...
} catch {
//handle error
}

Object.fromEntries()

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

person !== newPerson //true

String.prototype.{trimStart,trimEnd}

trimStart()

'Testing'.trimStart() //'Testing'
' Testing'.trimStart() //'Testing'
' Testing '.trimStart() //'Testing '
'Testing'.trimStart() //'Testing'

trimEnd()

'Testing'.trimEnd() //'Testing'
' Testing'.trimEnd() //' Testing'
' Testing '.trimEnd() //' Testing'
'Testing '.trimEnd() //'Testing'

Symbol.prototype.description

const testSymbol = Symbol('Test')
testSymbol.description // 'Test'

JSON improvements

Well-formed JSON.stringify()

Function.prototype.toString()

function /* this is bar */ bar () {}
bar.toString() //'function bar() {}
bar.toString(); // 'function /* this is bar */ bar () {}'

freeCodeCamp.org

This is no longer updated. Go to https://freecodecamp.org/news instead

Flavio Copes

Written by

I write tutorials for developers at https://flaviocopes.com

freeCodeCamp.org

This is no longer updated. Go to https://freecodecamp.org/news instead