JavaScript ES6/ES7/ES8/ES9: A crash course on modern JS

Sara
12 min readApr 24, 2022

--

Summary - updated 2022

JavaScript was invented on 1997 and became an ECMA standard. ECMAScript is the official language name. ECMAScript versions include ES1, ES2, ES3, ES5, and ES6, ES7, ES8, ES9.

Babel

One of the biggest changes from ES5 is that ES6 JavaScript is not able to be compiled directly in browsers. We need to use a transpiler called Babel.js to produce compatible JavaScript that the older browsers can read.

Babel allows you to use ES6 features and syntax in your project and then translates it to ES5 so that you can use it in production.

Read: How to: use Babel once your project is built.

Here we will introduce from the latest to the oldest the most important concepts in Javascript:

The most important ES2019

The most important ES2018 Updates

The most important ES2017 Updates

The most important ES2016 updates

The most important ES2019 updates

ES9 — Array.prototype.flat

This feature essentially flattens an array recursively up to a pre-specified depth. The flat() method makes a new array containing all sub-array elements. Infinity is used to flatten nested arrays.

const letters = [‘a’, ‘b’, [‘c’, ‘d’, [‘e’, ‘f’]]];
// default depth of 1
console.log(letters.flat());
// [‘a’, ‘b’, ‘c’, ‘d’, [‘e’, ‘f’]]

// depth of 2
console.log(letters.flat(2));
// [‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’]

// which is the same as executing flat with depth of 1 twice
console.log(letters.flat().flat());
// [‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’]

// Flattens recursively until the array contains no nested arrays
console.log(letters.flat(Infinity));
// [‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’]

ES9 — Symbol.prototype.description

The method .description allows you to return an optional description of a Symbol object.
Symbol objects can have an optional description used for debugging purposes, and this new update enables you to read that description but does not contain the enclosing Symbol ( ) string.

const me = Symbol(“Sarah”);
console.log(me.description);
// “Sarah”

console.log(me.toString());
// “Symbol(Sarah)”

ES9 — Changes to Object.fromEntries

This method transforms your list of key-value pairs into objects, and we can pass any iterable as an argument of Object.fromEntries.

const keyValueArray = [
[‘key1’, ‘value1’],
[‘key2’, ‘value2’]
]

const obj = Object.fromEntries(keyValueArray);
console.log(obj);
// {key1: “value1”, key2: “value2”}

ES9 — Other ES9 Features

  • Lifting template literals restriction
  • RegExp features
  • Promise.prototype.finally ( )

ES9 — Other updates include

  • String.prototype.trimStart( )/ trimEnd( )
  • Changes to Array.sort
  • Function.prototype.toString( )
  • Optional Catch Binding

ES8 — Rest / Spread for Objects

This feature builds off of updates from ES6 so we can use rest/spread syntax for objects. The spread operator enables us to create a clone of an Object so we can modify the original more easily.

This feature should not be used at the end, or it will result in an error.

let myObj = {
a:1,
b:3,
c:5,
d:8,
}

// we use the rest operator to grab everything else left in the object.
let { a, b, …z } = myObj;
console.log(a); // 1
console.log(b); // 3
console.log(z); // {c: 5, d: 8}

// using the spread syntax we cloned our Object
let clone = { …myObj };
console.log(clone);
// {a: 1, b: 3, c: 5, d: 8}
myObj.e = 15;
console.log(clone)
// {a: 1, b: 3, c: 5, d: 8}
console.log(myObj)
// {a: 1, b: 3, c: 5, d: 8, e: 15}

ES8 — Asynchronous Iteration

This update enables you to use await to declare asynchronous loops if the data comes from an asynchronous source. We use the for-await-of to convert the iterables to a Promise.

The GitHub documentation explains that “an async iterator is much like an iterator, except that its next() method returns a promise for a { value, done } pair.”

const iterables = [1,2,3];

async function test() {
for await (const value of iterables) {
console.log(value);
}
}
test();
// 1
// 2
// 3

ES8 — Other changes from ES8:

  • String Padding
  • Shared memory and atomics
  • Object.getOwnPropertyDescriptors( )
  • Trailing commas in function parameter lists and calls

The Most Important ES2017 Updates

ES7 — Object.entires( ) and Object.values( )

Object.values( ) enables us to return an array of all the values of our Object, and Object.entries( ) returns an array that contains both keys and values.

These are two new ways of accessing our objects, resolving some of the limitations of Object.keys( ), which return only the keys of the object.

const family = { father: “John Smith”,
mother: “Martha Smith”,
daughter: “Sarah Kent”,
}
console.log(Object.values(family));
console.log(Object.entries(family));
// [“father”, “John Smith”]
// [“mother”, “Martha Smith”]
// [“daughter”, “Sarah Smith”]

ES7 — Async and Await

The .await( ) operator waits for a Promise inside the async function. Take a look at the new way of writing this code.

This ES8 update offers an alternative to callbacks and Promise and uses much more manageable syntax. The Async function allows us to define an asynchronous function and return a Promise.

function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve(‘resolved’);
}, 2000);
});
}

async function asyncCall() {
console.log(‘calling’);
const result = await resolveAfter2Seconds();
console.log(result);
}

asyncCall();

  • We make an async function with the async keyword
  • This will return a Promise
  • If we specify to return non-promise, it returns a value wrapped inside a Promise
  • The await keyword only works inside an async function

ES7 — latest update: async/await

Async/await helps with writing completely synchronous-looking code while performing asynchronous tasks.

await is used to wait for a promise to resolve or reject, and can only be used inside an asynchronous function.

// The menu
var menu = [‘Hamburger’, ‘Chicken Soup’, ‘Pasta’];

// The following function returns a promise that resolves if the customer
// orders something from the menu:
function order_food (order) {
let promise = new Promise((resolve, reject) => {
if(menu.includes(order)) {
resolve();
}else {
reject(‘This item is not on the menu.’);
}
});
return promise;
}

// The following function returns a promise that resolves if the customer
// pays 20 or more for the food:
function make_payment (payment) {
let promise = new Promise((resolve, reject) => {
if(payment >= 20) {
resolve();
}else {
reject(‘Your order costs 20.’);
}
});
return promise;
}

// await can only be used inside an async function
async function eat(order, payment){
// waiting for the promises to resolve
try{
await order_food(order);
console.log(“Order received by the customer.”);
console.log(“Collect payment.”);
await make_payment(payment);
console.log(“Payment received.”);
}
// Catching errors or rejected promises
catch (error){
console.log(error)
}
}

// Customer places his/her order and specifies the amount to pay
// Play around with these parameters to fully understand what is going on.
eat(“Hamburger”, 20);

The most important ES2016 updates

ES6 — Array.prototype.includes( )

The .includes( ) method makes it easier for you to check if particular values are stored in an array. In the past, JavaScript developers had to use indexOf and create a new function. But .include( ) will return true if an array includes an element and false if it does not. Take a look below to see it in action.

let array = [1,3,5,9];
console.log(array.includes(4));
// true
console.log(array.includes(5));
// false

ES6 — The exponential operator

The exponential operator simplifies the way we do math in JavaScript.

In the past, we had to use loop, recursive functions, or Math.pow( ), which could get pretty messy with each concatenation.

console.log(2**2);
console.log(2**3);

ES6 — latest update: .map

Before ES6, JavaScript developers used objects to map keys to values, but using an object as a map has some restrictions:

  • There’s no dependable way to iterate over keys
  • The keys() method converts fields to strings, which creates key collision
  • There’s no simple way to add new keys and new values

These issues were addressed in the ES6 release when the Map collection type was introduced. It can hold key-value pairs of any type and can remember key insertion order. Any value (objects and primitives) can be used as a key or a value.

JavaScript Map creates a new array, which contains the results from the iteration over the elements of the array and calls the provided function once for each element in order.

To create a new Map, follow this syntax:

let map = new Map([iterable]);
JavaScript map syntax

Description:
· array: the array on which the specific function is called
· map: the method called for the array with the required parameters
· function(currentValue, index, arr): the function with its parameters, which is required to run for each element in the array
· currentValue: the value of the current element
· index:
the index of the current element
· arr: the array object on which the map() is called
· thisValue: the value used as the function’s this value when it’s executed

//creating an array
let an_array = [5, 3, 1, 4, 2]
//map calls a function with “item” as parameter
//map will pass each element of an_array as “item” in this function
//the function will triple each element passed and return it as the return value
result = an_array.map(function(item) {
return item*3;
});
//new list will print with the tripled values
console.log(result);

Return a value mapped to a specific key

userRoles.get(robin); // actress

Return a boolean value showing whether a specified key exists

userRoles.has(emma); // false
userRoles.has(ruby); // true

Return the number of entries in the map

console.log(userRoles.size); // 4

Return a new iterator object that includes the value of each element

for (let role of userRoles.values()) {
console.log(role);
}
// director
// producer
// writer
// actress

Remove specified elements from map object

userRoles.delete(ruby);

Remove all elements in map object

userRoles.clear();
console.log(userRoles.size); // 0

ES6 — Advanced map concepts

JavaScript Map is a valuable collection type that makes programming with JavaScript cleaner and more efficient.
Some recommended concepts to cover next are:

  • Metaprogramming
  • Prototypal inheritance
  • Implementing constructors
  • Literals

ES6 — latest update: set

A JavaScript set is a collection of unique elements that can be traversed in the same order in which they were inserted.

A set can store primitive or object values​.

var example_set = new Set([“apple”,”bannana”,”mango”,”kiwi”]);
console.log(example_set)

There are various methods and properties implemented in the Set class.
A few of them are described below.

  • Set.prototype.size returns the number of elements in the set.

// print size of the set
console.log(fruits.size)

  • Set.prototype.add(val) adds the new element val at the end of the Set object.

// add “orange” to fruits.
fruits.add(“orange”)
console.log(fruits.values())

  • Set.prototype.delete(val) deletes the element val from the Set object.

// remove “kiwi” from fruits.
fruits.delete(“kiwi”)
console.log(fruits.values())

  • Set.prototype.clear() removes all the elements from the set.

// clear the set
fruits.clear()
console.log(fruits.values())

  • Set.prototype.has(val) returns true if val is present in the Set object.

// check if fruits contains “apple”
console.log(fruits.has(“apple”))

  • Set.prototype.values() returns all the values from the Set in the same insertion order.

console.log(fruits.values())

ES6 — latest update: generators

From its name, a generator is a function that allows you to generate one or more values by exiting and re-entering the execution procedure whilst saving its state (context) across multiple calls. To put it in simpler words, a generator is similar to normal functions, but has the ability to continue execution on demand at the point at which it was previously terminated, simply by saving its previous state.

There are some syntactic differences between a normal function and a generator:

// Normal Function
function normalFunction(params) {
// your logic goes here
return value;
}

/* --------------------------------- */

// Generator Function
function* generatorFunction(params) {
// your logic
yield value1;

// your logic
yield value2;

/*
.
.
.
*/

// your logic
yield valueN;
}

The first noticeable difference in syntax is that a generator is declared using the function* keyword instead of function. Also, notice how we use the return keyword in a normal function, while we use the yield keyword in a generator function instead, respectively. The yield keyword inside the generator allows us to 'return' a value, terminate execution, save the state (context) of the current lexical scope and waits for the next invocation to resume execution at the last termination point.

note: In a normal function, you can only execute the return keyword once, which will return a value and terminate the function completely. In a generator, you can use the yield keyword multiple times as much as you want to 'return' values on consecutive calls. You can also use the return keyword inside a generator, but leave this discussion for a different day.

Invocation

function normalFunction() {
console.log('I have been invoked');
}

// invocation
normalFunction();
----I have been invoked

In general, you can invoke a normal function by typing the function’s signature followed by a pair of parentheses ().

Now let’s try to use the same procedure to invoke a generator:

function* generatorFunction() {
console.log('I have been invoked');
yield 'first value';

console.log('resuming execution');
yield 'second value';
}

// does this invoke the generator?
generatorFunction();

In general, you can invoke a normal function by typing the function’s signature followed by a pair of parentheses ()but it’s not onGeneratorObject case.

The first and the most important method is called next(), which, from its name, yields the next value from the defined generator. Now lets modify our previous code to actually yield our values:

function* generatorFunction() {
console.log('I have been invoked');
yield 'first value';
console.log('resuming execution');
yield 'second value';
}
// store the Generator Object in a variable
let foo = generatorFunction();
// execute until we yield the first value
console.log(foo.next());
// resume execution until we yield the second value
console.log(foo.next());
// execute until the function ends
console.log(foo.next());
----I have been invoked
{ value: 'first value', done: false }
resuming execution
{ value: 'second value', done: false }
{ value: undefined, done: true }

When calling the first foo.next() method, the generator began to execute until it hit the first yield keyword and stops the execution. This is reflected in the first two lines of the output. Notice how the foo.next() returned an Object instead of the actual yielded value. This Object should always contain the following properties:

  • ‘value’: which holds the current yielded value from the generator.
  • ‘done’: a boolean flag which indicates whether the generator execution has reached the end or not.

ES6 — latest update: Block-Scoped Let and Const

ES6 introduced the keywords let and const that enable us to declare variables much easier. Previously, variables declared with var are function scoped, so if we declare them inside a for loop they will be available outside of the loop.

Variables declared with let and const are block-scoped, which means they are only accessible within the block where they were declared. So, if we declare a variable with let, it does not change its value in the outer scope. Const is similar, but the value of variables declared with this keyword cannot change through reassignment.

// using `var`
var y = “global”;

if (y === “global”) {
var y= “block-scoped”;

console.log(y);
// expected output: block-scoped
}

console.log(y);
// expected output: block-scoped

// using `let`
let x = “global”;

if (x === “global”) {
let x = “block-scoped”;

console.log(x);
// expected output: block-scoped
}

console.log(x);
// expected output: global

There is no hard-and-fast rule about when to use which variables. Here are two different opinions from popular JavaScript developers on how to use these three variables.

From Mathias Bynes: Use const by default and let if rebinding is needed. var should never be used in ES6.

From Kyle Simpson: Use var for top-level variables that are shared across many scopes. Use let for smaller scopes.

ES6 — latest update: Arrow functions

ES6 introduced arrows (=>) as a shorthand way to declare functions. This update has three notable advantages:

  • You no longer have to use .bind( ) method
  • Code is much cleaner and less verbose
  • You can skip the explicit return

var greeting = (name) => {
console.log(`hello ${name}`);
}

There are some cases where you will want to avoid using arrow functions. You need to be careful when using it alongside the this keyword. Now, when you use the arrow function, the this keyword is inherited from the parent scope.

ES6 — latest update: Classes

The updates to classes in ES6 do not introduce a new OO inheritance model. Instead, these classes are “syntactical sugar” to support prototype inheritance. This update is useful because it simplified your code without changing the basic model of JavaScript. It’s essentially a nicer, cleaner way of doing inheritance. You can create a class in two ways:

  • class declaration
  • class expression

You will need the method constructor to create a class.

class Person {
constructor(name, age){
this.name = name;
this.age = age;
}
greet(){
console.log(`My name is ${this.name} and I am ${this.age} years old` );
} // no commas in between methods
}

const sarah = new Person(“Sarah”,35);

sarah.greet();

ES6 — latest update: Template Literals

ES6 implemented the useful feature of template strings, now called template literals. This allows you to easily implement variables with a very simple syntax (${ }) and embed expressions.

It’s particularly useful for constructing API requests and nesting templates.

  • The template literal syntax is enclosed in backticks.
  • The syntax placeholders in template literals use ${expression}

let name = “Sarah”;
const greeting = `Hello my name is ${name}`;
console.log(greeting);

ES6 — Other updates with ES6

  • Additional String Methods
  • Destructuring
  • Array Improvements
  • Symbols
  • Promises
  • Default function arguments i.e. default parameters and default values
  • Generator
  • Proxies
  • Object literal updates
  • Sets, WeakSets, Maps, and WeakMaps

Last updated: April 2022

--

--