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.
Here we will introduce from the latest to the oldest the most important concepts in Javascript:
The most important ES2019
- ES9 — Array.prototype.flat:
Infinity
- ES9 — Symbol.prototype.description:
.description
- ES9 — Changes to Object.fromEntries:
Object.fromEntries
- ES9 — Other ES9 features
- ES9 — Other updates include
The most important ES2018 Updates
- ES8 — Rest / Spread for Objects
- ES8 — Asynchronous Iteration
- ES8 — Other updates from ES8
The most important ES2017 Updates
- ES7 — Object.entires( ) and Object.values( ):
Object.values( )
Object.entries( )
- ES7 — Async and Await:
.await( )
- ES7 — latest update: async/await:
async await
The most important ES2016 updates
- ES6 — Array.prototype.includes( ):
.includes( )
- ES6 — The exponential operator:
**
- ES6 — latest update: .map:
Map
- ES6 — latest update: set:
Set
- ES6 — latest update: Block-Scoped Let and Const:
let
const
- ES6 — latest update: Arrow functions:
=>
- ES6 — latest update: Classes:
class
- ES6 — latest update: Template Literals:
${ }
- ES6 —Almost deprecated : generators:
.next() function*
- ES6 — Other deprecated and obsolete features
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, orMath.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]);
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 themap()
is called
· thisValue: the value used as the function’sthis
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 elementval
at the end of the Set object.
// add “orange” to fruits.
fruits.add(“orange”)
console.log(fruits.values())
Set.prototype.delete(val)
deletes the elementval
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 ifval
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 onGenerator
Object 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
andconst
are block-scoped, which means they are only accessible within the block where they were declared. So, if we declare a variable withlet
, 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 andlet
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. Uselet
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, thethis
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
declarationclass
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