0% found this document useful (0 votes)
5 views189 pages

2 Es6

This document provides an overview of new ES6 syntax in JavaScript, focusing on the 'let' and 'const' keywords for variable declaration, their block-scoping behavior, and differences from the 'var' keyword. It explains concepts such as redeclaration, hoisting, temporal dead zones, and how 'const' can create immutable references while allowing mutable properties in objects. Additionally, it covers default function parameters and how to set them in function declarations.

Uploaded by

ROSHAN URKANDE
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views189 pages

2 Es6

This document provides an overview of new ES6 syntax in JavaScript, focusing on the 'let' and 'const' keywords for variable declaration, their block-scoping behavior, and differences from the 'var' keyword. It explains concepts such as redeclaration, hoisting, temporal dead zones, and how 'const' can create immutable references while allowing mutable properties in objects. Additionally, it covers default function parameters and how to set them in function declarations.

Uploaded by

ROSHAN URKANDE
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 189

Section 1.

New ES6 syntax

JavaScript let: Declaring Block-Scoped Variables

Summary: in this tutorial, you will learn how to use the JavaScript let keyword to declare
block-scoped variables.

Introduction to the JavaScript let keyword


In ES5, when you declare a variable using the var keyword, the scope of the variable is either
global or local. If you declare a variable outside of a function, the scope of the variable is
global. When you declare a variable inside a function, the scope of the variable is local.

ES6 provides a new way of declaring a variable by using the let keyword. The let keyword is
similar to the var keyword, except that these variables are blocked-scope. For example:

let variable_name;

In JavaScript, blocks are denoted by curly braces {} , for example, the if else, for, do while,
while, try catch and so on:

if(condition) {
// inside a block
}

See the following example:

let x = 10;
if (x == 10) {
let x = 20;
console.log(x); // 20: reference x inside the block
}
console.log(x); // 10: reference at the begining of the script

How the script works:


• First, declare a variable x and initialize its value to 10.
• Second, declare a new variable with the same name x inside the if block but with an
initial value of 20.
• Third, output the value of the variable x inside and after the if block.
Because the let keyword declares a block-scoped variable, the x variable inside the if block is
a new variable and it shadows the x variable declared at the top of the script. Therefore, the
value of x in the console is 20.

When the JavaScript engine completes executing the if block, the x variable inside the if
block is out of scope. Therefore, the value of the x variable that following the if block is 10.

JavaScript let and global object


When you declare a global variable using the var keyword, you add that variable to the
property list of the global object. In the case of the web browser, the global object is the
window. For example:

var a = 10;

console.log(window.a); // 10

However, when you use the let keyword to declare a variable, that variable is not attached to
the global object as a property. For example:

let b = 20;
console.log(window.b); // unde ned

JavaScript let and callback function in a for loop


See the following example.

for (var i = 0; i < 5; i++) {


setTimeout(function () {
console.log(i);
}, 1000);
}

The intention of the code is to output numbers from 0 to 4 to the console every second.
However, it outputs the number 5 ve times:

5
5
5
5
5
fi
fi
In this example, the variable i is a global variable. After the loop, its value is 5. When the
callback functions are passed to the setTimeout() function executes, they reference the same
variable i with the value 5

In ES5, you can x this issue by creating another scope so that each callback function
references a new variable. And to create a new scope, you need to create a function.
Typically, you use the IIFE pattern as follows:

for (var i = 0; i < 5; i++) {


(function (j) {
setTimeout(function () {
console.log(j);
}, 1000);
})(i);
}

Output:

0
1
2
3
4

In ES6, the let keyword declares a new variable in each loop iteration. Therefore, you just
need to replace the var keyword with the let keyword to x the issue:

for (let i = 0; i < 5; i++) {


setTimeout(function () {
console.log(i);
}, 1000);
}

To make the code completely ES6 style, you can use an arrow function as follows:

for (let i = 0; i < 5; i++) {


setTimeout(() => console.log(i), 1000);
}
fi
fi
Redeclaration
The var keyword allows you to redeclare a variable without any issue:

var counter = 0;
var counter;
console.log(counter); // 0

However, redeclaring a variable using the let keyword will result in an error:
Here’s the error message:

let counter = 0;
let counter;
console.log(counter);

Uncaught SyntaxError: Identi er 'counter' has already been declared

JavaScript let variables and hoisting


Let’s examine the following example:
This code causes an error:

{
console.log(counter); //
let counter = 10;
}

In this example, accessing the counter variable before declaring it causes a ReferenceError.

Uncaught ReferenceError: Cannot access 'counter' before initialization

You may think that a variable declaration using the let keyword does not hoist, but it does.

In fact, the JavaScript engine will hoist a variable declared by the let keyword to the top of the
block. However, the JavaScript engine does not initialize the variable. Therefore, when you
reference an uninitialized variable, you’ll get a ReferenceError.
fi
Temporal death zone (TDZ)
A variable declared by the let keyword has a so-called temporal dead zone (TDZ). The TDZ is
the time from the start of the block until the variable declaration is processed.

The following example illustrates that the temporal dead zone is time-based, not location-
based.

{ // enter new scope, TDZ starts


let log = function () {
console.log(message); // messagedeclared later
};

// This is the TDZ and accessing log


// would cause a ReferenceError

let message= 'Hello'; // TDZ ends


log(); // called outside TDZ
}

In this example:

First, the curly brace starts a new block scope, therefore, the TDZ starts.

Second, the log() function expression accesses the message variable. However,
the log() function has not been executed yet.

Third, declare the message variable and initialize its value to 'Hello'. The time from the start
of the block scope to the time that the message variable is accessed is called a temporal
death zone. When the JavaScript engine processes the declaration, the TDZ ends.

Finally, call the log() function that accesses the message variable outside of the TDZ.

Note that if you access a variable declared by the let keyword in the TDZ, you’ll get
a ReferenceError as illustrated in the following example.

{ // TDZ starts
console.log(typeof myVar); // unde ned
console.log(typeof message); // ReferenceError
let message; // TDZ ends
}

Notice that myVar variable is a non-existing variable, therefore, its type is unde ned.
The temporal death zone prevents you from accidentally referencing a variable before its
declaration.
fi
fi
Di erences Between var and let
Summary: in this tutorial, you will learn about the differences between the var and let keywords.

#1: Variable scopes


The var variables belong to the global scope when you de ne them outside a function. For
example:

var counter;

In this example, the counter is a global variable. It means that the counter variable is
accessible by any functions.

When you declare a variable inside a function using the var keyword, the scope of the
variable is local. For example:

function increase() {
var counter = 10;
}
// cannot access the counter variable here

In this example, the counter variable is local to the increase() function. It cannot be
accessible outside of the function.

The following example displays four numbers from 0 to 4 inside the loop and the number 5
outside the loop.

for (var i = 0; i < 5; i++) {


console.log("Inside the loop:", i);
}

console.log("Outside the loop:", i);

Output:

Inside the loop: 0


Inside the loop: 1
Inside the loop: 2
Inside the loop: 3
Inside the loop: 4
Outside the loop: 5
ff
fi
In this example, the i variable is a global variable. Therefore, it can be accessed from both
inside and after the for loop.

The following example uses the let keyword instead of the var keyword:

for (let i = 0; i < 5; i++) {


console.log("Inside the loop:", i);
}

console.log("Outside the loop:", i);

In this case, the code shows four numbers from 0 to 4 inside a loop and a reference error:

Inside the loop: 0


Inside the loop: 1
Inside the loop: 2
Inside the loop: 3
Inside the loop: 4

The error:

Uncaught ReferenceError: i is not de ned

Since this example uses the let keyword, the variable i is blocked scope. It means that the
variable i only exists and can be accessible inside the for loop block.

In JavaScript, a block is delimited by a pair of curly braces {} like in


the if...else and for statements:

if(condition) {
// inside a block
}

for(...) {
// inside a block
}
fi
#2: Creating global properties
The global var variables are added to the global object as properties. The global object
is window on the web browser and global on Node.js:

var counter = 0;
console.log(window.counter); // 0

However, the let variables are not added to the global object:

let counter = 0;
console.log(window.counter); // unde ned

#3: Redeclaration
The var keyword allows you to redeclare a variable without any issue:

var counter = 10;


var counter;
console.log(counter); // 10

However, if you redeclare a variable with the let keyword, you will get an error:

let counter = 10;


let counter; // error
fi
#4: The Temporal dead zone
The let variables have temporal dead zones while the var variables don’t. To understand the
temporal dead zone, let’s examine the life cycles of both var and let variables, which have
two steps: creation and execution.

The var variables


• In the creation phase, the JavaScript engine assigns storage spaces to var variables
and immediately initializes them to unde ned.
• In the execution phase, the JavaScript engine assigns the var variables the values
speci ed by the assignments if there are ones. Otherwise, the var variables remain
unde ned.

The let variables


• In the creation phase, the JavaScript engine assigns storage spaces to the let variables
but does not initialize the variables. Referencing uninitialized variables will cause
a ReferenceError.
• The let variables have the same execution phase as the var variables.

The temporal dead zone starts from the block until the let variable declaration is processed.
In other words, it is the location where you cannot access the let variables before they are
de ned.

In this tutorial, you have learned about the differences between var and let keywords.
fi
fi
fi
fi
const
Summary: in this tutorial, you’ll learn how to de ne constants by using the JavaScript const keyword.

Introduction to the JavaScript const keyword


ES6 provides a new way of declaring a constant by using the const keyword. The const keyword
creates a read-only reference to a value.

const CONSTANT_NAME = value;

By convention, the constant identi ers are in uppercase.

Like the let keyword, the const keyword declares blocked-scope variables. However, the
block-scoped variables declared by the const keyword can’t be reassigned.

The variables declared by the let keyword are mutable. It means that you can change their
values anytime you want as shown in the following example:

let a = 10;
a = 20;
a = a + 5;
console.log(a); // 25

However, variables created by the const keyword are “immutable”. In other words, you can’t
reassign them to different values.

If you attempt to reassign a variable declared by the const keyword, you’ll get
a TypeError like this:

const RATE = 0.1;


RATE = 0.2; // TypeError

Unlike the let keyword, you need to initialize the value to the variable declared by the const
keyword.

The following example causes a SyntaxError due to missing the initializer in the const variable
declaration:

const RED; // SyntaxError


fi
fi
JavaScript const and Objects
The const keyword ensures that the variable it creates is read-only. However, it doesn’t mean
that the actual value to which the const variable reference is immutable. For example:

const person = { age: 20 };


person.age = 30; // OK
console.log(person.age); // 30

Even though the person variable is a constant, you can change the value of its property.

However, you cannot reassign a different value to the person constant like this:

person = { age: 40 }; // TypeError

If you want the value of the person object to be immutable, you have to freeze it by using
the Object.freeze() method:

const person = Object.freeze({age: 20});


person.age = 30; // TypeError

Note that Object.freeze() is shallow, meaning that it can freeze the properties of the object,
not the objects referenced by the properties.

For example, the company object is constant and frozen.

const company = Object.freeze({


name: 'ABC corp',
address: {
street: 'North 1st street',
city: 'San Jose',
state: 'CA',
zipcode: 95134
}
});

But the company.address object is not immutable, you can add a new property to
the company.address object as follows:

company.address.country = 'USA'; // OK
JavaScript const and Arrays
Consider the following example:

const colors= ['red'];


colors.push(‘green');
console.log(colors); // ["red", "green"]

colors.pop();
colors.pop();
console.log(colors); // []

colors = []; // TypeError

In this example, we declare an array colors that has one element using the const keyword.
Then, we can change the array’s elements by adding the green color. However, we cannot
reassign the array colors to another array.

JavaScript const in a for loop


ES6 provides a new construct called for...of that allows you to create a loop iterating over
iterable objects such as arrays, maps, and sets.

let scores = [75, 80, 95];

for (let score of scores) {


console.log(score);
}

If you don’t intend to modify the score variable inside the loop, you can use the const keyword instead:

let scores = [75, 80, 95];


for (const score of scores) {
console.log(score);
}

In this example, the for...of creates a new binding for the const keyword in each loop
iteration. In other words, a new score constant is created in each iteration.

Notice that the const will not work in an imperative for loop. Trying to use the const keyword
to declare a variable in the imperative for loop will result in a TypeError:

for (const i = 0; i < scores.length; i++) { // TypeError


console.log(scores[i]);
}

The reason is that the declaration is only evaluated once before the loop body starts.
Default function Parameters
Summary: in this tutorial, you will learn how to handle JavaScript default parameters in ES6.

TL;DR

function say(message='Hi') {
console.log(message);
}

say(); // 'Hi'
say('Hello') // 'Hello'

The default value of the message paramater in the say() function is 'Hi'.

In JavaScript, default function parameters allow you to initialize named parameters with
default values if no values or unde ned are passed into the function.

Arguments vs. Parameters


Sometimes, you can use the terms argument and parameter interchangeably. However, by
de nition, parameters are what you specify in the function declaration whereas the
arguments are what you pass into the function.

Consider the following add() function:

function add(x, y) {
return x + y;
}

add(100,200);

In this example, the x and y are the parameters of the add() function, and the values passed
to the add() function 100 and 200 are the arguments.
fi
fi
Setting JavaScript default parameters for a function
In JavaScript, a parameter has a default value of unde ned. It means that if you don’t pass
the arguments into the function, its parameters will have the default values of unde ned.

See the following example:

function say(message) {
console.log(message);
}

say(); // unde ned

The say() function takes the message parameter. Because we didn’t pass any argument into
the say() function, the value of the message parameter is unde ned.

Suppose that you want to give the message parameter a default value 10.

A typical way for achieving this is to test parameter value and assign a default value if it
is unde ned using a ternary operator:

function say(message) {
message = typeof message !== 'unde ned' ? message : 'Hi';
console.log(message);
}
say(); // 'Hi'

In this example, we didn’t pass any value into the say() function. Therefore, the default value
of the message argument is unde ned. Inside the function, we reassigned
the message variable the Hi string.

ES6 provides you with an easier way to set the default values for the function parameters like
this:

function fn(param1=default1, param2=default2,..) {


}

In the syntax above, you use the assignment operator (=) and the default value after the
parameter name to set a default value for that parameter. For example:

function say(message='Hi') {
console.log(message);
}

say(); // 'Hi'
say(unde ned); // 'Hi'
say('Hello'); // 'Hello'
fi
fi
fi
fi
fi
fi
fi
fi
How it works.

• In the rst function call, we didn’t pass any argument into the say() function,
therefore message parameter took the default value 'Hi'.
• In the second function call, we passed the unde ned into the say() function, hence
the message parameter also took the default value 'Hi'.
• In the third function call, we passed the 'Hello' string into the say() function,
therefore message parameter took the string 'Hello' as the default value.

More JavaScript default parameter examples
Let’s look at some more examples to learn some available options for setting default values
of the function parameters.

1) Passing unde ned arguments


The following createDiv() function creates a new <div> element in the document with a
speci c height, width, and border-style:

function createDiv(height = '100px', width = '100px', border = 'solid 1px red') {


let div = document.createElement('div');
div.style.height = height;
div.style.width = width;
div.style.border = border;
document.body.appendChild(div);
return div;
}

The following doesn’t pass any arguments to the function so the createDiv() function uses the
default values for the parameters.

createDiv();

Suppose you want to use the default values for the height and width parameters and speci c
border style. In this case, you need to pass unde ned values to the rst two parameters as
follows:

createDiv(unde ned,unde ned,'solid 5px blue');


fi
fi
fi
fi
fi
fi
fi
fi
fi
2) Evaluating default parameters
JavaScript engine evaluates the default arguments at the time you call the function. See the
following example:

function put(toy, toyBox = []) {


toyBox.push(toy);
return toyBox;
}

console.log(put('Toy Car'));
// -> ['Toy Car']
console.log(put('Teddy Bear'));
// -> ['Teddy Bear'], not ['Toy Car','Teddy Bear']

The parameter can take a default value which is a result of a function.

Consider the following example:

function date(d = today()) {


console.log(d);
}
function today() {
return (new Date()).toLocaleDateString("en-US");
}
date();

The date() function takes one parameter whose default value is the returned value of
the today() function. The today() function returns today’s date in a speci ed string format.

When we declared the date() function, the today() function has not yet evaluated until we
called the date() function.

We can use this feature to make arguments mandatory. If the caller doesn’t pass any
argument, we throw an error as follows:

function requiredArg() {
throw new Error('The argument is required');
}
function add(x = requiredArg(), y = requiredArg()){
return x + y;
}

add(10); // error
add(10,20); // OK
fi
3) Using other parameters in default values
You can assign a parameter a default value that references other default parameters as
shown in the following example:

function add(x = 1, y = x, z = x + y) {
return x + y + z;
}

console.log(add()); // 4

In the add() function:

• The default value of the y is set to x parameter.


• The default value of the z is the sum of x and y
• The add() function returns the sum of x, y, and z.
The parameter list seems to have its own scope. If you reference the parameter that has not
been initialized yet, you will get an error. For example:

function subtract( x = y, y = 1 ) {
return x - y;
}
subtract(10);

Error message:

Uncaught ReferenceError: Cannot access 'y' before initialization

Using functions
You can use a return value of a function as a default value for a parameter. For example:

let taxRate = () => 0.1;


let getPrice = function( price, tax = price * taxRate() ) {
return price + tax;
}

let fullPrice = getPrice(100);


console.log(fullPrice); // 110

In the getPrice() function, we called the taxRate() function to get the tax rate and use this tax
rate to calculate the tax amount from the price.
The arguments object
The value of the arguments object inside the function is the number of actual arguments that
you pass to the function. For example:

function add(x, y = 1, z = 2) {
console.log( arguments.length );
return x + y + z;
}

add(10); // 1
add(10, 20); // 2
add(10, 20, 30); // 3

Now, you should understand the JavaScript default function parameters and how to use them
effectively.
Rest Parameters
Summary: in this tutorial, you will learn how to use the JavaScript rest parameters to gather
parameters and put them all in an array.

Introduction to JavaScript rest parameters


ES6 provides a new kind of parameter so-called rest parameter that has a pre x of three
dots (...). A rest parameter allows you to represent an inde nite number of arguments as
an array. See the following syntax:

function fn(a,b,...args) {
//...
}

The last parameter (args) is pre xed with the three-dots ( ...). It’s called a rest parameter
( ...args).

All the arguments you pass to the function will map to the parameter list. In the syntax above,
the rst argument maps to a, the second one maps to b, and the third, the fourth, etc., will be
stored in the rest parameter args as an array. For example:

fn(1, 2, 3, "A", "B", "C");

The args array stores the following values:

[3,'A','B','C']

If you pass only the rst two parameters, the rest parameter will be an empty array:

fn(1,2);

The args will be:

[]
fi
fi
fi
fi
fi
Notice that the rest parameters must appear at the end of the argument list. The following
code will result in an error:

function fn(a,...rest, b) {
// error
}

Error:

SyntaxError: Rest parameter must be last formal parameter

More JavaScript rest parameters examples


See the following example:

function sum(...args) {
let total = 0;
for (const a of args) {
total += a;
}
return total;
}

sum(1, 2, 3);

The output of the script is:

In this example, args in an array. Therefore, you could use the for..of loop to iterate over its
elements and sum them up.

Assuming that the caller of the sum() function may pass arguments with various kinds of data
types such as number, string, and boolean, and you want to calculate the total of numbers
only:
function sum(...args) {
return args
. lter(function (e) {
return typeof e === 'number';
})
.reduce(function (prev, curr) {
return prev + curr;
});
}

The following script uses the new sum() function to sum only numeric arguments:

let result = sum(10,'Hi',null,unde ned,20);


console.log(result);

Output:

30

Note that without the rest parameters, you have to use the arguments object of the function.

However, the arguments object itself is not an instance of the Array type. Therefore, you
cannot use the lter() method directly. In ES5, you have to use Array.prototype. lter.call() as
follows:

function sum() {
return Array.prototype. lter
.call(arguments, function (e) {
return typeof e === 'number';
})
.reduce(function (prev, curr) {
return prev + curr;
});
}

As you see, the rest parameter makes the code more elegant. Suppose you need to lter the
arguments based on a speci c type such as numbers, strings, boolean, and null. The
following function helps you to do it:

function lterBy(type, ...args) {


return args. lter(function (e) {
return typeof e === type;
});
}
fi
fi
fi
fi
fi
fi
fi
fi
fi
JavaScript rest parameters and arrow function
An arrow function does not have the arguments object. Therefore, if you want to pass some
arguments to the arrow function, you must use the rest parameters. See the following
example:

const combine = (...args) => {


return args.reduce(function (prev, curr) {
return prev + ' ' + curr;
});
};

let message = combine('JavaScript', 'Rest', 'Parameters'); // =>


console.log(message); // JavaScript Rest Parameters

Output:

JavaScript Rest Parameters

The combine() function is an arrow that takes an inde nite number of arguments and
concatenates these arguments.

JavaScript rest parameter in a dynamic function


JavaScript allows you to create dynamic functions through the Function constructor. And it is
possible to use the rest parameter in a dynamic function. Here is an example:

var showNumbers = new Function('...numbers', 'console.log(numbers)');


showNumbers(1, 2, 3);

Output:

[ 1, 2, 3 ]

In this tutorial, you have learned how to use the JavaScript rest parameter to represent an
inde nite number of arguments as an array.
fi
fi
Spread Operator
Summary: in this tutorial, you will learn about the JavaScript spread operator that spreads
out elements of an iterable object.

Introduction to the JavaScript spread operator


ES6 provides a new operator called spread operator that consists of three dots (...). The
spread operator allows you to spread out elements of an iterable object such as
an array, map, or set. For example:

const odd = [1,3,5];


const combined = [2,4,6, ...odd];
console.log(combined);

Output:

[ 2, 4, 6, 1, 3, 5 ]

In this example, the three dots ( ...) located in front of the odd array is the spread operator.
The spread operator (...) unpacks the elements of the odd array.

Note that ES6 also has the three dots ( ...) which is a rest parameter that collects all
remaining arguments of a function into an array.

function f(a, b, ...args) {


console.log(args);
}

f(1, 2, 3, 4, 5);

Output:

[ 3, 4, 5 ]

In this example, the rest parameter (...) collects the arguments 3, 4, and 5 into an array args.
So the three dots ( ...) represent both the spread operator and the rest parameter.
Here are the main differences:

• The spread operator (...) unpacks the elements of an iterable object.


• The rest parameter (...) packs the elements into an array.
The rest parameters must be the last arguments of a function. However, the spread operator
can be anywhere:

const odd = [1,3,5];


const combined = [...odd, 2,4,6];
console.log(combined);

Output:

[ 1, 3, 5, 2, 4, 6 ]

Or

const odd = [1,3,5];


const combined = [2,...odd, 4,6];
console.log(combined);

Output:

[ 2, 1, 3, 5, 4, 6 ]

Note that ES2018 expands the spread operator to objects, which is known as object
spread.
Let’s look at some scenarios where you can use the spread operators.

JavaScript spread operator and apply() method


See the following compare() function compares two numbers:

function compare(a, b) {
return a - b;
}

In ES5, to pass an array of two numbers to the compare() function, you often use
the apply() method as follows:

let result = compare.apply(null, [1, 2]);


console.log(result); // -1

However, by using the spread operator, you can pass an array of two numbers to
the compare() function:

let result = compare(...[1, 2]);


console.log(result); // -1

The spread operator spreads out the elements of the array so a is 1 and b is 2 in this case.

A better way to use the Array’s push() method example


Sometimes, a function may accept an inde nite number of arguments. Filling arguments from
an array is not convenient.

For example, the push() method of an array object allows you to add one or more elements to
an array. If you want to pass an array to the push() method, you need to use apply() method
as follows:

let rivers = ['Nile', 'Ganges', 'Yangte'];


let moreRivers = ['Danube', 'Amazon'];

[ ].push.apply(rivers, moreRivers);
console.log(rivers);

This solution looks verbose.

The following example uses the spread operator to improve the readability of the code:

rivers.push(...moreRivers);
fi
JavaScript spread operator and array manipulation

1) Constructing array literal


The spread operator allows you to insert another array into the initialized array when you
construct an array using the literal form. See the following example:

let initialChars = ['A', 'B'];


let chars = [...initialChars, 'C', 'D'];
console.log(chars); // ["A", "B", "C", "D"]

2) Concatenating arrays
Also, you can use the spread operator to concatenate two or more arrays:

let numbers = [1, 2];


let moreNumbers = [3, 4];
let allNumbers = [...numbers, ...moreNumbers];
console.log(allNumbers); // [1, 2, 3, 4]

3) Copying an array
In addition, you can copy an array instance by using the spread operator:

let scores = [80, 70, 90];


let copiedScores = [...scores];
console.log(copiedScores); // [80, 70, 90]

Note that the spread operator only copies the array itself to the new one, not the
elements. This means that the copy is shallow, not deep.
JavaScript spread operator and strings
Consider the following example:

let chars = ['A', ...'BC', 'D'];


console.log(chars); // ["A", "B", "C", "D"]

In this example, we constructed the chars array from individual strings. When we applied the
spread operator to the 'BC'string, it spreads out each individual character of the string 'BC' into
individual characters.

Summary
• The spread operator is denoted by three dots (…).
• The spread operator unpacks elements of iterable objects such as arrays, sets, and
maps into a list.
• The rest paramter is also denoted by three dots (…). However, it packs the remaining
arguments of a function into an array.
• The spread operator can be used to clone an iterable object or merge iterable objects
into one.
Object Literal Syntax Extensions
Summary: in this tutorial, you will learn about the syntax extensions of the object literal in
ES6 that make your code cleaner and more exible.

The object literal is one of the most popular patterns for creating objects in
JavaScript because of its simplicity. ES6 makes the object literal more succinct and powerful
by extending the syntax in some ways.

Object property initializer shorthand


Prior to ES6, an object literal is a collection of name-value pairs. For example:

function createMachine(name, status) {


return {
name: name,
status: status
};
}

The createMachine() function takes two arguments name and status and returns a new
object literal with two properties: name and status.

The name and status properties take the values of the name and status parameters. This
syntax looks redundant because name and status mentioned twice in both the name and
value of properties.

ES6 allows you to eliminate the duplication when a property of an object is the same as the
local variable name by including the name without a colon and value.

For example, you can rewrite the createMachine() function in ES6 as follows:

Internally, when a property of an object literal only has a name, the JavaScript engine

function createMachine(name, status) {


return {
name,
status
};
}

searches for a variable with the same name in the surrounding scope. If the JavaScript
engine can nd one, it assigns the property the value of the variable.
fi
fl
In this example, the JavaScript engine assigns the name and status property values of
the name and status arguments.

Similarly, you can construct an object literal from local variables as shown in the following
example:

let name = 'Computer',


status = 'On';

let machine = {
name,
status
};

Computed property name


Prior to ES6, you could use the square brackets( []) to enable the computed property
names for the properties on objects.

The square brackets allow you to use the string literals and variables as the property names.

let name = 'machine name';


let machine = {
[name]: 'server',
'machine hours': 10000
};

console.log(machine[name]); // server
console.log(machine['machine hours']); // 10000

The name variable was initialized to a value of 'machine name'. Since both properties of
the machine object contains a space, you can only reference them using the square
brackets.

In ES6, the computed property name is a part of the object literal syntax, and it uses the
square bracket notation.

When a property name is placed inside the square brackets, the JavaScript engine evaluates
it as a string. It means that you can use an expression as a property name. For example:

let pre x = 'machine';


let machine = {
[pre x + ' name']: 'server',
[pre x + ' hours']: 10000
};

console.log(machine['machine name']); // server


console.log(machine['machine hours']); // 10000
fi
fi
fi
The machine object’s properties evaluate to 'machine name' and 'machine hours', therefore
you can reference them as the properties of the machine object.

Concise method syntax


Prior to ES6, when de ning a method for an object literal, you need to specify the name and
full function de nition as shown in the following example:

let server = {
name: "Server",
restart: function () {
console.log("The" + this.name + " is restarting...");
}
};

ES6 makes the syntax for making a method of the object literal more succinct by removing
the colon (:) and the function keyword.

The following example rewrites the server object above using the ES6 syntax.

let server = {
name: 'Server',
restart() {
console.log("The" + this.name + " is restarting...");
}
};

This shorthand syntax is also known as the concise method syntax. It’s valid to have
spaces in the property name. For example:

let server = {
name: 'Server',
restart() {
console.log("The " + this.name + " is restarting...");
},
'starting up'() {
console.log("The " + this.name + " is starting up!");
}
};

server['starting up']();

In this example, the method 'starting up' has spaces in its name. To call the method, you use
the following syntax:

object_name['property name']();
fi
fi
for…of Loop
Summary: in this tutorial, you’ll how to use JavaScript for...of statement to iterate over
iterable objects.

Introduction to the JavaScript for…of loop


ES6 introduced a new statement for...of that iterates over an iterable object such as:

• Built-in Array, String, Map, Set, …


• Array-like objects such as arguments or NodeList
• User-de ned objects that implement the iterator protocol.
The following illustrates the syntax of the for...of:

for (variable of iterable) {


// ...
}

variable
In each iteration, a property of the iterable object is assigned to the variable. You can
use var, let, or const to declare the variable.

iterable
The iterable is an object whose iterable properties are iterated.

JavaScript for of loop examples


Let’s take a look at some examples of using the for...of loop.

1) Iterating over arrays


The following example shows how to use the for...of to iterate over elements of an array:

let scores = [80, 90, 70];

for (let score of scores) {


score = score + 5;
console.log(score);
}
fi
Output:

85
95
75

In this example, the for...of iterates over every element of the scores array. It assigns the
element of the scores array to the variable score in each iteration.

If you don’t change the variable inside the loop, you should use the const keyword instead of
the let keyword as follows:

let scores = [80, 90, 70];

for (const score of scores) {


console.log(score);
}

Output:

80
90
70

To access the index of the array elements inside the loop, you can use the for...of statement
with the entries() method of the array.

The array.entries() method returns a pair of [index, element] in each iteration. For example:

let colors = ['Red', 'Green', 'Blue'];

for (const [index, color] of colors.entries()) {


console.log(`${color} is at index ${index}`);
}

Output:

Red is at index 0
Green is at index 1
Blue is at index 2

In this example, we used the array destructuring to assign the result of the entries() method
to the index and color variables in each iteration:

const [index, color] of colors.entries()


2) In-place object destructuring with for…of
Consider the following example:

const ratings = [
{user: 'John',score: 3},
{user: 'Jane',score: 4},
{user: 'David',score: 5},
{user: 'Peter',score: 2},
];

let sum = 0;
for (const {score} of ratings) {
sum += score;
}

console.log(`Total scores: ${sum}`); // 14

Output:

Total scores: 14

How it works:

• The ratings is an array of objects. Each object has two properties user and score.
• The for...of iterate over the ratings array and calculate the total scores of all objects.
• The expression const {score} of ratings uses object destructing to assign
the score property of the current iterated element to the score variable.

3) Iterating over strings


The following example uses the for...of loop to iterate over characters of a string.

let str = 'abc';


for (let c of str) {
console.log(c);
}

Output:

a
b
c
4) Iterating over Map objects
The following example illustrates how to use the for...of statement to iterate over
a Map object.

let colors = new Map();

colors.set('red', '# 0000');


colors.set('green', '#00 00');
colors.set('blue', '#0000 ');

for (let color of colors) {


console.log(color);
}

Output:

[ 'red', '# 0000' ]


[ 'green', '#00 00' ]
[ 'blue', '#0000 ' ]

5) Iterating over set objects


The following example shows how to iterate over a set object using the for...of loop:

let nums = new Set([1, 2, 3]);

for (let num of nums) {


console.log(num);
}
ff
ff
ff
ff
ff
ff
for...of vs. for...in
The for...in iterates over all enumerable properties of an object. It doesn’t iterate over a
collection such as Array, Map or Set.

Unlike the for...in loop, the for...of iterates a collection, rather than an object. In fact,
the for...of iterates over elements of any collection that has the [Symbol.iterator] property.

The following example illustrates the differences between for...of and for...in

let scores = [10,20,30];


scores.message = 'Hi';

console.log("for...in:");
for (let score in scores) {
console.log(score);
}

console.log('for...of:');
for (let score of scores) {
console.log(score);
}

Output:

for...in:
0
1
2
message
for...of:
10
20
30

In this example, the for…in statement iterates over the properties of the scores array:

for...in:
0
1
2
message

while the for…of iterates over the element of an array:

for...of:
10
20
30
Octal and Binary Literals
Summary: in this tutorial, you will learn how to represent the octal and binary literals in ES6.

ES5 provided numeric literals in octal (pre x 0), decimal (no pre x), and hexadecimal (0x).
ES6 added support for binary literals and changed how it represents octal literals.

Octal literals
To represent an octal literal in ES5, you use the zero pre x (0) followed by a sequence of
octal digits (from 0 to 7). For example:

let a = 051;
console.log(a); // 41

If the octal literal contains a number that is out of range, JavaScript ignores the leading 0 and
treats the octal literal as a decimal, as shown in the following example:

let b = 058; // invalid octal


console.log(b); // 58

In this example, since 8 is an invalid digit for representing the octal number, JavaScript
ignores the 0 and treats the whole number as a decimal with a value of 58.

Note you can use the octal literals in non-strict mode. If you use them in strict mode,
JavaScript will throw an error.

"use strict"
let b = 058; // invalid octal
console.log(b);

Here is the error message:

SyntaxError: Decimals with leading zeros are not allowed in strict mode.

ES6 allows you to specify the octal literal by using the pre x 0o followed by a sequence of
octal digits from 0 through 7. Here is an example:

let c = 0o51;
console.log(c); // 41
fi
fi
fi
fi
If you use an invalid number in the octal literal, JavaScript will throw a SyntaxError as shown
in the following example:

let d = 0o58;
console.log(d); // SyntaxError

Binary literals
In ES5, JavaScript didn’t provide any literal form for binary numbers. To parse a binary string,
you use the parseInt() function as follows:

let e = parseInt('111',2);
console.log(e); // 7

ES6 added support for binary literals by using the 0b pre x followed by a sequence of binary
numbers (0 and 1). Here is an example:

let f = 0b111;
console.log(f); // 7

Summary
• Octal literals start with 0o followed by a sequence of numbers between 0 and 7.
• Binary literals start with 0b followed by a sequence of number 0 and 1.
fi
Template Literal
Summary: in this tutorial, you will learn about JavaScript template literal, which allows you to
work with a string template more easily.

Before ES6, you use single quotes (') or double quotes (") to wrap a string literal. And the
strings have very limited functionality.

To enable you to solve more complex problems, ES6 template literals provide the syntax that
allows you to work with strings more safely and cleanly.

In ES6, you create a template literal by wrapping your text in backticks (`) as follows:

let simple = `This is a template literal`;

and you get the following features:

• A multiline string: a string that can span multiple lines.


• String formatting: the ability to substitute part of the string for the values of variables or
expressions. This feature is also called string interpolation.
• HTML escaping: the ability to transform a string so that it is safe to include in HTML.

The basic syntax of JavaScript template literals


As mentioned earlier, instead of using the single quotes or double quotes, a template literal
uses backticks, as shown in the following example:
Using the backticks, you can freely use the single or double quotes in the template literal
without escaping.

let str = `Template literal in ES6`;

console.log(str);// Template literal in ES6


console.log(str.length); // 23
console.log(typeof str);// string

If a string contains a backtick, you must escape it using a backslash (\) :

let anotherStr = `Here's a template literal`;

let strWithBacktick = `Template literals use backticks \` insead of quotes`;


Multiline strings
Before ES6, you use the following technique to create a multi-line string by manually
including the newline character ( \n) in the string as follows:

let msg = 'Multiline \n\


string';

console.log(msg);
//Multiline
//string

Note that the backslash ( \) placed after the newline character ( \n) indicates the continuation
of the string rather than a new line.

This technique, however, is not consistent across JavaScript engines. Therefore, it was pretty
common to create a multiline string that relies on an array and string concatenation as
follows:

let msg = ['This text',


'can',
'span multiple lines'].join('\n');

The template literals allow you to de ne multiline strings more easily because you need to
add a new line in the string wherever you want:

let p =
`This text
can
span multiple lines`;

Note that the whitespace is a part of the string. Therefore, you need to ensure that the text
lines up with proper indentation. Suppose you have a post object:

let post = {
title: 'JavaScript Template Literals',
excerpt: 'Introduction to JavaScript template literals in ES6',
body: 'Content of the post will be here...',
tags: ['es6', 'template literals', 'javascript']
};
fi
The following code returns the HTML code of the post object. Note that we use the object
destructuring technique to assign the properties of the post object to individual
variables : title, excerpt, body, and tags.

let {title, excerpt, body, tags} = post;

let postHtml = `<article>


<header>
<h1>${title}</h1>
</header>
<section>
<div>${excerpt}</div>
<div>${body}</div>
</section>
<footer>
<ul>
${tags.map(tag => `<li>${tag}</li>`).join('\n ')}
</ul>
</footer>`;

The following is the output of the variable postHtml. Notice how we used the spacing to indent
the <li> tags correctly.

<article>
<header>
<h1>JavaScript Template Literals</h1>
</header>
<section>
<div>Introduction to JavaScript template literals in ES6</div>
<div>Content of the post will be here...</div>
</section>
<footer>
<ul>
<li>es6</li>
<li>template literals</li>
<li>javascript</li>
</ul>
</footer>
Variable and expression substitutions
At this point, a template literal is just like a better version of a regular JavaScript string. The
big difference between a template literal and a regular string is substitutions.

The substitutions allow you to embed variables and expressions in a string. The JavaScript
engine will automatically replace these variables and expressions with their values. This
feature is known as string interpolation.

To instruct JavaScript to substitute a variable and expression, you place the variable and
expression in a special block as follows:

${variable_name}

See the following example:

let rstName = 'John',


lastName = 'Doe';

let greeting = `Hi ${ rstName}, ${lastName}`;


console.log(greeting); // Hi John, Doe

The substitution ${ rstName} and ${lastName} access the variables


rstName and lastName to insert their values into the greeting string.

The greeting variable then holds the result of the substitutions. The following example
substitutes an expression instead:

let price = 8.99,


tax = 0.1;

let netPrice = `Net Price:$${(price * (1 + tax)).toFixed(2)}`;

console.log(netPrice); // netPrice:$9.89
fi
fi
fi
fi
Tagged templates
A template tag carries the transformation on the template literal and returns the result string.

You place the tag at the beginning of the template before the backtick (`) character as
follows:

let greeting = tag`Hi`;

In this example, tag is the template tag that applies to the Hi template literal. The tag can be
any function with the following signature:

function tag(literals, ...substitutions) {


// return a string
}

In this function:

• The literals parameter is an array that contains the literal strings.


• The substitutions parameter contains the subsequent arguments interpreted for each
substitution.
See the following example:

function format(literals, ...substitutions) {


let result = '';

for (let i = 0; i < substitutions.length; i++) {


result += literals[i];
result += substitutions[i];
}
// add the last literal
result += literals[literals.length - 1];
return result;
}

let quantity = 9,
priceEach = 8.99,
result = format`${quantity} items cost $${(quantity * priceEach).toFixed(2)}.`;

console.log(result); // 9 items cost $80.91.


In this example, the format() function accepts three arguments: the literals array and two
other arguments stored in the substitutions array.

The rst argument is the literals array that contains three elements:

• An empty string before the rst substitution (”). Note that the rst argument of the
literals array is an empty string.
• A string 'items cost' that locates between the rst and the second substitutions.
• A string that follows the second substitution ('.')
The second argument is 9, which is the interpreted value of the quantity variable. It becomes
the rst element of the substitutions array. The third argument is 80.91, which is the
interpreted value of the expression (quantity * priceEach).toFixed(2). It becomes the second
element of the substitutions array.

Summary
• Use the backtick to create a string literal for string interpolation.
fi
fi
fi
fi
fi
Section 2. Destructuring

Array destructuring
Summary: in this tutorial, you will learn how to use the ES6 destructuring assignment that
allows you to destructure an array into individual variables.

ES6 provides a new feature called destructing assignment that allows you to destructure
properties of an object or elements of an array into individual variables.

Let’s start with the array destructuring.

Introduction to JavaScript Array destructuring


Assuming that you have a function that returns an array of numbers as follows:

function getScores() {
return [70, 80, 90];
}

The following invokes the getScores() function and assigns the returned value to a variable:

let scores = getScores();

To get the individual score, you need to do like this:

let x = scores[0],
y = scores[1],
z = scores[2];

Prior to ES6, there was no direct way to assign the elements of the returned array to multiple
variables such as x, y and z.

Fortunately, starting from ES6, you can use the destructing assignment as follows:

let [x, y, z] = getScores();

console.log(x); // 70
console.log(y); // 80
console.log(z); // 90
The variables x, y and z will take the values of the rst, second, and third elements of the
returned array.

Note that the square brackets [] look like the array syntax but they are not.

If the getScores() function returns an array of two elements, the third variable will
be unde ned, like this:

function getScores() {
return [70, 80];
}

let [x, y, z] = getScores();

console.log(x); // 70
console.log(y); // 80
console.log(z); // unde ned

In case the getScores() function returns an array that has more than three elements, the
remaining elements are discarded. For example:

function getScores() {
return [70, 80, 90, 100];
}

let [x, y, z] = getScores();

console.log(x); // 70
console.log(y); // 80
console.log(z); // 90

Array Destructuring Assignment and Rest syntax


It’s possible to take all remaining elements of an array and put them in a new array by using
the rest syntax (...):

let [x, y ,...args] = getScores();


console.log(x); // 70
console.log(y); // 80
console.log(args); // [90, 100]

The variables x and y receive values of the rst two elements of the returned array. And
the args variable receives all the remaining arguments, which are the last two elements of
the returned array.
fi
fi
fi
fi
Note that it’s possible to destructure an array in the assignment that separates from the
variable’s declaration. For example:

let a, b;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20

Setting default values


See the following example:

function getItems() {
return [10, 20];
}

let items = getItems();


let thirdItem = items[2] != unde ned ? items[2] : 0;

console.log(thirdItem); // 0

How it works:

• First, declare the getItems() function that returns an array of two numbers.
• Then, assign the items variable to the returned array of the getItems() function.
• Finally, check if the third element exists in the array. If not, assign the value 0 to
the thirdItem variable.
It’ll be simpler with the destructuring assignment with a default value:

let [, , thirdItem = 0] = getItems();

console.log(thirdItem); // 0

If the value taken from the array is unde ned, you can assign the variable a default value,
like this:

let a, b;
[a = 1, b = 2] = [10];
console.log(a); // 10
console.log(b); // 2

If the getItems() function doesn’t return an array and you expect an array, the destructing
assignment will result in an error. For example:
fi
fi
function getItems() {

Uncaught TypeError: getItems is not a function or its return value is not iterable

return null;
}

let [x = 1, y = 2] = getItems();

Error:

A typical way to solve this is to fallback the returned value of the getItems() function to an
empty array like this:

function getItems() {
return null;
}

let [a = 10, b = 20] = getItems() || [];

console.log(a); // 10
console.log(b); // 20

Nested array destructuring


The following function returns an array that contains an element which is another array, or
nested array:

function getPro le() {


return [
'John',
'Doe',
['Red', 'Green', 'Blue']
];
}

Since the third element of the returned array is another array, you need to use the nested
array destructuring syntax to destructure it, like this:

let [
rstName,
lastName,
[
color1,
color2,
color3
]
] = getPro le();

console.log(color1, color2, color3); // Red Green Blue


fi
fi
fi
Array Destructuring Assignment Applications
Let’s see some practical examples of using the array destructuring assignment syntax.

1) Swapping variables
The array destructuring makes it easy to swap values of variables without using a temporary
variable:

let a = 10,
b = 20;

[a, b] = [b, a];

console.log(a); // 20
console.log(b); // 10

2) Functions that return multiple values


In JavaScript, a function can return a value. However, you can return an array that contains
multiple values, for example:

function stat(a, b) {
return [
a + b,
(a + b) / 2,
a-b
]
}

And then you use the array destructuring assignment syntax to destructure the elements of
the return array into variables:

let [sum, average, di erence] = stat(20, 10);


console.log(sum, average, di erence); // 30, 15, 10

In this tutorial, you have learned how to use the ES6 destructuring assignment to destructure
elements in an array into individual variables.
ff
ff
Object Destructuring
Summary: in this tutorial, you’ll learn about JavaScript object destructuring which assigns
properties of an object to individual variables.

Introduction to the JavaScript object destructuring assignment


Suppose you have a person object with two properties: rstName and lastName.

let person = {
rstName: 'John',
lastName: 'Doe'
};

Prior to ES6, when you want to assign properties of the person object to variables, you
typically do it like this:

let rstName = person. rstName;


let lastName = person.lastName;

ES6 introduces the object destructuring syntax that provides an alternative way to
assign properties of an object to variables:

let { rstName: fname, lastName: lname } = person;

In this example, the rstName and lastName properties are assigned to


the fName and lName variables respectively.

In this syntax:

let { property1: variable1, property2: variable2 } = object;

The identi er before the colon (:) is the property of the object and the identi er after the colon
is the variable.

Notice that the property name is always on the left whether it’s an object literal or object
destructuring syntax.
fi
fi
fi
fi
fi
fi
fi
fi
If the variables have the same names as the properties of the object, you can make the code
more concise as follows:

let { rstName, lastName } = person;

console.log( rstName); // 'John'


console.log(lastName); // 'Doe'

In this example, we declared two variables rstName and lastName, and assigned the
properties of the person object to the variables in the same statement.

It’s possible to separate the declaration and assignment. However, you must surround the
variables in parentheses:

({ rstName, lastName} = person);

If you don’t use the parentheses, the JavaScript engine will interpret the left-hand side as a
block and throw a syntax error.

When you assign a property that does not exist to a variable using the object destructuring,
the variable is set to unde ned. For example:

let { rstName, lastName, middleName } = person;


console.log(middleName); // unde ned

In this example, the middleName property doesn’t exist in the person object, therefore,
the middleName variable is unde ned.

Setting default values


You can assign a default value to the variable when the property of an object doesn’t exist.
For example:

let person = {
rstName: 'John',
lastName: 'Doe',
currentAge: 28
};

let { rstName, lastName, middleName = '', currentAge: age = 18 } = person;

console.log(middleName); // ''
console.log(age); // 28
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
In this example, we assign an empty string to the middleName variable when the person
object doesn’t have the middleName property.

Also, we assign the currentAge property to the age variable with the default value of 18.

However, when the person object does have the middleName property, the assignment
works as usual:

let person = {
rstName: 'John',
lastName: 'Doe',
middleName: 'C.',
currentAge: 28
};

let { rstName, lastName, middleName = '', currentAge: age = 18 } = person;

console.log(middleName); // 'C.'
console.log(age); // 28

Destructuring a null object


A function may return an object or null in some situations. For example:

function getPerson() {
return null;
}

And you use the object destructuring assignment:

let { rstName, lastName } = getPerson();

console.log( rstName, lastName);

The code will throw a TypeError:

TypeError: Cannot destructure property ' rstName' of 'getPerson(...)' as it is null.

To avoid this, you can use the OR operator (||) to fallback the null object to an empty object:

let { rstName, lastName } = getPerson() || {};

Now, no error will occur. And the rstName and lastName will be unde ned.
fi
fi
fi
fi
fi
fi
fi
fi
Nested object destructuring
Assuming that you have an employee object which has a name object as the property:

let employee = {
id: 1001,
name: {
rstName: 'John',
lastName: 'Doe'
}
};

The following statement destructures the properties of the nested name object into individual
variables:

let {
name: {
rstName,
lastName
}
} = employee;

console.log( rstName); // John


console.log(lastName); // Doe

It’s possible to do multiple assignement of a property to multiple variables:

let employee = {
id: 1001,
name: {
rstName: 'John',
lastName: 'Doe'
}
};

let {
name: {
rstName,
lastName
},
name
} = employee;

console.log( rstName); // John


console.log(lastName); // Doe
console.log(name); // { rstName: 'John', lastName: 'Doe' }
fi
fi
fi
fi
fi
fi
fi
Destructuring function arguments
Suppose you have a function that displays the person object:

let display = (person) => console.log(`${person. rstName} ${person.lastName}`);

let person = {
rstName: 'John',
lastName: 'Doe'
};

display(person);

It’s possible to destructure the object argument passed into the function like this:

let display = ({ rstName, lastName}) => console.log(`${ rstName} ${lastName}`);

let person = {
rstName: 'John',
lastName: 'Doe'
};

display(person);

It looks less verbose especially when you use many properties of the argument object. This
technique is often used in React.

Summary
• Object destructuring assigns the properties of an object to variables with the same
names by default.
fi
fi
fi
fi
fi
Section 3. ES6 Modules

ES6 Modules
Summary: in this tutorial, you will learn about ES6 modules and how to export variables,
functions, and classes from a module, and reuse them in other modules.

An ES6 module is a JavaScript le that executes in strict mode only. It means that
any variables or functions declared in the module won’t be added automatically to the global
scope.

Executing modules on web browsers


First, create a new le called message.js and add the following code:

export let message = 'ES6 Modules';

The message.js is a module in ES6 that contains the message variable.


The export statement exposes the message variable to other modules.

Second, create another new le named app.js that uses the message.js module.
The app.js module creates a new heading 1 (h1) element and attaches it to an HTML page.
The import statement imports the message variable from the message.js module.

import { message } from './message.js'

const h1 = document.createElement('h1');
h1.textContent = message

document.body.appendChild(h1)

Third, create a new HTML page that uses the app.js module:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ES6 Modules</title>
</head>
<body>
<script type="module" src="./app.js"></script>
</body>
</html>
fi
fi
fi
Note that we used the type="module" in the <script> tag to load the app.js module. If you
view the page on a web browser, you will see the following page:

- ES6 Module
Let’s examine the export and import statements in more detail.

Exporting
To export a variable, a function, or a class, you place the export keyword in front of it as
follows:

// log.js
export let message = 'Hi';

export function getMessage() {


return message;
}

export function setMessage(msg) {


message = msg;
}

export class Logger {


}

In this example, we have the log.js module with a variable, two functions, and one class. We
used the export keyword to exports all identi ers in the module.

Note that the export keyword requires the function or class to have a name to be exported.
You can’t export an anonymous function or class using this syntax.

JavaScript allows you to de ne a variable, a function, or a class rst and then export it later
as follows:

// foo.js
function foo() {
console.log('foo');
}

function bar() {
console.log('bar');
}
export foo;
fi
fi
fi
In this example, we de ned the foo() function rst and then exported it. Since we didn’t export
the bar() function, we couldn’t access it in other modules. The bar() function is inaccessible
outside the module or we say it is private.

Importing
Once you de ne a module with exports, you can access the exported variables, functions,
and classes in another module by using the import keyword. The following illustrates the
syntax:

import { what, ever } from './other_module.js';

In this syntax:

• First, specify what to import inside the curly braces, which are called bindings.
• Then, specify the module from which you import the given bindings.

Note that when you import a binding from a module, the binding behaves like it was
de ned using const. It means you can’t have another identi er with the same name or
change the value of the binding.

See the following example:

// greeting.js
export let message = 'Hi';

export function setMessage(msg) {


message = msg;
}

When you import the message variable and setMessage() function, you can use
the setMessage() function to change the value of the message variable as shown below:

// app.js
import {message, setMessage } from './greeting.js';
console.log(message); // 'Hi'

setMessage('Hello');
console.log(message); // 'Hello'
fi
fi
fi
fi
fi
However, you can’t change the value of the message variable directly. The following
expression causes an error:

message = 'Hallo'; // error

Behind the scenes, when you called the setMessage() function. JavaScript went back to
the greeting.js module and executed the code in there and changed the message variable.
The change was then automatically re ected on the imported message binding.

The message binding in the app.js is the local name for exported message identi er. So
basically the message variables in the app.js and greeting.js modules aren’t the same.

Import a single binding


Suppose you have a module with the foo variable as follows:

// foo.js
export foo = 10;

Then, in another module, you can reuse the foo variable:

// app.js
import { foo } from './foo.js';
console.log(foo); // 10;

However, you can’t change the value of foo. If you attempt to do so, you will get an error:

foo = 20; // throws an error


fl
fi
Import multiple bindings
Suppose you have the cal.js module as follows:

// cal.js
export let a = 10,
b = 20,
result = 0;

export function sum() {


result = a + b;
return result;
}

export function multiply() {


result = a * b;
return result;
}

And you want to import these bindings from the cal.js, you can explicitly list them as follows:

import {a, b, result, sum, multiply } from './cal.js';


sum();
console.log(result); // 30

multiply();
console.log(result); // 200

Import an entire module as an object


To import everything from a module as a single object, you use the asterisk (*) pattern as
follows:

import * as cal from './cal.js';

In this example, we imported all bindings from the cal.js module as the cal object. In this case,
all the bindings become properties of the cal object, so you can access them as shown
below:

cal.a;
cal.b;
cal.sum();
This import is called namespace import.

It’s important to keep in mind that the imported module executes only once even if import
multiple times. Consider this example:

import { a } from './cal.js';


import { b } from './cal.js';
import {result} from './cal.js';

After the rst import statement, the cal.js module is executed and loaded into the memory,
and it is reused whenever it is referenced by the subsequent import statement.

Limitation of import and export statements


Note that you must use the import or export statement outside other statements and
functions. The following example causes a SyntaxError:

if( requiredSum ) {
export sum;
}

Because we used the export statement inside the if statement. Similarly, the
following import statement also causes a SyntaxError:

function importSum() {
import {sum} from './cal.js';
}

Because we used the import statement inside a function.

The reason for the error is that JavaScript must statically determine what will be exported
and imported.

Note that ES2020 introduced the function-like object import() that allows you to
dynamically import a module.
fi
Aliasing
JavaScript allows you to create aliases for variables, functions, or classes when you export
and import. See the following math.js module:

// math.js
function add( a, b ) {
return a + b;
}

export { add as sum };

In this example, instead of exporting the add() function, we used the as keyword to assign
the sum() function an alias.

So when you import the add() function from the math.js module, you must use sum instead:

import { sum } from './math.js';

If you want to use a different name when you import, you can use the as keyword as follows:

import {sum as total} from './math.js';

Re-exporting a binding
It’s possible to export bindings that you have imported. This is called re-exporting. For example:

import { sum } from './math.js';


export { sum };

In this example, we imported sum from the math.js module and re-export it. The following
statement is equivalent to the statements above:

export {sum} from './math.js';

In case you want to rename the bindings before re-exporting, you use the as keyword. The
following example of imports sum from the math.js module and re-export it as add.

export { sum as add } from './math.js';

If you want to export all the bindings from another module, you can use the asterisk (*):

export * from './cal.js';


Importing without bindings
Sometimes, you want to develop a module that doesn’t export anything, for example, you
may want to add a new method to a built-in object such as the Array.

// array.js
if (!Array.prototype.contain) {
Array.prototype.contain = function(e) {
// contain implementation
// ...
}
}

Now, you can import the module without any binding and use the contain() method de ned in
the array.js module as follows:

import './array.js';
[1,2,3].contain(2); // true

Default exports
A module can have one and only one default export. The default export is easier to import.
The default for a module can be a variable, a function, or a class.

The following is the sort.js module with a default export.

// sort.js
export default function(arr) {
// sorting here
}

Note that you don’t need to specify the name for the function because the module represents
the function name.

import sort from sort.js;


sort([2,1,3]);

As you see, the sort identi er represents the default function of the sort.js module. Notice
that we didn’t use the curly brace {} surrounding the sort identi er.
fi
fi
fi
Let’s change the sort.js module to include the default export as well as the non-default one:

// sort.js
export default function(arr) {
// sorting here
}
export function heapSort(arr) {
// heapsort
}

To import both default and non-default bindings, you specify a list of bindings after
the import keyword with the following rules:

• The default binding must come rst.


• The non-default binding must be surrounded by curly braces.
See the following example:

import sort, {heapSort} from './sort.js';


sort([2,1,3]);
heapSort([3,1,2]);

To rename the default export, you also use the as keyword as follows:

import { default as quicksort, heapSort} from './sort.js';

In this tutorial, you have learned about ES6 modules and how to export bindings from a
module and import them into another module.
fi
Section 4. ES6 Classes

Class
Summary: in this tutorial, you’ll learn about the JavaScript class and how to use it effectively.

A JavaScript class is a blueprint for creating objects. A class encapsulates data and functions
that manipulate data.

Unlike other programming languages such as Java and C#, JavaScript classes are syntactic
sugar over the prototypal inheritance. In other words, ES6 classes are just special functions.

Classes prior to ES6 revisited


Prior to ES6, JavaScript had no concepts of classes. To mimic a class, you often use
the constructor/prototype pattern as shown in the following example:

function Person(name) {
this.name = name;
}

Person.prototype.getName = function () {
return this.name;
};

var john = new Person("John Doe");


console.log(john.getName());

Output:

John Doe

First, create the Person as a constructor function that has a property name called name.
The getName() function is assigned to the prototype so that it can be shared by all instances
of the Person type.

Then, create a new instance of the Person type using the new operator. The john object,
hence, is an instance of the Person and Object through prototypal inheritance.

The following statements use the instanceof operator to check if john is an instance of
the Person and Object type:

console.log(john instanceof Person); // true


console.log(john instanceof Object); // true
ES6 class declaration
ES6 introduced a new syntax for declaring a class as shown in this example:

class Person {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}

This Person class behaves like the Person type in the previous example. However,
instead of using a constructor/prototype pattern, it uses the class keyword.

In the Person class, the constructor() is where you can initialize the properties of
an instance. JavaScript automatically calls the constructor() method when you
instantiate an object of the class.

The following creates a new Person object, which will automatically call
the constructor() of the Person class:

let john = new Person("John Doe");

The getName() is called a method of the Person class. Like a constructor function, you can
call the methods of a class using the following syntax:

objectName.methodName(args)

For example:

let name = john.getName();


console.log(name); // "John Doe"

To verify the fact that classes are special functions, you can use the typeof operator of to
check the type of the Person class.

console.log(typeof Person); // function

It returns function as expected.

The john object is also an instance of the Person and Object types:

console.log(john instanceof Person); // true


console.log(john instanceof Object); // true
Class vs. Custom type
Despite the similarities between a class and a custom type de ned via a constructor function,
there are some important differences.

First, class declarations are not hoisted like function declarations.

For example, if you place the following code above the Person class declaration section, you
will get a ReferenceError.

let john = new Person("John Doe");

Error:

Uncaught ReferenceError: Person is not de ned

Second, all the code inside a class automatically executes in the strict mode. And you cannot
change this behavior.

Third, class methods are non-enumerable. If you use a constructor/prototype pattern, you
have to use the Object.de neProperty() method to make a property non-enumerable.

Finally, calling the class constructor without the new operator will result in an error as shown
in the following example.

let john = Person("John Doe");

Error:

Uncaught TypeError: Class constructor Person cannot be invoked without 'new'

Note that it’s possible to call the constructor function without the new operator. In this
case, the constructor function behaves like a regular function.

Summary.
• Use the JavaScript class keyword to declare a new class.
• A class declaration is syntactic sugar over prototypal inheritance with
additional enhancements.
fi
fi
fi
Getters and Setters
Summary: in this tutorial, you will learn about JavaScript getters and setters and how to use
them effectively.

Introduction to the JavaScript getters and setters


The following example de nes a class called Person:

class Person {
constructor(name) {
this.name = name;
}
}

let person = new Person("John");


console.log(person.name); // John

The Person class has a property name and a constructor. The constructor initializes
the name property to a string.

Sometimes, you don’t want the name property to be accessed directly like this:

person.name

To do that, you may come up with a pair of methods that manipulate the name property. For
example:

class Person {
constructor(name) {
this.setName(name);
}
getName() {
return this.name;
}
setName(newName) {
newName = newName.trim();
if (newName === '') {
throw 'The name cannot be empty';
}
this.name = newName;
}
}

let person = new Person('Jane Doe');


console.log(person); // Jane Doe

person.setName('Jane Smith');
console.log(person.getName()); // Jane Smith
fi
In this example, the Person class has the name property. Also, it has two additional
methods getName() and setName().

The getName() method returns the value of the name property.

The setName() method assigns an argument to the name property. The setName() removes
the whitespaces from both ends of the newName argument and throws an exception if
the newName is empty.

The constructor() calls the setName() method to initialize the name property:

constructor(name) {
this.setName(name);
}

The getName() and setName() methods are known as getter and setter in other
programming languages such as Java and C++.

ES6 provides speci c syntax for de ning the getter and setter using the get and set
keywords. For example:

class Person {
constructor(name) {
this.name = name;
}
get name() {
return this._name;
}
set name(newName) {
newName = newName.trim();
if (newName === '') {
throw 'The name cannot be empty';
}
this._name = newName;
}
}

How it works.

First, the name property is changed to _name to avoid the name collision with the getter and
setter.

Second, the getter uses the get keyword followed by the method name:

get name() {
return this._name;
}
fi
fi
To call the getter, you use the following syntax:

let name = person.name;

When JavaScript sees the access to name property of the Person class, it checks if
the Person class has any name property.

If not, JavaScript checks if the Person class has any method that binds to the name property.
In this example, the name() method binds to the name property via the get keyword. Once
JavaScript nds the getter method, it executes the getter method and returns a value.

Third, the setter uses the set keyword followed by the method name:

set name(newName) {
newName = newName.trim();
if (newName === '') {
throw 'The name cannot be empty';
}
this._name = newName;
}

JavaScript will call the name() setter when you assign a value to the name property like this:

person.name = 'Jane Smith';

If a class has only a getter but not a setter and you attempt to use the setter, the change
won’t take any effect. See the following example:

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

let person = new Person("Jane Doe");


console.log(person.name);

// attempt to change the name, but cannot


person.name = 'Jane Smith';
console.log(person.name); // Jane Doe
fi
In this example, the Person class has the name getter but not the name setter. It attempts to
call the setter. However, the change doesn’t take effect since the Person class doesn’t have
the name setter.

Using getter in an object literal


The following example de nes a getter called latest to return the latest attendee of
the meeting object:

let meeting = {
attendees: [],
add(attendee) {
console.log(`${attendee} joined the meeting.`);
this.attendees.push(attendee);
return this;
},
get latest() {
let count = this.attendees.length;
return count == 0 ? unde ned : this.attendees[count - 1];
}
};

meeting.add('John').add('Jane').add('Peter');
console.log(`The latest attendee is ${meeting.latest}.`);

Output:

John joined a meeting.


Jane joined a meeting.
Peter joined a meeting.
The latest attendee is Peter.

Summary
• Use the get and set keywords to de ne the JavaScript getters and setters for a class or
an object.
• The get keyword binds an object property to a method that will be invoked when that
property is looked up.
• The set keyword binds an object property to a method that will be invoked when that
property is assigned.
fi
fi
fi
Class Expressions
Summary: in this tutorial, you’ll learn how to use JavaScript class expressions to declare
new classes.

Introduction to JavaScript class expressions


Similar to functions, classes have expression forms. A class expression provides you with an
alternative way to de ne a new class.

A class expression doesn’t require an identi er after the class keyword. And you can use a
class expression in a variable declaration and pass it into a function as an argument.

For example, the following de nes a class expression:

let Person = class {


constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}

How it works.

On the left side of the expression is the Person variable. It’s assigned to a class expression.

The class expression starts with the keyword class followed by the class de nition.

A class expression may have a name or not. In this example, we have an unnamed class
expression.

If a class expression has a name, its name can be local to the class body.

The following creates an instance of the Person class expression. Its syntax is the same as if
it were a class declaration.

let person = new Person('John Doe');

Like a class declaration, the type of a class expression is also a function:

console.log(typeof Person); // function

Similar to function expressions, class expressions are not hoisted. It means that you cannot
create an instance of the class before de ning the class expression.
fi
fi
fi
fi
fi
First-class citizen
JavaScript classes are rst-class citizens. It means that you can pass a class into a function,
return it from a function, and assign it to a variable.

See the following example:

function factory(aClass) {
return new aClass();
}

let greeting = factory(class {


sayHi() { console.log('Hi'); }
});

greeting.sayHi(); // 'Hi'

How it works.

First, de ne a factory() function that takes a class expression as an argument and return the
instance of the class:

function factory(aClass) {
return new aClass();
}

Second, pass an unnamed class expression to the factory() function and assign its result to
the greeting variable:

let greeting = factory(class {


sayHi() { console.log('Hi'); }
});

The class expression has a method called sayHi(). And the greeting variable is an instance of
the class expression.

Third, call the sayHi() method on the greeting object:

greeting.sayHi(); // 'Hi'
fi
fi
Singleton
Singleton is a design pattern that limits the instantiation of a class to a single instance. It
ensures that only one instance of a class can be created throughout the system.

Class expressions can be used to create a singleton by calling the class constructor
immediately.

To do that, you use the new operator with a class expression and include the parentheses at
the end of class declaration as shown in the following example:

let app = new class {


constructor(name) {
this.name = name;
}
start() {
console.log(`Starting the ${this.name}...`);
}
}('Awesome App');

app.start(); // Starting the Awesome App...

How it works.

The following is an unnamed class expresion:

new class {
constructor(name) {
this.name = name;
}
start() {
console.log(`Starting the ${this.name}...`);
}
}

The class has a constructor() that accepts an argument. It aslo has a method called start().

The class expression evaluates to a class. Therefore, you can call its constructor
immediately by placing parentheses after the expression:

new class {
constructor(name) {
this.name = name;
}
start() {
console.log(`Starting the ${this.name}...`);
}
}('Awesome App')
This expression returns an instance of the class expression which is assigned to the app
variable.

The following calls the start() method on the app object:

app.start(); // Starting the Awesome App...

Summary
• ES6 provides you with an alternative way to de ning a new class using a class
expression.
• Class expressions can be named or unnamed.
• The class expression can be used to create a singleton object.
fi
Static Methods
Summary: in this tutorial, you’ll learn about the JavaScript static methods and how to use
them effectively.

Introduction to the JavaScript static methods


By de nition, static methods are bound to a class, not the instances of that class. Therefore,
static methods are useful for de ning helper or utility methods.

To de ne a static method before ES6, you add it directly to the constructor of the class. For
example, suppose you have a Person type as follows:

function Person(name) {
this.name = name;
}

Person.prototype.getName = function () {
return this.name;
};

The following adds a static method called createAnonymous() to the Person type:

Person.createAnonymous = function (gender) {


let name = gender == "male" ? "John Doe" : "Jane Doe";
return new Person(name);
};

The createAnonymous() method is considered a static method because it doesn’t depend on


any instance of the Person type for its property values.

To call the createAnonymous() method, you use the Person type instead of its instances:

var anonymous = Person.createAnonymous();


fi
fi
fi
JavaScript static methods in ES6
In ES6, you de ne static methods using the static keyword. The following example de nes a
static method called createAnonymous() for the Person class:

class Person {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
static createAnonymous(gender) {
let name = gender == "male" ? "John Doe" : "Jane Doe";
return new Person(name);
}
}

To invoke the static method, you use the following syntax:

let anonymous = Person.createAnonymous("male");

If you attempt to call the static method from an instance of the class, you’ll get an error. For
example:

let person = new Person('James Doe');


let anonymous = person.createAnonymous("male");

Error:

TypeError: person.createAnonymous is not a function


fi
fi
Calling a static method from the class constructor or an instance method
To call a static method from a class constructor or an instance method, you use the class
name, followed by the . and the static method:

className.staticMethodName();

Alternatively, you can use the following syntax:

this.constructor.staticMethodName();

Summary
• JavaScript static methods are shared among instances of a class. Therefore, they
are bound to the class.
• Call the static methods via the class name, not the instances of that class.
• Use the className.staticMethodName() or this.constructor.staticMethodName() to
call a static method in a class constructor or an instance method.
Static Properties
Summary: in this tutorial, you’ll learn about the JavaScript static properties of a class and
how to access the static properties in a static method, class constructor, and other instance
methods.

Introduction to the JavaScript static properties


Like a static method, a static property is shared by all instances of a class. To de ne static
property, you use the static keyword followed by the property name like this:

class Item {
static count = 0;
}

To access a static property, you use the class name followed by the . operator and the static
property name. For example:

console.log(Item.count); // 0

To access the static property in a static method, you use the class name followed by
the . operator and the static property name. For example:

class Item {
static count = 0;
static getCount() {
return Item.count;
}
}

console.log(Item.getCount()); // 0

To access a static property in a class constructor or instance method, you use the following
syntax:

className.staticPropertyName;

Or

this.constructor.staticPropertyName;
fi
The following example increases the count static property in the class constructor:

class Item {
constructor(name, quantity) {
this.name = name;
this.quantity = quantity;
this.constructor.count++;
}
static count = 0;
static getCount() {
return Item.count;
}
}

When you create a new instance of the Item class, the following statement increases
the count static property by one:

this.constructor.count++;

For example:

// Item class ...

let pen = new Item("Pen", 5);


let notebook = new Item("notebook", 10);

console.log(Item.getCount()); // 2

This example creates two instances of the Item class, which calls the class constructor. Since
the class constructor increases the count property by one each time it’s called, the value of
the count is two.

Put it all together.

class Item {
constructor(name, quantity) {
this.name = name;
this.quantity = quantity;
this.constructor.count++;
}
static count = 0;
static getCount() {
return Item.count;
}
}

let pen = new Item('Pen', 5);


let notebook = new Item('notebook', 10);

console.log(Item.getCount()); // 2
Summary
• A static property of a class is shared by all instances of that class.
• Use the static keyword to de ne a static property.
• Use the className.staticPropertyName to access the static property in a static method.
• Use the this.constructor.staticPropertyName or className.staticPropertyName to
access the static property in a constructor.
fi
Computed Property
Summary: in this tutorial, you’ll learn about the JavaScript computed properties introduced in
ES6.

Introduction to JavaScript Computed Property


ES6 allows you to use an expression in brackets [ ]. It’ll then use the result of the expression
as the property name of an object. For example:

let propName = 'c';

const rank = {
a: 1,
b: 2,
[propName]: 3,
};

console.log(rank.c); // 3

In this example, the [propName] is a computed property of the rank object. The property
name is derived from the value of the propName variable.

When you access c property of the rank object, JavaScript evaluates propName and returns
the property’s value.

Like an object literal, you can use computed properties for getters and setters of a class. For
example:

let name = 'fullName';

class Person {
constructor( rstName, lastName) {
this. rstName = rstName;
this.lastName = lastName;
}
get [name]() {
return `${this. rstName} ${this.lastName}`;
}
}

let person = new Person('John', 'Doe');


console.log(person.fullName);
fi
fi
fi
fi
Output:

John Doe

How it works:

The get[name] is a computed property name of a getter of the Person class. At runtime, when
you access the fullName property, the person object calls the getter and returns the full
name.

Summary
• Computed properties allow you to use the values of expressions as property names of
an object.
Inheritance Using extends & super
Summary: in this tutorial, you will learn how to implement JavaScript inheritance by
using extends and super in ES6.

Implementing JavaScript inheritance using extends and super


Prior to ES6, implementing a proper inheritance required multiple steps. One of the most
commonly used strategies is prototypal inheritance.

The following illustrates how the Bird inherits properties from the Animal using the prototypal
inheritance technique:

function Animal(legs) {
this.legs = legs;
}

Animal.prototype.walk = function() {
console.log('walking on ' + this.legs + ' legs');
}

function Bird(legs) {
Animal.call(this, legs);
}

Bird.prototype = Object.create(Animal.prototype);
Bird.prototype.constructor = Animal;

Bird.prototype. y = function() {
console.log(' ying');
}

var pigeon = new Bird(2);


pigeon.walk(); // walking on 2 legs
pigeon. y(); // ying

ES6 simpli ed these steps by using the extends and super keywords.

The following example de nes the Animal and Bird classes and establishes the inheritance
through the extends and super keywords.
fl
fi
fl
fl
fl
fi
class Animal {
constructor(legs) {
this.legs = legs;
}
walk() {
console.log('walking on ' + this.legs + ' legs');
}
}

class Bird extends Animal {


constructor(legs) {
super(legs);
}
y() {
console.log(' ying');
}
}

let bird = new Bird(2);

bird.walk();
bird. y();

How it works.

First, use the extends keyword to make the Bird class inheriting from the Animal class:

class Bird extends Animal {


// ...
}

The Animal class is called a base class or parent class while the Bird class is known
as a derived class or child class. By doing this, the Bird class inherits all methods and
properties of the Animal class.

Second, in the Bird‘s constructor, call super() to invoke the Animal‘s constructor with
the legs argument.

JavaScript requires the child class to call super() if it has a constructor. As you can see in
the Bird class, the super(legs) is equivalent to the following statement in ES5:

Animal.call(this, legs);
fl
fl
fl
If the Bird class doesn’t have a constructor, you can don’t need to do anything else:

class Bird extends Animal {


y() {
console.log(' ying');
}
}

It is equivalent to the following class:

class Bird extends Animal {


constructor(...args) {
super(...args);
}
y() {
console.log(' ying');
}
}

However, the child class has a constructor, it needs to call super(). For example, the following
code results in an error:

class Bird extends Animal {


constructor(legs) {
}
y() {
console.log(' ying');
}
}

Error:

ReferenceError: Must call super constructor in derived class before accessing 'this' or
returning from derived constructor

Because the super() initializes the this object, you need to call the super() before accessing
the this object. Trying to access this before calling super() also results in an error.

For example, if you want to initialize the color property of the Bird class, you can do it as
follows:
fl
fl
fl
fl
fl
fl
class Bird extends Animal {
constructor(legs, color) {
super(legs);
this.color = color;
}
y() {
console.log(" ying");
}
getColor() {
return this.color;
}
}

let pegion = new Bird(2, "white");


console.log(pegion.getColor());

Shadowing methods
ES6 allows the child class and parent class to have methods with the same name. In this
case, when you call the method of an object of the child class, the method in the child class
will shadow the method in the parent class.

The following Dog class extends the Animal class and rede nes the walk() method:

class Dog extends Animal {


constructor() {
super(4);
}
walk() {
console.log(`go walking`);
}
}

let bingo = new Dog();


bingo.walk(); // go walking

To call the method of the parent class in the child class, you use super.method(arguments) like
this:
fl
fl
fi
class Dog extends Animal {
constructor() {
super(4);
}
walk() {
super.walk();
console.log(`go walking`);
}
}

let bingo = new Dog();


bingo.walk();
// walking on 4 legs
// go walking

Inheriting static members


Besides the properties and methods, the child class also inherits all static properties and
methods of the parent class. For example:

class Animal {
constructor(legs) {
this.legs = legs;
}
walk() {
console.log('walking on ' + this.legs + ' legs');
}
static helloWorld() {
console.log('Hello World');
}
}

class Bird extends Animal {


y() {
console.log(' ying');
}
}

In this example, the Animal class has the helloWorld() static method and this method is
available as Bird.helloWorld() and behaves the same as the Animal.helloWorld() method:

Bird.helloWorld(); // Hello World


fl
fl
Inheriting from built-in types
JavaScript allows you to extend a built-in type such as Array, String, Map, and Set through
inheritance.

The following Queue class extends the Array reference type. The syntax is much cleaner
than the Queue implemented using the constructor/prototype pattern.

class Queue extends Array {


enqueue(e) {
super.push(e);
}
dequeue() {
return super.shift();
}
peek() {
return !this.empty() ? this[0] : unde ned;
}
empty() {
return this.length === 0;
}
}

var customers = new Queue();


customers.enqueue('A');
customers.enqueue('B');
customers.enqueue('C');

while (!customers.empty()) {
console.log(customers.dequeue());
}

Summary
• Use the extends keyword to implement the inheritance in ES6. The class to be extended
is called a base class or parent class. The class that extends the base class or parent
class is called the derived class or child class.
• Call the super(arguments) in the child class’s constructor to invoke the parent class’s
constructor.
• Use super keyword to call methods of the parent class in the methods of the child class.
fi
new.target
Summary: in this tutorial, you learn about the JavaScript new.target metaproperty that
detects whether a function or constructor was called using the new operator.

Introduction to JavaScript new.target


ES6 provides a metaproperty named new.target that allows you to detect whether
a function or constructor was called using the new operator.

The new.target consists of the new keyword, a dot, and target property. The new.target is
available in all functions.

However, in arrow functions, the new.target is the one that belongs to the surrounding
function.

The new.target is very useful to inspect at runtime whether a function is being executed as a
function or as a constructor. It is also handy to determine a speci c derived class that was
called by using the new operator from within a parent class.

JavaScript new.target in functions


Let’s see the following Person constructor function:

function Person(name) {
this.name = name;
}

You can create a new object from the Person function by using the new operator as follows:

let john = new Person('John');


console.log(john.name); // john

Or you can call the Person as a function:

Person('Lily');

Because the this is set to the global object i.e., the window object when you run JavaScript in
the web browser, the name property is added to the window object as follows:

console.log(window.name); //Lily
fi
To help you detect whether a function was called using the new operator, you use
the new.target metaproperty.

In a regular function call, the new.target returns unde ned. If the function was called with
the new operator, the new.target returns a reference to the function.

Suppose you don’t want the Person to be called as a function, you can use the new.target as
follows:

function Person(name) {
if (!new.target) {
throw "must use new operator with Person";
}
this.name = name;
}

Now, the only way to use Person is to instantiate an object from it by using the new operator. If
you try to call it as a regular function, you will get an error.

JavaScript new.target in constructors


In a class constructor, the new.target refers to the constructor that was invoked directly by
the new operator. It is true if the constructor is in the parent class and was delegated from the
constructor of the child class:

class Person {
constructor(name) {
this.name = name;
console.log(new.target.name);
}
}

class Employee extends Person {


constructor(name, title) {
super(name);
this.title = title;
}
}

let john = new Person('John Doe'); // Person


let lily = new Employee('Lily Bush', 'Programmer'); // Employee

In this example, new.target.name is the human-friendly name of the constructor reference of


new.target

In this tutorial, you have learned how to use the JavaScript new.target metaproperty to detect
whether a function or constructor was called using the new operator.
fi
Section 5. Arrow Functions

Arrow Functions
Summary: in this tutorial, you will learn how to use the JavaScript arrow function to write
more concise code for function expressions.

Introduction to JavaScript arrow functions


ES6 arrow functions provide you with an alternative way to write a shorter syntax compared
to the function expression.

The following example de nes a function expression that returns the sum of two numbers:

let add = function (x, y) {


return x + y;
};

console.log(add(10, 20)); // 30

The following example is equivalent to the above add() function expression but use an arrow
function instead:

let add = (x, y) => x + y;

console.log(add(10, 20)); // 30;

In this example, the arrow function has one expression x + y so it returns the result of the
expression.

However, if you use the block syntax, you need to specify the return keyword:

let add = (x, y) => { return x + y; };

The typeof operator returns function indicating the type of arrow function.

console.log(typeof add); // function

The arrow function is also an instance of the Function type as shown in the following
example:

console.log(add instanceof Function); // true


fi
JavaScript arrow functions with multiple parameters
If an arrow function has two or more parameters, you use the following syntax:

(p1, p2, ..., pn) => expression;

The following expression:

=> expression

is equivalent to the following expression:

=> { return expression; }

For example, to sort an array of numbers in the descending order, you use the sort() method
of the array object as follows:

let numbers = [4,2,6];


numbers.sort(function(a,b){
return b - a;
});
console.log(numbers); // [6,4,2]

The code is more concise with the arrow function syntax:

let numbers = [4,2,6];


numbers.sort((a,b) => b - a);
console.log(numbers); // [6,4,2]

JavaScript arrow functions with a single parameter


If an arrow function takes a single parameter, you use the following syntax:

(p1) => { statements }

Note that you can omit the parentheses as follows:

p => { statements }
The following example uses an arrow function as an argument of the map() method that
transforms an array of strings into an array of the string’s lengths.

let names = ['John', 'Mac', 'Peter'];


let lengths = names.map(name => name.length);

console.log(lengths);

Output:

[ 4, 3, 5 ]

JavaScript arrow functions with no parameter


If the arrow function has no parameter, you need to use parentheses, like this:

() => { statements }

For example:

let logDoc = () => console.log(window.document);


logDoc();

Line break between parameter de nition and arrow


JavaScript doesn’t allow you to have a line break between the parameter de nition and the
arrow (=>) in an arrow function.

For example, the following code causes a SyntaxError:

let multiply = (x,y)


=> x * y;

However, the following code works perfectly ne:

let multiply = (x,y) =>


x * y;
fi
fi
fi
JavaScript allows you to have line breaks between parameters as shown in the following
example:

let multiply = (
x,
y
) =>
x * y;

Statements & expressions in the arrow function body


In JavaScript, an expression evaluates to a value as shown in the following example.

10 + 20;

A statement does a speci c task such as:

if (x === y) {
console.log('x equals y');
}

If you use an expression in the body of an arrow function, you don’t need to use the curly
braces.

let square = x => x * x;

However, if you use a statement, you must wrap it inside a pair of curly braces as in the
following example:

let except = msg => {


throw msg;
};
fi
JavaScript arrow functions and object literal
Consider the following example:

let setColor = function (color) {


return {value: color}
};

let backgroundColor = setColor('Red');


console.log(backgroundColor.value); // "Red"

The setColor() function expression returns an object that has the value property set to
the color argument.

If you use the following syntax to return an object literal from an arrow function, you will get
an error.

p => {object:literal}

For example, the following code causes an error.

let setColor = color => {value: color };

Since both block and object literal use curly brackets, the JavasScript engine cannot
distinguish between a block and an object.

To x this, you need to wrap the object literal in parentheses as follows:

let setColor = color => ({value: color });

Arrow function vs. regular function


There are two main differences between an arrow function and a regular function.

1. First, in the arrow function, the this, arguments, super, new.target are lexical. It means
that the arrow function uses these variables (or constructs) from the enclosing lexical
scope.
2. Second, an arrow function cannot be used as a function constructor. If you use
the new keyword to create a new object from an arrow function, you will get an error.
fi
JavaScript arrow functions and this value
In JavaScript, a new function de nes its own this value. However, it is not the case for the
arrow function. See the following example:

function Car() {
this.speed = 0;

this.speedUp = function (speed) {


this.speed = speed;
setTimeout(function () {
console.log(this.speed); // unde ned
}, 1000);

};
}

let car = new Car();


car.speedUp(50);

Inside the anonymous function of the setTimeout() function, the this.speed is unde ned. The
reason is that the this of the anonymous function shadows the this of the speedUp() method.

To x this, you assign the this value to a variable that doesn’t shadow inside the anonymous
function as follows:

function Car() {
this.speed = 0;

this.speedUp = function (speed) {


this.speed = speed;
let self = this;
setTimeout(function () {
console.log(self.speed);
}, 1000);

};
}

let car = new Car();


car.speedUp(50); // 50;

Unlike an anonymous function, an arrow function captures the this value of the enclosing
context instead of creating its own this context. The following code should work as expected:
fi
fi
fi
fi
function Car() {
this.speed = 0;

this.speedUp = function (speed) {


this.speed = speed;
setTimeout(
() => console.log(this.speed),
1000);

};
}

let car = new Car();


car.speedUp(50); // 50;

JavaScript arrow functions and the arguments object


An arrow function doesn’t have the arguments object. For example:

function show() {
return x => x + arguments[0];
}

let display = show(10, 20);


let result = display(5);
console.log(result); // 15

The arrow function inside the showMe() function references the arguments object. However,
this arguments object belongs to the show() function, not the arrow function.

Also, an arrow function doesn’t have the new.target keyword.


JavaScript arrow functions and the prototype property
When you de ne a function using a function keyword, the function has a property
called prototype:

function dump( message ) {


console.log(message);
}
console.log(dump.hasOwnProperty('prototype')); // true

However, arrow functions don’t have the prototype property:

let dump = message => console.log(message);


console.log(dump.hasOwnProperty('prototype')); // false

It is a good practice to use arrow functions for callbacks and closures because the syntax of
arrow functions is cleaner.

Summary
• Use the (...args) => expression; to de ne an arrow function.
• Use the (...args) => { statements } to de ne an arrow function that has multiple
statements.
• An arrow function doesn’t have its binding to this or super.
• An arrow function doesn’t have arguments object, new.target keyword,
and prototype property.
fi
fi
fi
When You Should Not Use Arrow Functions
An arrow function doesn’t have its own this value and the arguments object. Therefore, you
should not use it as an event handler, a method of an object literal, a prototype method, or
when you have a function that uses the arguments object.

1) Event handlers
Suppose that you have the following input text eld:

<input type="text" name="username" id="username" placeholder="Enter a username">

And you want to show a greeting message when users type their usernames. The following
shows the <div> element that will display the greeting message:

<div id="greeting"></div>

Once users type their usernames, you capture the current value of the input and update it to the <div> element:

const greeting = document.querySelector('#greeting');


const username = document.querySelector('#username');
username.addEventListener('keyup', () => {
greeting.textContent = 'Hello ' + this.value;
});

However, when you execute the code, you will get the following message regardless of
whatever you type:

Hello unde ned

It means that the this.value in the event handler always returns unde ned.

As mentioned earlier, the arrow function doesn’t have its own this value. It uses the this value
of the enclosing lexical scope. In the above example, the this in arrow function references the
global object.

In the web browser, the global object is window. The window object doesn’t have
the value property. Therefore, the JavaScript engine adds the value property to
the window object and sets its values to unde ned.

To x this issue, you need to use a regular function instead. The this value will be bound to
the <input> element that triggers the event.

username.addEventListener('keyup', function () {
input.textContent = 'Hello ' + this.value;
});
fi
fi
fi
fi
fi
2) Object methods
See the following counter object:

const counter = {
count: 0,
next: () => ++this.count,
current: () => this.count
};

The counter object has two methods: current() and next(). The current() method returns the
current counter value and the next() method returns the next counter value.

The following shows the next counter value which should be 1:

console.log(counter.next());

However, it returns NaN.

The reason is that when you use the arrow function inside the object, it inherits the this value
from the enclosing lexical scope which is the global scope in this example.

The this.count inside the next() method is equivalent to the window.count (in the web
browser).

The window.count is unde ned by default because the window object doesn’t have
the count property. The next() method adds one to unde ned that results in NaN.

To x this, you use regular functions as the method of an object literal as follows:

const counter = {
count: 0,
next() {
return ++this.count;
},
current() {
return this.count;
}
};

Now, calling the next() method will return one as expected:

console.log(counter.next()); // 1
fi
fi
fi
3) Prototype methods
See the following Counter object that uses the prototype pattern:

function Counter() {
this.count = 0;
}

Counter.prototype.next = () => {
return this.count;
};

Counter.prototype.current = () => {
return ++this.next;
}

The this value in these next() and current() methods reference the global object. Since you
want the this value inside the methods to reference the Counter object, you need to use the
regular functions instead:

function Counter() {
this.count = 0;
}

Counter.prototype.next = function () {
return this.count;
};

Counter.prototype.current = function () {
return ++this.next;
}
4) Functions that use the arguments object
Arrow functions don’t have the arguments object. Therefore, if you have a function that
uses arguments object, you cannot use the arrow function.

For example, the following concat() function won’t work:

const concat = (separator) => {


let args = Array.prototype.slice.call(arguments, 1);
return args.join(separator);
}

Instead, you use a regular function like this:

function concat(separator) {
let args = Array.prototype.slice.call(arguments, 1);
return args.join(separator);
}

Summary
• An arrow function doesn’t have its own this value. Instead, it uses the this value of the
enclosing lexical scope. An arrow function also doesn’t have the arguments object.
• Avoid using the arrow function for event handlers, object methods, prototype methods,
and functions that use the arguments object.
Section 6. Symbol

The Ultimate Guide to JavaScript Symbol


Summary: in this tutorial, you will learn about the JavaScript symbol primitive type and how
to use the symbol effectively.

Creating symbols
ES6 added Symbol as a new primitive type. Unlike other primitive types such
as number, boolean, null, unde ned, and string, the symbol type doesn’t have a literal form.

To create a new symbol, you use the global Symbol() function as shown in this example:

let s = Symbol('foo');

The Symbol() function creates a new unique value each time you call it:

console.log(Symbol() === Symbol()); // false

The Symbol() function accepts a description as an optional argument. The description argument will
make your symbol more descriptive.

The following example creates two symbols: rstName and lastName.

let rstName = Symbol(' rst name'),


lastName = Symbol('last name');

You can access the symbol’s description property using the toString() method.
The console.log() method calls the toString() method of the symbol implicitly as shown in the
following example:

console.log( rstName); // Symbol( rst name)


console.log(lastName); // Symbol(last name)

Since symbols are primitive values, you can use the typeof operator to check whether a
variable is a symbol. ES6 extended typeof to return the symbol string when you pass in a
symbol variable:

console.log(typeof rstName); // symbol


fi
fi
fi
fi
fi
fi
fi
Since a symbol is a primitive value, if you attempt to create a symbol using the new operator,
you will get an error:

let s = new Symbol(); // error

Sharing symbols
ES6 provides you with a global symbol registry that allows you to share symbols globally. If
you want to create a symbol that will be shared, you use the Symbol.for() method instead of
calling the Symbol() function.

The Symbol.for() method accepts a single parameter that can be used for symbol’s
description, as shown in the following example:

let ssn = Symbol.for('ssn');

The Symbol.for() method rst searches for the symbol with the ssn key in the global symbol
registry. It returns the existing symbol if there is one. Otherwise, the Symbol.for() method
creates a new symbol, registers it to the global symbol registry with the speci ed key, and
returns the symbol.

Later, if you call the Symbol.for() method using the same key, the Symbol.for() method will
return the existing symbol.

let citizenID = Symbol.for('ssn');


console.log(ssn === citizenID); // true

In this example, we used the Symbol.for() method to look up the symbol with the ssn key.
Since the global symbol registry already contained it, the Symbol.for() method returned the
existing symbol.

To get the key associated with a symbol, you use the Symbol.keyFor() method as shown in
the following example:

console.log(Symbol.keyFor(citizenID)); // 'ssn'

If a symbol that does not exist in the global symbol registry, the System.keyFor() method
returns unde ned.

let systemID = Symbol('sys');


console.log(Symbol.keyFor(systemID)); // unde ned
fi
fi
fi
fi
Symbol usages
A) Using symbols as unique values
Whenever you use a string or a number in your code, you should use symbols instead. For
example, you have to manage the status in the task management application.

Before ES6, you would use strings such as open, in progress, completed, canceled, and on
hold to represent different statuses of a task. In ES6, you can use symbols as follows:

let statuses = {
OPEN: Symbol('Open'),
IN_PROGRESS: Symbol('In progress'),
COMPLETED: Symbol('Completed'),
HOLD: Symbol('On hold'),
CANCELED: Symbol('Canceled')
};
// complete a task
task.setStatus(statuses.COMPLETED);

B) Using symbol as the computed property name of an object


You can use symbols as computed property names. See the following example:

let status = Symbol('status');


let task = {
[status]: statuses.OPEN,
description: 'Learn ES6 Symbol'
};
console.log(task);

To get all the enumerable properties of an object, you use the Object.keys() method.

console.log(Object.keys(task)); // ["description"]

To get all properties of an object whether the properties are enumerable or not, you use
the Object.getOwnPropertyNames() method.

console.log(Object.getOwnPropertyNames(task)); // ["description"]

To get all property symbols of an object, you use the Object.getOwnPropertySymbols() method,
which has been added in ES6.

console.log(Object.getOwnPropertySymbols(task)); //[Symbol(status)]
The Object.getOwnPropertySymbols() method returns an array of own property symbols from
an object.

Well-known symbols
ES6 provides prede ned symbols which are called well-known symbols. The well-known
symbols represent the common behaviors in JavaScript. Each well-known symbol is a static
property of the Symbol object.

Symbol.hasInstance
The Symbol.hasInstance is a symbol that changes the behavior of the instanceof operator.
Typically, when you use the instanceof operator:

obj instanceof type;

JavaScript will call the Symbol.hasIntance method as follows:

type[Symbol.hasInstance](obj);

It then depends on the method to determine if obj is an instance of the type object. See the
following example.

class Stack {
}
console.log([] instanceof Stack); // false

The [ ] array is not an instance of the Stack class, therefore, the instanceof operator
returns false in this example.

Assuming that you want the [ ] array is an instance of the Stack class, you can add
the Symbol.hasInstance method as follows:

class Stack {
static [Symbol.hasInstance](obj) {
return Array.isArray(obj);
}
}
console.log([] instanceof Stack); // true
fi
Symbol.iterator
The Symbol.iterator speci es whether a function will return an iterator for an object.

The objects that have Symbol.iterator property are called iterable objects.

In ES6, all collection objects (Array, Set and Map) and strings are iterable objects.

ES6 provides the for…of loop that works with the iterable object as in the following example.

var numbers = [1, 2, 3];


for (let num of numbers) {
console.log(num);
}

// 1
// 2
// 3

Internally, JavaScript engine rst calls the Symbol.iterator method of the numbers array to get
the iterator object. Then, it calls the iterator.next() method and copies the value property fo
the iterator object into the num variable. After three iterations, the done property of the result
object is true, the loop exits.

You can access the default iterator object via System.iterator symbol as follows:

var iterator = numbers[Symbol.iterator]();

console.log(iterator.next()); // Object {value: 1, done: false}


console.log(iterator.next()); // Object {value: 2, done: false}
console.log(iterator.next()); // Object {value: 3, done: false}
console.log(iterator.next()); // Object {value: unde ned, done: true}

By default, a collection is not iterable. However, you can make it iterable by using
the Symbol.iterator as shown in the following example:
fi
fi
fi
class List {
constructor() {
this.elements = [];
}

add(element) {
this.elements.push(element);
return this;
}

*[Symbol.iterator]() {
for (let element of this.elements) {
yield element;
}
}
}

let chars = new List();


chars.add('A')
.add('B')
.add('C');

// because of the Symbol.iterator


for (let c of chars) {
console.log(c);
}

// A
// B
// C

Symbol.isConcatSpreadable
To concatenate two arrays, you use the concat() method as shown in the following example:

let odd = [1, 3],


even = [2, 4];
let all = odd.concat(even);
console.log(all); // [1, 3, 2, 4]

In this example, the resulting array contains the single elements of both arrays. In addition,
the concat() method also accepts a non-array argument as illustrated below.

let extras = all.concat(5);


console.log(extras); // [1, 3, 2, 4, 5]
The number 5 becomes the fth element of the array.

As you see in the above example, when we pass an array to the concat() method,
the concat() method spreads the array into individual elements. However, it treats a single
primitive argument differently. Prior to ES6, you could not change this behavior.

This is why the Symbol.isConcatSpreadable symbol comes into play.

The Symbol.isConcatSpreadable property is a Boolean value that determines whether an


object is added individually to the result of the concat() function.

Consider the following example:

let list = {
0: 'JavaScript',
1: 'Symbol',
length: 2
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", Object]

The list object is concatenated to the ['Learning'] array. However, its individual elements are
not spreaded.

To enable the elements of the list object added to the array individually when passing to
the concat() method, you need to add the Symbol.isConcatSpreadable property to
the list object as follows:

let list = {
0: 'JavaScript',
1: 'Symbol',
length: 2,
[Symbol.isConcatSpreadable]: true
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", "JavaScript", "Symbol"]

Note that if you set the value of the Symbol.isConcatSpreadable to false and pass the list object
to the concat() method, it will be concatenated to the array as the whole object.
fi
Symbol.toPrimitive
The Symbol.toPrimitive method determines what should happen when an object is converted
into a primitive value.

The JavaScript engine de nes the Symbol.toPrimitive method on the prototype of each
standard type.

The Symbol.toPrimitive method takes a hint argument which has one of three values:
“number”, “string”, and “default”. The hint argument speci es the type of the return
value. The hint parameter is lled by the JavaScript engine based on the context in which the
object is used.

Here is an example of using the Symbol.toPrimitive method.

function Money(amount, currency) {


this.amount = amount;
this.currency = currency;
}
Money.prototype[Symbol.toPrimitive] = function(hint) {
var result;
switch (hint) {
case 'string':
result = this.amount + this.currency;
break;
case 'number':
result = this.amount;
break;
case 'default':
result = this.amount + this.currency;
break;
}
return result;
}

var price = new Money(799, 'USD');

console.log('Price is ' + price); // Price is 799USD


console.log(+price + 1); // 800
console.log(String(price)); // 799USD

In this tutorial, you have learned about JavaScript symbols and how to use symbols for
unique values and object properties. Also, you learned how to use well-known symbols to
modify object behaviors.
fi
fi
fi
Section 7. Iterators & Generators

Iterators
Summary: in this tutorial, you will learn about JavaScript iterator and how to use iterators to
process a sequence of data more ef ciently.

The for loop issues


When you have an array of data, you typically use a for loop to iterate over its elements. For
example:

let ranks = ['A', 'B', 'C'];

for (let i = 0; i < ranks.length; i++) {


console.log(ranks[i]);
}

The for loop uses the variable i to track the index of the ranks array. The value of
i increments each time the loop executes as long as the value of i is less than the number of
elements in the ranks array.

This code is straightforward. However, its complexity grows when you nest a loop inside
another loop. In addition, keeping track of multiple variables inside the loops is error-prone.

ES6 introduced a new loop construct called for...of to eliminate the standard loop’s
complexity and avoid the errors caused by keeping track of loop indexes.

To iterate over the elements of the ranks array, you use the following for...of construct:

for(let rank of ranks) {


console.log(rank);
}

The for...of is far more elegant than the for loop because it shows the true intent of the code –
iterate over an array to access each element in the sequence.

On top of this, the for...of loop has the ability to create a loop over any iterable object, not
just an array.

To understand the iterable object, you need to understand the iteration protocols rst.
fi
fi
Iteration protocols
There are two iteration protocols: iterable protocol and iterator protocol.

Iterator protocol
An object is an iterator when it implements an interface (or API) that answers two questions:

• Is there any element left?


• If there is, what is the element?
Technically speaking, an object is quali ed as an iterator when it has a next() method that
returns an object with two properties:

• done: a boolean value indicating whether or not there are any more elements that could
be iterated upon.
• value: the current element.
Each time you call the next(), it returns the next value in the collection:

{ value: 'next value', done: false }

If you call the next() method after the last value has been returned, the next() returns the
result object as follows:

{done: true: value: unde ned}

The value of the done property indicates that there is no more value to return and the value of
the property is set to unde ned.

Iterable protocol
An object is iterable when it contains a method called [Symbol.iterator] that takes no
argument and returns an object which conforms to the iterator protocol.

The [Symbol.iterator] is one of the built-in well-known symbols in ES6.

Iterators
Since ES6 provides built-in iterators for the collection types Array, Set, and Map, you don’t
have to create iterators for these objects.

If you have a custom type and want to make it iterable so that you can use the for...of loop
construct, you need to implement the iteration protocols.

The following code creates a Sequence object that returns a list of numbers in the range of
( start, end) with an interval between subsequent numbers.
fi
fi
fi
class Sequence {
constructor( start = 0, end = In nity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
[Symbol.iterator]() {
let counter = 0;
let nextIndex = this.start;
return {
next: () => {
if ( nextIndex <= this.end ) {
let result = { value: nextIndex, done: false }
nextIndex += this.interval;
counter++;
return result;
}
return { value: counter, done: true };
}
}
}
};

The following code uses the Sequence iterator in a for...of loop:

let evenNumbers = new Sequence(2, 10, 2);

for (const num of evenNumbers) {


console.log(num);
}

Output:

2
4
6
8
10

You can explicitly access the [Symbol.iterator]() method as shown in the following script:

let evenNumbers = new Sequence(2, 10, 2);


let iterator = evenNumbers[Symbol.iterator]();

let result = iterator.next();

while( !result.done ) {
console.log(result.value);
result = iterator.next();
}
fi
Cleaning up
In addition to the next() method, the [Symbol.iterator]() may optionally return a method
called return().

The return() method is invoked automatically when the iteration is stopped prematurely. It is
where you can place the code to clean up the resources.

The following example implements the return() method for the Sequence object:

class Sequence {
constructor( start = 0, end = In nity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
[Symbol.iterator]() {
let counter = 0;
let nextIndex = this.start;
return {
next: () => {
if ( nextIndex <= this.end ) {
let result = { value: nextIndex, done: false }
nextIndex += this.interval;
counter++;
return result;
}
return { value: counter, done: true };
},
return: () => {
console.log('cleaning up...');
return { value: unde ned, done: true };
}
}
}
}

The following snippet uses the Sequence object to generate a sequence of odd numbers from
1 to 10. However, it prematurely stops the iteration. As a result, the return() method is
automatically invoked.

let oddNumbers = new Sequence(1, 10, 2);


Output:
for (const num of oddNumbers) {
1
if( num > 7 ) {
3
break;
5
}
7
console.log(num);
cleaning up...
}
fi
fi
Generators
Summary: in this tutorial, you will learn about JavaScript Generators and how to use them
effectively.

Introduction to JavaScript Generators


In JavaScript, a regular function is executed based on the run-to-completion model. It cannot
pause midway and then continues from where it paused. For example:

function foo() {
console.log('I');
console.log('cannot');
console.log('pause');
}

The foo() function executes from top to bottom. The only way to exit the foo() is by returning
from it or throwing an error. If you invoke the foo() function again, it will start the execution
from the top to bottom.

foo();

Output:

I
cannot
pause

ES6 introduces a new kind of function that is different from a regular function: function
generator or generator.

A generator can pause midway and then continues from where it paused. For example:

function* generate() {
console.log('invoked 1st time');
yield 1;
console.log('invoked 2nd time');
yield 2;
}
Let’s examine the generate() function in detail.

• First, you see the asterisk (*) after the function keyword. The asterisk denotes that
the generate() is a generator, not a normal function.
• Second, the yield statement returns a value and pauses the execution of the function.
The following code invokes the generate() generator:

let gen = generate();

When you invoke the generate() generator:

• First, you see nothing in the console. If the generate() were a regular function, you
would expect to see some messages.
• Second, you get something back from generate() as a returned value.
Let’s show the returned value on the console:

console.log(gen);

Output:

Object [Generator] {}

So, a generator returns a Generator object without executing its body when it is invoked.

The Generator object returns another object with two properties: done and value. In other
words, a Generator object is iterable.

The following calls the next() method on the Generator object:

let result = gen.next();


console.log(result);

Output:

invoked 1st time


{ value: 1, done: false }

As you can see, the Generator object executes its body which outputs message 'invoked 1st
time' at line 1 and returns the value 1 at line 2.

The yield statement returns 1 and pauses the generator at line 2.


Similarly, the following code invokes the next() method of the Generator second time:

result = gen.next();
console.log(result);

Output:

invoked 2nd time


{ value: 2, done: false }

This time the Generator resumes its execution from line 3 that outputs the message 'invoked
2nd time' and returns (or yield) 2.

The following invokes the next() method of the generator object a third time:

result = gen.next();
console.log(result);

Output:

{ value: unde ned, done: true }

Since a generator is iterable, you can use the for...of loop:

for (const g of gen) {


console.log(g);
}

Here is the output:

invoked 1st time


1
invoked 2nd time
2
fi
More JavaScript generator examples
The following example illustrates how to use a generator to generate a never-ending
sequence:

function* forever() {
let index = 0;
while (true) {
yield index++;
}
}

let f = forever();
console.log(f.next()); // 0
console.log(f.next()); // 1
console.log(f.next()); // 2

Each time you call the next() method of the forever generator, it returns the next number in
the sequence starting from 0.

Using generators to implement iterators


When you implement an iterator, you have to manually de ne the next() method. In
the next() method, you also have to manually save the state of the current element.

Since generators are iterables, they can help you simplify the code for implementing iterator.

The following is a Sequence iterator created in the iterator tutorial:

class Sequence {
constructor( start = 0, end = In nity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
[Symbol.iterator]() {
let counter = 0;
let nextIndex = this.start;
return {
next: () => {
if ( nextIndex < this.end ) {
let result = { value: nextIndex, done: false }
nextIndex += this.interval;
counter++;
return result;
}
return { value: counter, done: true };
}
}
}
}
fi
fi
And here is the new Sequence iterator that uses a generator:

class Sequence {
constructor( start = 0, end = In nity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
* [Symbol.iterator]() {
for( let index = this.start; index <= this.end; index += this.interval ) {
yield index;
}
}
}

As you can see, the method Symbol.iterator is much simpler by using the generator.

The following script uses the Sequence iterator to generate a sequence of odd numbers from
1 to 10:

let oddNumbers = new Sequence(1, 10, 2);

for (const num of oddNumbers) {


console.log(num);
}

Output:

1
3
5
7
9
fi
Using a generator to implement the Bag data structure
A Bag is a data structure that has the ability to collect elements and iterate through elements.
It doesn’t support removing items.

The following script implements the Bag data structure:

class Bag {
constructor() {
this.elements = [];
}
isEmpty() {
return this.elements.length === 0;
}
add(element) {
this.elements.push(element);
}
* [Symbol.iterator]() {
for (let element of this.elements) {
yield element;
}
}
}

let bag = new Bag();

bag.add(1);
bag.add(2);
bag.add(3);

for (let e of bag) {


console.log(e);
}

Output:

1
2
3

Summary
• Generators are created by the generator function function* f(){}.
• Generators do not execute its body immediately when they are invoked.
• Generators can pause midway and resumes their executions where they were paused.
The yield statement pauses the execution of a generator and returns a value.
• Generators are iterable so you can use them with the for...of loop.
yield
Summary: in this tutorial, you will learn about the JavaScript yield keyword and how to use it
in generator functions.

Introduction to the JavaScript yield keyword


The yield keyword allows you to pause and resume a generator function (function*).

The following shows the syntax of the yield keyword:

[variable_name] = yield [expression];

In this syntax:

• The expression speci es the value to return from a generator function via the iteration
protocol. If you omit the expression, the yield returns unde ned.
• The variable_name stores the optional value passed to the next() method of the iterator
object.

JavaScript yield examples


Let’s take some examples of using the yield keyword.

A) Returning a value
The following trivial example illustrates how to use the yield keyword to return a value from a
generator function:

function* foo() {
yield 1;
yield 2;
yield 3;
}

let f = foo();

console.log(f.next());

Output:

{ value: 1, done: false }


fi
fi
As you can see the value that follows the yield is added to the value property of the return
object when the next() is called:

yield 1;

B) Returning unde ned


This example illustrates how to use the yield keyword to return unde ned:

function* bar() {
yield;
}

let b = bar();
console.log(b.next());

Output:

{ value: unde ned, done: false }

C) Passing a value to the next() method


In the following example, the yield keyword is an expression that evaluates the argument
passed to the next() method:

function* generate() {
let result = yield;
console.log(`result is ${result}`);
}

let g = generate();
console.log(g.next());

console.log(g.next(1000));

The rst call g.next() returns the following object:

{ value: unde ned, done: false }


fi
fi
fi
fi
fi
The second call g.next() carries the following tasks:

• Evaluate yield to 1000.


• Assign result the value of yield, which is 1000.
• Output the message and return the object

Output:

result is 1000
{ value: unde ned, done: true }

D) Using yield in an array


The following example uses the yield keyword as elements of an array:

function* baz() {
let arr = [yield, yield];
console.log(arr);
}

var z = baz();

console.log(z.next());
console.log(z.next(1));
console.log(z.next(2));

The rst call z.next() sets the rst element of the arr array to 1 and returns the following
object:

{ value: unde ned, done: false }

The second call z.next() sets the second of the arr array to 2 and returns the following object:

{ value: unde ned, done: false }

The third call z.next() shows the contents of the arr array and returns the following object:

[ 1, 2 ]
{ value: unde ned, done: true }
fi
fi
fi
fi
fi
fi
E) Using yield to return an array
The following generator function uses the yield keyword to return an array:

function* yieldArray() {
yield 1;
yield [ 20, 30, 40 ];
}

let y = yieldArray();

console.log(y.next());
console.log(y.next());
console.log(y.next());

The rst call y.next() returns the following object:

{ value: 1, done: false }

The second call y.next() returns the following object:

{ value: [ 20, 30, 40 ], done: false }

In this case, yield sets the array [ 20, 30, 40 ] as the value of the value property of the return
object.

The third call y.next() returns the following object:

{ value: unde ned, done: true }


fi
fi
F) Using the yield to return individual elements of an array
See the following generator function:

function* yieldArrayElements() {
yield 1;
yield* [ 20, 30, 40 ];
}

let a = yieldArrayElements();

console.log(a.next()); // { value: 1, done: false }


console.log(a.next()); // { value: 20, done: false }
console.log(a.next()); // { value: 30, done: false }
console.log(a.next()); // { value: 40, done: false }

In this example, yield* is the new syntax. The yield* expression is used to delegate to another
iterable object or generator.

As a result, the following expression returns the individual elements of the array [20, 30, 40]:

yield* [20, 30, 40];

In this tutorial, you have learned about the JavaScript yield keyword and how to use it in
function generators.
Section 8. Promises

Promises
Summary: in this tutorial, you will learn about JavaScript promises and how to use them
effectively.

Why JavaScript promises


The following example de nes a function getUsers() that returns a list of user objects:

function getUsers() {
return [
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
];
}

Each user object has two properties username and email.

To nd a user by username from the user list returned by the getUsers() function, you can
use the ndUser() function as follows:

function ndUser(username) {
const users = getUsers();
const user = users. nd((user) => user.username === username);
return user;
}

In the ndUser() function:

• First, get a user array by calling the getUsers() function


• Second, nd the user with a speci c username by using the nd() method of
the Array object.
• Third, return the matched user.
The following shows the complete code for nding a user with the username 'john':
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
function getUsers() {
return [
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
];
}

function ndUser(username) {
const users = getUsers();
const user = users. nd((user) => user.username === username);
return user;
}

console.log( ndUser('john'));

Output:

{ username: 'john', email: '[email protected]' }

The code in the ndUser() function is synchronous and blocking. The ndUser() function
executes the getUsers() function to get a user array, calls the nd() method on
the users array to search for a user with a speci c username, and returns the matched user.

In practice, the getUsers() function may access a database or call an API to get the user list.
Therefore, the getUsers() function will have a delay.

To simulate the delay, you can use the setTimeout() function. For example:

function getUsers() {
let users = [];

// delay 1 second (1000ms)


setTimeout(() => {
users = [
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
];
}, 1000);

return users;
}
fi
fi
fi
fi
fi
fi
fi
How it works.

• First, de ne an array users and initialize its value with an empty array.
• Second, assign an array of the users to the users variable inside the callback of
the setTimeout() function.
• Third, return the users array
The getUsers() won’t work properly and always returns an empty array. Therefore,
the ndUser() function won’t work as expected:

function getUsers() {
let users = [];
setTimeout(() => {
users = [
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
];
}, 1000);
return users;
}

function ndUser(username) {
const users = getUsers(); // A
const user = users. nd((user) => user.username === username); // B
return user;
}

console.log( ndUser('john'));

Output:

unde ned

Because the getUsers() returns an empty array, the users array is empty (line A). When
calling the nd() method on the users array, the method returns unde ned (line B)

The challenge is how to access the users returned from the getUsers() function after one
second. One classical approach is to use the callback.
fi
fi
fi
fi
fi
fi
fi
fi
Using callbacks to deal with an asynchronous operation
The following example adds a callback argument to the getUsers() and ndUser() functions:

function getUsers(callback) {
setTimeout(() => {
callback([
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
]);
}, 1000);
}

function ndUser(username, callback) {


getUsers((users) => {
const user = users. nd((user) => user.username === username);
callback(user);
});
}

ndUser('john', console.log);

Output:

{ username: 'john', email: '[email protected]' }

In this example, the getUsers() function accepts a callback function as an argument and
invokes it with the users array inside the setTimeout() function. Also, the ndUser() function
accepts a callback function that processes the matched user.

The callback approach works very well. However, it makes the code more dif cult to follow.
Also, it adds complexity to the functions with callback arguments.

If the number of functions grows, you may end up with the callback hell problem. To resolve
this, JavaScript comes up with the concept of promises.
fi
fi
fi
fi
fi
fi
Understanding JavaScript Promises
By de nition, a promise is an object that encapsulates the result of an asynchronous
operation.

A promise object has a state that can be one of the following:

• Pending
• Ful lled with a value
• Rejected for a reason
In the beginning, the state of a promise is pending, indicating that the asynchronous
operation is in progress. Depending on the result of the asynchronous operation, the state
changes to either ful lled or rejected.

The ful lled state indicates that the asynchronous operation was completed successfully:

The rejected state indicates that the asynchronous operation failed.


fi
fi
fi
fi
Creating a promise
To create a promise object, you use the Promise() constructor:

const promise = new Promise((resolve, reject) => {


// contain an operation
// ...

// return the state


if (success) {
resolve(value);
} else {
reject(error);
}
});

The promise constructor accepts a callback function that typically performs an asynchronous
operation. This function is often referred to as an executor.

In turn, the executor accepts two callback functions with the name resolve and reject.

Note that the callback functions passed into the executor are resolve and reject by
convention only.

If the asynchronous operation completes successfully, the executor will call


the resolve() function to change the state of the promise from pending to ful lled with a value.

In case of an error, the executor will call the reject() function to change the state of the
promise from pending to rejected with the error reason.

Once a promise reaches either ful lled or rejected state, it stays in that state and can’t go to
another state.

In other words, a promise cannot go from the ful lled state to the rejected state and vice
versa. Also, it cannot go back from the ful lled or rejected state to the pending state.

Once a new Promise object is created, its state is pending. If a promise


reaches ful lled or rejected state, it is resolved.
fi
fi
fi
fi
fi
Note that you will rarely create promise objects in practice. Instead, you will consume
promises provided by libraries.

Consuming a Promise: then, catch, nally


1) The then() method
To get the value of a promise when it’s ful lled, you call the then() method of the promise
object. The following shows the syntax of the then() method:

promise.then(onFul lled,onRejected);

The then() method accepts two callback functions: onFul lled and onRejected.

The then() method calls the onFul lled() with a value, if the promise is ful lled or
the onRejected() with an error if the promise is rejected.

Note that both onFul lled and onRejected arguments are optional.

The following example shows how to use then() method of the Promise object returned by
the getUsers() function:

function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
]);
}, 1000);
});
}

function onFul lled(users) {


console.log(users);
}

const promise = getUsers();


promise.then(onFul lled);
fi
fi
fi
fi
fi
fi
fi
fi
fi
Output:

[
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' }
]

In this example:

• First, de ne the onFul lled() function to be called when the promise is ful lled.
• Second, call the getUsers() function to get a promise object.
• Third, call the then() method of the promise object and output the user list to the
console.
To make the code more concise, you can use an arrow function as the argument of
the then() method like this:

function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
]);
}, 1000);
});
}

const promise = getUsers();

promise.then((users) => {
console.log(users);
});

Because the getUsers() function returns a promise object, you can chain the function call with
the then() method like this:

// getUsers() function
//...

getUsers().then((users) => {
console.log(users);
});
fi
fi
fi
In this example, the getUsers() function always succeeds. To simulate the error, we can use
a success ag like the following:

let success = true;

function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (success) {
resolve([
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
]);
} else {
reject('Failed to the user list');
}
}, 1000);
});
}

function onFul lled(users) {


console.log(users);
}
function onRejected(error) {
console.log(error);
}

const promise = getUsers();


promise.then(onFul lled, onRejected);

How it works.

First, de ne the success variable and initialize its value to true.

If the success is true, the promise in the getUsers() function is ful lled with a user list.
Otherwise, it is rejected with an error message.

Second, de ne the onFul lled and onRejected functions.

Third, get the promise from the getUsers() function and call the then() method with
the onFul lled and onRejected functions.

The following shows how to use the arrow functions as the arguments of the then() method:
fi
fi
fl
fi
fi
fi
fi
fi
// getUsers() function
// ...

const promise = getUsers();


promise.then(
(users) => console.log,
(error) => console.log
);

2) The catch() method


If you want to get the error only when the state of the promise is rejected, you can use
the catch() method of the Promise object:

promise.catch(onRejected);

Internally, the catch() method invokes the then(unde ned, onRejected) method.

The following example changes the success ag to false to simulate the error scenario:

let success = false;

function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (success) {
resolve([
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
]);
} else {
reject('Failed to the user list');
}
}, 1000);
});
}

const promise = getUsers();

promise.catch((error) => {
console.log(error);
});
fl
fi
3) The nally() method
Sometimes, you want to execute the same piece of code whether the promise is ful lled or
rejected. For example:

const render = () => {


//...
};

getUsers()
.then((users) => {
console.log(users);
render();
})
.catch((error) => {
console.log(error);
render();
});

As you can see, the render() function call is duplicated in both then() and catch() methods.

To remove this duplicate and execute the render() whether the promise is ful lled or rejected,
you use the nally() method, like this:

const render = () => {


//...
};

getUsers()
.then((users) => {
console.log(users);
})
.catch((error) => {
console.log(error);
})
. nally(() => {
render();
});
fi
fi
fi
fi
fi
A practical JavaScript Promise example
The following example shows how to load a JSON le from the server and display its
contents on a webpage.

Suppose you have the following JSON le:

https://www.javascripttutorial.net/sample/promise/api.json

with the following contents:

{
"message": "JavaScript Promise Demo"
}

The following shows the HTML page that contains a button. When you click the button, the
page loads data from the JSON le and shows the message:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JavaScript Promise Demo</title>
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<div id="container">
<div id="message"></div>
<button id="btnGet">Get Message</button>
</div>
<script src="js/promise-demo.js">
</script>
</body>
</html>
fi
fi
fi
The following shows the promise-demo.js le:

function load(url) {
return new Promise(function (resolve, reject) {
const request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (this.readyState === 4 && this.status == 200) {
resolve(this.response);
} else {
reject(this.status);
}
};
request.open('GET', url, true);
request.send();
});
}

const url = 'https://www.javascripttutorial.net/sample/promise/api.json';


const btn = document.querySelector('#btnGet');
const msg = document.querySelector('#message');

btn.addEventListener('click', () => {
load(URL)
.then((response) => {
const result = JSON.parse(response);
msg.innerHTML = result.message;
})
.catch((error) => {
msg.innerHTML = `Error getting the message, HTTP status: ${error}`;
});
});

How it works.

First, de ne the load() function that uses the XMLHttpRequest object to load the JSON le
from the server:

function load(url) {
return new Promise(function (resolve, reject) {
const request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (this.readyState === 4 && this.status == 200) {
resolve(this.response);
} else {
reject(this.status);
}
};
request.open('GET', url, true);
request.send();
});
}
fi
fi
fi
In the executor, we call resolve() function with the Response if the HTTP status code is 200.
Otherwise, we invoke the reject() function with the HTTP status code.

Second, register the button click event listener and call the then() method of the promise
object. If the load is successful, then we show the message returned from the server.
Otherwise, we show the error message with the HTTP status code.

const url = 'https://www.javascripttutorial.net/sample/promise/api.json';


const btn = document.querySelector('#btnGet');
const msg = document.querySelector('#message');

btn.addEventListener('click', () => {
load(URL)
.then((response) => {
const result = JSON.parse(response);
msg.innerHTML = result.message;
})
.catch((error) => {
msg.innerHTML = `Error getting the message, HTTP status: ${error}`;
});
});

Summary
• A promise is an object that encapsulates the result of an asynchronous operation.
• A promise starts in the pending state and ends in either ful lled state or rejected state.
• Use then() method to schedule a callback to be executed when the promise is ful lled,
and catch() method to schedule a callback to be invoked when the promise is rejected.
• Place the code that you want to execute in the nally() method whether the promise is
ful lled or rejected.
fi
fi
fi
fi
Promise Chaining
Summary: in this tutorial, you will learn about the JavaScript promise chaining pattern that
chains the promises to execute asynchronous operations in sequence.

Note that ES2017 introduced the async/await that helps you write the code that is
cleaner than using the promise chaining technique.

Introduction to the JavaScript promise chaining


Sometimes, you want to execute two or more related asynchronous operations, where the
next operation starts with the result from the previous step. For example:

First, create a new promise that resolves to the number 10 after 3 seconds:

let p = new Promise((resolve, reject) => {


setTimeout(() => {
resolve(10);
}, 3 * 100);
});

Note that the setTimeout() function simulates an asynchronous operation.

Message Message
Then, invoke the then() method of the promise:

p.then((result) => {
console.log(result);
return result * 2;
});

The callback passed to the then() method executes once the promise is resolved. In the
callback, we show the result of the promise and return a new value multiplied by two
(result*2).
Because the then() method returns a new Promise with a value resolved to a value, you can
call the then() method on the return Promise like this:

let p = new Promise((resolve, reject) => {


setTimeout(() => {
resolve(10);
}, 3 * 100);
});

p.then((result) => {
console.log(result);
return result * 2;
}).then((result) => {
console.log(result);
return result * 3;
});

Output:

10
20

In this example, the return value in the rst then() method is passed to the
second then() method. You can keep calling the then() method successively as follows:

let p = new Promise((resolve, reject) => {


setTimeout(() => {
resolve(10);
}, 3 * 100);
});

p.then((result) => {
console.log(result); // 10
return result * 2;
}).then((result) => {
console.log(result); // 20
return result * 3;
}).then((result) => {
console.log(result); // 60
return result * 4;
});

Output:

10
20
60
fi
The way we call the then() methods like this is often referred to as a promise chain.

The following picture illustrates the promise chain:


Multiple handlers for a promise
When you call the then() method multiple times on a promise, it is not the promise chaining.
For example:

let p = new Promise((resolve, reject) => {


setTimeout(() => {
resolve(10);
}, 3 * 100);
});

p.then((result) => {
console.log(result); // 10
return result * 2;
})

p.then((result) => {
console.log(result); // 10
return result * 3;
})

p.then((result) => {
console.log(result); // 10
return result * 4;
});

Output:

10
10
10

In this example, we have multiple handlers for one promise. These handlers have no
relationships. Also, they execute independently and don’t pass the result from one to another
like the promise chain above.

The following picture illustrates a promise that has multiple handlers:

In practice, you will rarely use multiple handlers for one promise.
Returning a Promise
When you return a value in the then() method, the then() method returns a new Promise that
immediately resolves to the return value.

Also, you can return a new promise in the then() method, like this:

let p = new Promise((resolve, reject) => {


setTimeout(() => {
resolve(10);
}, 3 * 100);
});

p.then((result) => {
console.log(result);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result * 2);
}, 3 * 1000);
});
}).then((result) => {
console.log(result);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result * 3);
}, 3 * 1000);
});
}).then(result => console.log(result));

Output:

10
20
60

This example shows 10, 20, and 60 after every 3 seconds. This code pattern allows you to
execute some tasks in sequence.
The following modi ed the above example:

function generateNumber(num) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num);
}, 3 * 1000);
});
}

generateNumber(10)
.then((result) => {
console.log(result);
return generateNumber(result * 2);
})
.then((result) => {
console.log(result);
return generateNumber(result * 3);
})
.then((result) => console.log(result));

Promise chaining syntax


Sometimes, you have multiple asynchronous tasks that you want to execute in sequence. In
addition, you need to pass the result of the previous step to the next one. In this case, you
can use the following syntax:

step1()
.then(result => step2(result))
.then(result => step3(result))
...

If you need to pass the result from the previous task to the next one without passing the
result, you use this syntax:

step1()
.then(step2)
.then(step3)
...

Suppose that you want to perform the following asynchronous operations in sequence:

• First, get the user from the database.


• Second, get the services of the selected user.
• Third, calculate the service cost from the user’s services.
fi
The following functions illustrate the three asynchronous operations:

function getUser(userId) {
return new Promise((resolve, reject) => {
console.log('Get the user from the database.');
setTimeout(() => {
resolve({
userId: userId,
username: 'admin'
});
}, 1000);
})
}

function getServices(user) {
return new Promise((resolve, reject) => {
console.log(`Get the services of ${user.username} from the API.`);
setTimeout(() => {
resolve(['Email', 'VPN', 'CDN']);
}, 3 * 1000);
});
}

function getServiceCost(services) {
return new Promise((resolve, reject) => {
console.log(`Calculate the service cost of ${services}.`);
setTimeout(() => {
resolve(services.length * 100);
}, 2 * 1000);
});
}

The following uses the promises to serialize the sequences:

getUser(100)
.then(getServices)
.then(getServiceCost)
.then(console.log);

Output

Get the user from the database.


Get the services of admin from the API.
Calculate the service cost of Email,VPN,CDN.
300
Promise composition
Promise.all() & Promise.race()

Promise.all()
Summary: in this tutorial, you will learn how to use the Promise.all() static method to
aggregate results from multiple asynchronous operations.

Introduction to the JavaScript Promise.all() method


The Promise.all() static method takes an iterable of promises:

Promise.all(iterable);

The Promise.all() method returns a single promise that resolves when all the input promises
have been resolved. The returned promise resolves to an array of the results of the input
promises:

In this diagram, the promise1 resolves to a value v1 at t1 and the promise2 resolves to a
value v2 at t2. Hence, the Promise.all(promise1, promise2) returns a promise that resolves to
an array containing the results of the promise1 and promise2 [v1, v2] at t2.

In other words, the Promise.all() waits for all the input promises to resolve and returns a new
promise that resolves to an array containing the results of the input promises.

If one of the input promise rejects, the Promise.all() method immediately returns a promise
that rejects with an error of the rst rejected promise:
fi
In this diagram, the promise2 rejects at t1 with an error. Therefore, the Promise.all() returns a
new promise that immediately rejects with the same error. Also, the Promise.all() doesn’t care
other input promises, whether they will resolve or reject.

In practice, the Promise.all() is useful to aggregate the results from multiple asynchronous
operations.

JavaScript Promise.all() method examples


Let’s take some examples to understand how the Promise.all() method works.

1) Resolved promises example


The following promises resolve to 10, 20, and 30 after 1, 2, and 3 seconds. We use
the setTimeout() to simulate the asynchronous operations:

const p1 = new Promise((resolve, reject) => {


setTimeout(() => {
console.log('The rst promise has resolved');
resolve(10);
}, 1 * 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('The second promise has resolved');
resolve(20);
}, 2 * 1000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('The third promise has resolved');
resolve(30);
}, 3 * 1000);
});

Promise.all([p1, p2, p3]).then((results) => {


const total = results.reduce((p, c) => p + c);

console.log(`Results: ${results}`);
console.log(`Total: ${total}`);
});
fi
Output

The rst promise has resolved


The second promise has resolved
The third promise has resolved
Results: 10,20,30
Total: 60

When all promises have resolved, the values from these promises are passed into the
callback of the then() method as an array.

Inside the callback, we use the Array’s reduce() method to calculate the total value and use
the console.log to display the array of values as well as the total.

2) Rejected promises example


The Promise.all() returns a Promise that is rejected if any of the input promises are rejected.

const p1 = new Promise((resolve, reject) => {


setTimeout(() => {
console.log('The rst promise has resolved');
resolve(10);
}, 1 * 1000);

});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('The second promise has rejected');
reject('Failed');
}, 2 * 1000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('The third promise has resolved');
resolve(30);
}, 3 * 1000);
});

Promise.all([p1, p2, p3])


.then(console.log) // never execute
.catch(console.log);

Output:

The rst promise has resolved


The second promise has rejected
Failed
The third promise has resolved
fi
fi
fi
In this example, we have three promises: the rst one is resolved after 1 second, the second
is rejected after 2 seconds, and the third one is resolved after 3 seconds.

As a result, the returned promise is rejected because the second promise is rejected.
The catch() method is executed to display the reason for the rejected promise.

Summary
• The Promise.all() method accepts a list of promises and returns a new promsie that
resolve to an array of results of the input promises if all the input promises resolved; or
reject with an error of the rst rejected promise.
• Use the Promise.all() method to aggregate results from multiple asynchronous
operations.
fi
fi
Promise.race()
Summary: The Promise.race(iterable) method returns a new promise that ful lls or rejects as
soon as one of the promises in an iterable ful lls or rejects, with the value or error from that
promise.

Introduction to JavaScript Promise.race() static method


The Promise.race() static method accepts a list of promises as an iterable object and returns
a new promise that ful lls or rejects as soon as there is one promise that ful lls or rejects,
with the value or reason from that promise.

Here’s the syntax of the Promise.race() method:

Promise.race(iterable)

In this syntax, the iterable is an iterable object that contains a list of promises.

The name of Promise.race() implies that all the promises race against each other with a
single winner, either resolved or rejected.

See the following diagram:

In this diagram:

• The promise1 is ful lled with the value v1 at t1.


• The promise2 is rejected with the error at t2.
• Because the promise1 is resolved earlier than the promise2, the promise1 wins the
race. Therefore, the Promise.race([promise1, promise2]) returns a new promise that is
ful lled with the value v1 at t1.
fi
fi
fi
fi
fi
fi
See another diagram:

In this diagram:

• The promise1 is ful lled with v1 at t2.


• The promise2 is rejected with error at t1.
• Because the promise2 is resolved earlier than the promise1, the promise2 wins the
race. Therefore, the Promise.race([promise1, promise2]) returns a new promise that is
rejected with the error at t1.

JavaScript Promise.race() examples


Let’s take some examples of using the Promise.race() static method.

1) Simple JavaScript Promise.race() examples


The following creates two promises: one resolves in 1 second and the other resolves in 2
seconds. Because the rst promise resolves faster than the second one,
the Promise.race() resolves with the value from the rst promise:

const p1 = new Promise((resolve, reject) => {


setTimeout(() => {
console.log('The rst promise has resolved');
resolve(10);
}, 1 * 1000);

});

const p2 = new Promise((resolve, reject) => {


setTimeout(() => {
console.log('The second promise has resolved');
resolve(20);
}, 2 * 1000);
});

Promise.race([p1, p2])
.then(value => console.log(`Resolved: ${value}`))
.catch(reason => console.log(`Rejected: ${reason}`));
fi
fi
fi
fi
Output:

The rst promise has resolved


Resolved: 10
The second promise has resolved

The following example creates two promises. The rst promise resolves in 1 second while
the second one rejects in 2 seconds. Because the rst promise is faster than the second one,
the returned promise resolves to the value of the rst promise:

const p1 = new Promise((resolve, reject) => {


setTimeout(() => {
console.log('The rst promise has resolved');
resolve(10);
}, 1 * 1000);

});

const p2 = new Promise((resolve, reject) => {


setTimeout(() => {
console.log('The second promise has rejected');
reject(20);
}, 2 * 1000);
});

Promise.race([p1, p2])
.then(value => console.log(`Resolved: ${value}`))
.catch(reason => console.log(`Rejected: ${reason}`));

Output

The rst promise has resolved


Resolved: 10
The second promise has rejected

Note that if the second promise was faster than the rst one, the return promise would reject
for the reason of the second promise.
fi
fi
fi
fi
fi
fi
fi
2) Practical JavaScript Promise.race() example
Suppose you have to show a spinner if the data loading process from the server is taking
longer than a number of seconds.

To do this, you can use the Promise.race() static method. If a timeout occurs, you show the
loading indicator, otherwise, you show the message.

The following illustrates the HTML code:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JavaScript Promise.race() Demo</title>
<link href="css/promise-race.css" rel="stylesheet">
</head>

<body>
<div id="container">
<button id="btnGet">Get Message</button>
<div id="message"></div>
<div id="loader"></div>
</div>
<script src="js/promise-race.js"></script>
</body>
</html>

To create the loading indicator, we use the CSS animation feature. See the promise-
race.css for more information. Technically speaking, if an element has the .loader class, it
shows the loading indicator.

First, de ne a new function that loads data. It uses the setTimeout() to emulate an
asynchronous operation:

const DATA_LOAD_TIME = 5000;

function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const message = 'Promise.race() Demo';
resolve(message);
}, DATA_LOAD_TIME);
});
}
fi
Second, develop a function that shows some contents:

function showContent(message) {
document.querySelector('#message').textContent = message;
}

This function can also be used to set the message to blank.

Third, de ne the timeout() function that returns a promise. The promise will reject when a
speci ed TIMEOUT is passed.

const TIMEOUT = 500;

function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => reject(), TIMEOUT);
});
}

Fourth, develop a couple of functions that show and hide the loading indicator:

function showLoadingIndicator() {
document.querySelector('#loader').className = 'loader';
}

function hideLoadingIndicator() {
document.querySelector('#loader').className = '';
}

Fifth, attach a click event listener to the Get Message button. Inside the click handler, use
the Promise.race() static method:

// handle button click event


const btn = document.querySelector('#btnGet');

btn.addEventListener('click', () => {
// reset UI if users click the 2nd, 3rd, ... time
reset();

// show content or loading indicator


Promise.race([getData()
.then(showContent)
.then(hideLoadingIndicator), timeout()
])
.catch(showLoadingIndicator);
});
fi
fi
We pass two promises to the Promise.race() method:

Promise.race([getData()
.then(showContent)
.then(hideLoadingIndicator), timeout()
])
.catch(showLoadingIndicator);

The rst promise gets data from the server, shows the content, and hides the loading
indicator. The second promise sets a timeout.

If the rst promise takes more than 500 ms to settle, the catch() is called to show the loading
indicator. Once the rst promise resolves, it hides the loading indicator.

Finally, develop a reset() function that hides the message and loading indicator if the button
is clicked for the second time.

// reset UI
function reset() {
hideLoadingIndicator();
showContent('');
}
fi
fi
fi
Put it all together.

// after 0.5 seconds, if the getData() has not resolved, then show
// the Loading indicator
const TIMEOUT = 500;
const DATA_LOAD_TIME = 5000;

function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const message = 'Promise.race() Demo';
resolve(message);
}, DATA_LOAD_TIME);
});
}

function showContent(message) {
document.querySelector('#message').textContent = message;
}

function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => reject(), TIMEOUT);
});
}

function showLoadingIndicator() {
document.querySelector('#loader').className = 'loader';
}

function hideLoadingIndicator() {
document.querySelector('#loader').className = '';
}

// handle button click event


const btn = document.querySelector('#btnGet');

btn.addEventListener('click', () => {
// reset UI if users click the second time
reset();

// show content or loading indicator


Promise.race([getData()
.then(showContent)
.then(hideLoadingIndicator), timeout()
])
.catch(showLoadingIndicator);
});

// reset UI
function reset() {
hideLoadingIndicator();
showContent('');
}
Promise Error Handling
Summary: in this tutorial, you will learn how to deal with error handling in promises.

Suppose that you have a function called getUserById() that returns a Promise:

function getUserById(id) {
return new Promise((resolve, reject) => {
resolve({
id: id,
username: 'admin'
});
});
}

Normal error
First, change the getUserById() function to throw an error outside the promise:

function getUserById(id) {
if (typeof id !== 'number' || id <= 0) {
throw new Error('Invalid id argument');
}

return new Promise((resolve, reject) => {


resolve({
id: id,
username: 'admin'
});
});
}

Second, handle the promise by using both then() and catch() methods:

getUserById('a')
.then(user => console.log(user.username))
.catch(err => console.log(err));

The code throws an error:

Uncaught Error: Invalid id argument


When you raise an exception outside the promise, you must catch it with try/catch:

try {
getUserById('a')
.then(user => console.log(user.username))
.catch(err => console.log(`Caught by .catch ${err}`));
} catch (error) {
console.log(`Caught by try/catch ${error}`);
}

Output:

Caught by try/catch Error: Invalid id argument

Errors inside the Promises


We change the getUserById() function to throw an error inside the promise:

let authorized = false;

function getUserById(id) {
return new Promise((resolve, reject) => {
if (!authorized) {
throw new Error('Unauthorized access to the user data');
}

resolve({
id: id,
username: 'admin'
});
});
}

And consume the promise:

try {
getUserById(10)
.then(user => console.log(user.username))
.catch(err => console.log(`Caught by .catch ${err}`));
} catch (error) {
console.log(`Caught by try/catch ${error}`);
}
Output:

Caught by .catch Error: Unauthorized access to the user data

If you throw an error inside the promise, the catch() method will catch it, not the try/catch.

If you chain promises, the catch() method will catch errors that occurred in any promise. For
example:

promise1
.then(promise2)
.then(promise3)
.then(promise4)
.catch(err => console.log(err));

In this example, if any error in the promise1, promise2, or promise4, the catch() method will
handle it.

Calling reject() function


Throwing an error has the same effect as calling the reject() as illustrated in the following
example:

let authorized = false;

function getUserById(id) {
return new Promise((resolve, reject) => {
if (!authorized) {
reject('Unauthorized access to the user data');
}

resolve({
id: id,
username: 'admin'
});
});
}

try {
getUserById(10)
.then(user => console.log(user.username))
.catch(err => console.log(`Caught by .catch ${err}`));
} catch (error) {
console.log(`Caught by try/catch ${error}`);
}
In this example, instead of throwing an error inside the promise, we called
the reject() explicitly. The catch() method also handles the error in this case.

Missing the catch() method


The following example does not provide the catch() method to handle the error inside the
promise. It will cause a runtime error and terminate the program:

function getUserById(id) {
return new Promise((resolve, reject) => {
if (!authorized) {
reject('Unauthorized access to the user data');
}
resolve({
id: id,
username: 'admin'
});
});
}

try {
getUserById(10)
.then(user => console.log(user.username));
// the following code will not execute
console.log('next');

} catch (error) {
console.log(`Caught by try/catch ${error}`);
}

Output:

Uncaught (in promise) Unauthorized access to the user data

If the promise is resolved, you can omit the catch() method. In the future, a potential error
may cause the program to stop unexpectedly.

Summary
• Inside the promise, the catch() method will catch the error caused by
the throw statement and reject().
• If an error occurs and you don’t have the catch() method, the JavaScript engine issues
a runtime error and stops the program.
Section 9. ES6 collections

Map
Summary: in this tutorial, you will learn about the JavaScript Map object that maps a key to a
value.

Introduction to JavaScript Map object


Before ES6, we often used an object to emulate a map by mapping a key to a value of any
type. But using an object as a map has some side effects:

1. An object always has a default key like the prototype.


2. A key of an object must be a string or a symbol, you cannot use an object as a key.
3. An object does not have a property that represents the size of the map.
ES6 provides a new collection type called Map that addresses these de ciencies.

By de nition, a Map object holds key-value pairs. Keys are unique in a Map’s collection. In
other words, a key in a Map object only appears once.

Keys and values of a Map can be any values.

When iterating a Map object, each iteration returns a 2-member array of [key, value]. The
iteration order follows the insertion order which corresponds to the order in which each key-
value pair was rst inserted into the Map by the set() method.

To create a new Map, you use the following syntax:

let map = new Map([iterable]);

The Map() accepts an optional iterable object whose elements are key-value pairs.
fi
fi
fi
Useful JavaScript Map methods
• clear() – removes all elements from the map object.
• delete(key) – removes an element speci ed by the key. It returns if the element is in
the map, or false if it does not.
• entries() – returns a new Iterator object that contains an array of [key, value] for each
element in the map object. The order of objects in the map is the same as the
insertion order.
• forEach(callback[, thisArg]) – invokes a callback for each key-value pair in the map in
the insertion order. The optional thisArg parameter sets the this value for each
callback.
• get(key) – returns the value associated with the key. If the key does not exist, it
returns unde ned.
• has(key) – returns true if a value associated with the key exists or false otherwise.
• keys() – returns a new Iterator that contains the keys for elements in insertion order.
• set(key, value) – sets the value for the key in the map object. It returns the map object
itself therefore you can chain this method with other methods.
• values() returns a new iterator object that contains values for each element in
insertion order.

JavaScript Map examples


Let’s take some examples of using a Map object.

Create a new Map object


Suppose you have a list of user objects as follows:

let john = {name: 'John Doe'},


lily = {name: 'Lily Bush'},
peter = {name: 'Peter Drucker'};

Assuming that you have to create a map of users and roles. In this case, you use the
following code:

let userRoles = new Map();

The userRoles is an instance of the Map object and its type is an object as illustrated in the
following example:

console.log(typeof(userRoles)); // object
console.log(userRoles instanceof Map); // true
fi
fi
Add elements to a Map
To assign a role to a user, you use the set() method:

userRoles.set(john, 'admin');

The set() method maps user john with the admin role. Since the set() method is chainable, you
can save some typing as shown in this example:

userRoles.set(lily, 'editor')
.set(peter, 'subscriber');

Initialize a map with an iterable object


As mentioned earlier, you can pass an iterable object to the Map() constructor:

let userRoles = new Map([


[john, 'admin'],
[lily, 'editor'],
[peter, 'subscriber']
]);

Get an element from a map by key


If you want to see the roles of John , you use the get() method:

userRoles.get(john); // admin

If you pass a key that does not exist, the get() method will return unde ned.

let foo = {name: 'Foo'};


userRoles.get(foo); //unde ned

Check the existence of an element by key


To check if a key exists in the map, you use the has() method.

userRoles.has(foo); // false
userRoles.has(lily); // true
fi
fi
Get the number of elements in the map
The size property returns the number of entries of the Map object.

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

Iterate over map keys


To get the keys of a Map object, you use the keys() method. The keys() returns a
new iterator object that contains the keys of elements in the map.

The following example displays the username of the users in the userRoles map object.

let john = { name: 'John Doe' },


lily = { name: 'Lily Bush' },
peter = { name: 'Peter Drucker' };

let userRoles = new Map([


[john, 'admin'],
[lily, 'editor'],
[peter, 'subscriber'],
]);

for (const user of userRoles.keys()) {


console.log(user.name);
}

Output:

John Doe
Lily Bush
Peter Drucker
Iterate over map values
Similarly, you can use the values() method to get an iterator object that contains values for all
the elements in the map:

let john = { name: 'John Doe' },


lily = { name: 'Lily Bush' },
peter = { name: 'Peter Drucker' };

let userRoles = new Map([


[john, 'admin'],
[lily, 'editor'],
[peter, 'subscriber'],
]);

for (let role of userRoles.values()) {


console.log(role);
}

Output:

admin
editor
subscriber

Iterate over map elements


Also, the entries() method returns an iterator object that contains an array of [key,value] of
each element in the Map object:

let john = { name: 'John Doe' },


lily = { name: 'Lily Bush' },
peter = { name: 'Peter Drucker' };

let userRoles = new Map([


[john, 'admin'],
[lily, 'editor'],
[peter, 'subscriber'],
]);

for (const role of userRoles.entries()) {


console.log(`${role[0].name}: ${role[1]}`);
}
To make the iteration more natural, you can use destructuring as follows:

let john = { name: 'John Doe' },


lily = { name: 'Lily Bush' },
peter = { name: 'Peter Drucker' };

let userRoles = new Map([


[john, 'admin'],
[lily, 'editor'],
[peter, 'subscriber'],
]);

for (let [user, role] of userRoles.entries()) {


console.log(`${user.name}: ${role}`);
}

In addition to for...of loop, you can use the forEach() method of the map object:

let john = { name: 'John Doe' },


lily = { name: 'Lily Bush' },
peter = { name: 'Peter Drucker' };

let userRoles = new Map([


[john, 'admin'],
[lily, 'editor'],
[peter, 'subscriber'],
]);

userRoles.forEach((role, user) => console.log(`${user.name}: ${role}`));

Convert map keys or values to a array


Sometimes, you want to work with an array instead of an iterable object, in this case, you can
use the spread operator.

The following example converts keys for each element into an array of keys:

var keys = [...userRoles.keys()];


console.log(keys);

Output:

[ { name: 'John Doe' },


{ name: 'Lily Bush' },
{ name: 'Peter Drucker' } ]
And the following converts the values of elements to an array:

let roles = [...userRoles.values()];


console.log(roles);

Output

[ 'admin', 'editor', 'subscriber' ]

Delete an element by key


To delete an entry in the map, you use the delete() method.

userRoles.delete(john);

Delete all elements in the map


To delete all entries in the Map object, you use the clear() method:

userRoles.clear();

Hence, the size of the map now is zero.

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

A WeakMap is similar to a Map except the keys of a WeakMap must be objects. It means
that when a reference to a key (an object) is out of scope, the corresponding value is
automatically released from the memory.

A WeakMap only has subset methods of a Map object:

• get(key)
• set(key, value)
• has(key)
• delete(key)

Here are the main difference between a Map and a WeekMap:

• Elements of a WeakMap cannot be iterated.


• Cannot clear all elements at once.
• Cannot check the size of a WeakMap.

In this tutorial, you have learned how to work with the JavaScript Map object and its useful
methods to manipulate entries in the map.
Set
Summary: in this tutorial, you will learn about the JavaScript Set object that allows you to
manage a collection of unique values of any type effectively.

Introduction to the JavaScript Set object


ES6 provides a new type named Set that stores a collection of unique values of any type. To
create a new empty Set, you use the following syntax:

let setObject = new Set();

The Set constructor also accepts an optional iterable object. If you pass an iterable object to
the Set constructor, all the elements of the iterable object will be added to the new set:

let setObject = new Set(iterableObject);

Useful Set methods


The Set object provides the following useful methods:

• add(value) – appends a new element with a speci ed value to the set. It returns
the Set object, therefore, you can chain this method with another Set method.
• clear() – removes all elements from the Set object.
• delete(value) – deletes an element speci ed by the value.
• entries()– returns a new Iterator that contains an array of [value, value] .
• forEach(callback [, thisArg]) – invokes a callback on each element of the Set with
the this value sets to thisArg in each call.
• has(value) – returns true if an element with a given value is in the set, or false if it is
not.
• keys() – is the same as values() function.
• [@@iterator] – returns a new Iterator object that contains values of all elements stored
in the insertion order.
fi
fi
JavaScript Set examples

Create a new Set from an Array


The following example shows how to create a new Set from an array.

let chars = new Set(['a', 'a', 'b', 'c', 'c']);

All elements in the set must be unique therefore the chars only contains 3 distinct
elements a, b and c.

console.log(chars);

Output:

Set { 'a', 'b', 'c' }

When you use the typeof operator to the chars, it returns object.

console.log(typeof(chars));

Output:

object

The chars set is an instance of the Set type so the following statement returns true.

let result = chars instanceof Set;


console.log(result);

Get the size of a Set


To get the number of elements that the set holds, you use the size property of the Set object:

let size = chars.size;


console.log(size);// 3
Add elements to a Set
To add an element to the set, you use the add() method:

chars.add('d');
console.log(chars);

Output:

Set { 'a', 'b', 'c', 'd' }

Since the add() method is chainable, you can add multiple items to a set using a chain
statement:

chars.add('e')
.add('f');

Check if a value is in the Set


To check if a set has a speci c element, you use the has() method. The has() method
returns true if the set contains the element, otherwise, it returns false. Since the chars set
contains 'a', the following statement returns true:

let exist = chars.has('a');


console.log(exist);// true

The following statement returns false because the chars set does not contain the 'z' value.

exist = chars.has('z');
console.log(exist); // false

Remove elements from a set


To delete a speci ed element from a set, you use the delete() method. The following
statement deletes the 'f' value from the chars set.

chars.delete('f');
console.log(chars); // Set {"a", "b", "c", "d", "e"}

Output:

Set { 'a', 'b', 'c', 'd', 'e' }


fi
fi
The delete() method returns true indicating that the element has been removed successfully.
To delete all elements of a set, you use the clear() method:

chars.clear();
console.log(chars); // Set{}

Looping the elements of a JavaScript Set


A Set object maintains the insertion order of its elements, therefore, when you iterate over its
elements, the order of the elements is the same as the inserted order. Suppose you have a
set of user roles as follows.

let roles = new Set();


roles.add('admin')
.add('editor')
.add('subscriber');

The following example uses the for…of loop to iterate over the chars set.

for (let role of roles) {


console.log(role);
}

Output:

admin
editor
subscriber

The Set also provides the keys(), values(), and entries() methods like the Map. However,
keys and values in the Set are identical. For example:

for (let [key, value] of roles.entries()) {


console.log(key === value);
}

Output

true
true
true
Invoke a callback function on each element of a set
If you want to invoke a callback on every element of a set, you can use the forEach() method.

roles.forEach(role => console.log(role.toUpperCase()));

WeakSets
A WeakSet is similar to a Set except that it contains only objects. Since objects in
a WeakSet may be automatically garbage-collected, a WeakSet does not have size property.
Like a WeakMap, you cannot iterate elements of a WeakSet, therefore, you will nd that
WeakSet is rarely used in practice. In fact, you only use a WeakSet to check if a speci ed
value is in the set. Here is an example:

let computer = {type: 'laptop'};


let server = {type: 'server'};
let equipment = new WeakSet([computer, server]);

if (equipment.has(server)) {
console.log('We have a server');
}

Output

We have a server

In this tutorial, you have learned about the JavaScript Set object and how to manipulate its
elements.
fi
fi
Section 10. Object extensions

Object.assign()
Summary: in this tutorial, you will learn how to use the JavaScript Object.assign() method in
ES6.

The following shows the syntax of the Object.assign() method:

Object.assign(target, ...sources)

The Object.assign() copies all enumerable and own properties from the source objects to
the target object. It returns the target object.

The Object.assign() invokes the getters on the source objects and setters on the target. It
assigns properties only, not copying or de ning new properties.

Using JavaScript Object.assign() to clone an object


The following example uses the Object.assign() method to clone an object.

let widget = {
color: 'red'
};

let clonedWidget = Object.assign({}, widget);

console.log(clonedWidget);

Output

{ color: 'red' }

Note that the Object.assign() only carries a shallow clone, not a deep clone.
fi
Using JavaScript Object.assign() to merge objects
The Object.assign() can merge source objects into a target object which has properties
consisting of all the properties of the source objects. For example:

let box = {
height: 10,
width: 20
};

let style = {
color: 'Red',
borderStyle: 'solid'
};

let styleBox = Object.assign({}, box, style);

console.log(styleBox);

Output:

{
height: 10,
width: 20,
color: 'Red',
borderStyle: 'solid'
}

If the source objects have the same property, the property of the later object overwrites the
earlier one:

let box = {
height: 10,
width: 20,
color: 'Red'
};

let style = {
color: 'Blue',
borderStyle: 'solid'
};

let styleBox = Object.assign({}, box, style);

console.log(styleBox);
Output:

{
height: 10,
width: 20,
color: 'Blue',
borderStyle: 'solid'
}

Summary
• Object.assign() assigns enumerable and own properties from a source object to a
target object.
• Object.assign() can be used to clone an object or merge objects.
Object.is()
Summary: in this tutorial, you will learn about the JavaScript Object.is() to check if two values
are the same.

The Object.is() behaves like the === operator with two differences:

• -0 and +0
• NaN

Negative zero
The === operator treats -0 and +0 are the same value:

let amount = +0,


volume = -0;
console.log(volume === amount);

Output:

true

However, the Object.is() treats +0 and -0 as different values. For example:

let amount = +0,


volume = -0;
console.log(Object.is(amount, volume));

Output

false
NaN
The === operator considers NaN and NaN are different values. The NaN is the only number
that does not equal itself. For example:

let quantity = NaN;


console.log(quantity === quantity);

Output:

False

However, Object.is() treats NaN as the same value:

let quantity = NaN;


console.log(quantity, quantity);

Output:

True
See the following sameness comparison table for reference:
Section 13. Proxy & Re ection

Proxy
Summary: in this tutorial, you will learn about the JavaScript Proxy object in ES6.

What is a JavaScript Proxy object


A JavaScript Proxy is an object that wraps another object (target) and intercepts the
fundamental operations of the target object.

The fundamental operations can be the property lookup, assignment, enumeration, and
function invocations, etc.

Creating a proxy object


To create a new proxy object, you use the following syntax:

let proxy = new Proxy(target, handler);

In this syntax:

• target – is an object to wrap.


• handler – is an object that contains methods to control the behaviors of the target. The
methods inside the handler object are called traps.

A simple proxy example


First, de ne an object called user:

const user = {
rstName: 'John',
lastName: 'Doe',
email: '[email protected]',
}

Second, de ne a handler object:

const handler = {
get(target, property) {
console.log(`Property ${property} has been read.`);
return target[property];
}
}
fi
fi
fi
fl
Third, create a proxy object:

const proxyUser = new Proxy(user, handler);

The proxyUser object uses the user object to store data. The proxyUser can access all
properties of the user object.

Fourth, access the rstName and lastName properties of the user object via the proxyUser object:

console.log(proxyUser. rstName);
console.log(proxyUser.lastName);

Output:

Property rstName has been read.


John
Property lastName has been read.
Doe

When you access a property of the user object via the proxyUser object, the get() method in
the handler object is called.

Fifth, if you modify the original object user, the change is re ected in the proxyUser:

user. rstName = 'Jane';


console.log(proxyUser. rstName);

Output:

Property rstName has been read.


Jane
fi
fi
fi
fi
fi
fi
fl
Similarly, a change in the proxyUser object will be re ected in the original object (user):

proxyUser.lastName = 'William';
console.log(user.lastName);

Output:

William

Proxy Traps
The get() trap
The get() trap is red when a property of the target object is accessed via the proxy object.

In the previous example, a message is printed out when a property of the user object is
accessed by the proxyUser object.

Generally, you can develop a custom logic in the get() trap when a property is accessed.

For example, you can use the get() trap to de ne computed properties for the target object.
The computed properties are properties whose values are calculated based on values of
existing properties.

The user object does not have a property fullName, you can use the get() trap to create
the fullName property based on the rstName and lastName properties:

const user = {
rstName: 'John',
lastName: 'Doe'
}

const handler = {
get(target, property) {
return property === 'fullName' ?
`${target. rstName} ${target.lastName}` :
target[property];
}
};

const proxyUser = new Proxy(user, handler);

console.log(proxyUser.fullName);

Output:

John Doe
fi
fi
fi
fi
fi
fl
The set() trap
The set() trap controls behavior when a property of the target object is set.

Suppose that the age of the user must be greater than 18. To enforce this constraint, you
develop a set() trap as follows:

const user = {
rstName: 'John',
lastName: 'Doe',
age: 20
}

const handler = {
set(target, property, value) {
if (property === 'age') {
if (typeof value !== 'number') {
throw new Error('Age must be a number.');
}
if (value < 18) {
throw new Error('The user must be 18 or older.')
}
}
target[property] = value;
}
};

const proxyUser = new Proxy(user, handler);

First, set the age of user to a string:

proxyUser.age = 'foo';

Output:

Error: Age must be a number.

Second, set the age of the user to 16:

proxyUser.age = '16';

Output:

The user must be 18 or older.


fi
Third, set the age of the user to 21:

proxyUser.age = 21;

No error occurred.

The apply() trap


The handler.apply() method is a trap for a function call. Here is the syntax:

let proxy = new Proxy(target, {


apply: function(target, thisArg, args) {
//...
}
});

See the following example:

const user = {
rstName: 'John',
lastName: 'Doe'
}

const getFullName = function (user) {


return `${user. rstName} ${user.lastName}`;
}

const getFullNameProxy = new Proxy(getFullName, {


apply(target, thisArg, args) {
return target(...args).toUpperCase();
}
});

console.log(getFullNameProxy(user)); //

Output

JOHN DOE
fi
fi
More traps
The following are more traps:

• construct – traps usage of the new operator


• getPrototypeOf – traps an internal call to [[GetPrototypeOf]]
• setPrototypeOf – traps a call to Object.setPrototypeOf
• isExtensible – traps a call to Object.isExtensible
• preventExtensions – traps a call to Object.preventExtensions
• getOwnPropertyDescriptor – traps a call to Object.getOwnPropertyDescriptor
In this tutorial, you have learned about the JavaScript Proxy object used to wrap another
object to change the fundamental behaviors of that object.
Re ection
Summary: in this tutorial, you will learn about the JavaScript re ection and Re ect API in
ES6.

What is re ection
In computer programming, re ection is the ability of a program to manipulate variables,
properties, and methods of objects at runtime.

Prior to ES6, JavaScript already has re ection features even though they were not of cially
called that by the community or the speci cation. For example, methods
like Object.keys(), Object.getOwnPropertyDescriptor(), and Array.isArray() are the classic
re ection features.

ES6 introduces a new global object called Re ect that allows you to call methods, construct
objects, get and set properties, manipulate and extend properties.

The Re ect API is important because it allows you to develop programs and frameworks that
are able to handle dynamic code.

Re ect API
Unlike the most global objects, the Re ect is not a constructor. It means that you cannot
use Re ect with the new operator or invoke the Re ect as a function. It is similar to
the Math and JSON objects. All the methods of the Re ect object are static.

• Re ect.apply() – call a function with speci ed arguments.


• Re ect.construct() – act like the new operator, but as a function. It is equivalent to
calling new target(...args).
• Re ect.de neProperty() – is similar to Object.de neProperty(), but return a Boolean
value indicating whether or not the property was successfully de ned on the object.
• Re ect.deleteProperty() – behave like the delete operator, but as a function. It’s
equivalent to calling the delete objectName[propertyName].
• Re ect.get() – return the value of a property.
• Re ect.getOwnPropertyDescriptor() – is similar to Object.getOwnPropertyDescriptor().
It returns a property descriptor of a property if the property exists on the object,
or unde ned otherwise.
• Re ect.getPrototypeOf() – is the same as Object.getPrototypeOf().
• Re ect.has() – work like the in operator, but as a function. It returns a boolean
indicating whether an property (either owned or inherited) exists.
Re ect.isExtensible() – is the same as Object.isExtensible().
• Re ect.ownKeys() – return an array of the owned property keys (not inherited) of an
object.
fl
fl
fl
fl
fl
fl
fl
fl
fl
fl
fl
fl
fl
fl
fi
fl
fi
fl
fl
fl
fl
fi
fi
fl
fi
fl
fl
fl
fi
fl
fi
• Re ect.preventExtensions() – is similar to Object.preventExtensions(). It returns a
Boolean.
• Re ect.set() – assign a value to a property and return a Boolean value which is true if
the property is set successfully.
• Re ect.setPrototypeOf() – set the prototype of an object.

Let’s take some examples of using the Re ect API:

Creating objects: Re ect.construct()


The Re ect.construct() method behaves like the new operator, but as a function. It is
equivalent to calling the new target(...args) with the possibility to specify a different prototype:

Re ect.construct(target, args [, newTarget])

The Re ect.construct() returns the new instance of the target, or the newTarget if speci ed,
initialized by the target as a constructor with the given array-like object args. See the following
example:

class Person {
constructor( rstName, lastName) {
this. rstName = rstName;
this.lastName = lastName;
}
get fullName() {
return `${this. rstName} ${this.lastName}`;
}
};

let args = ['John', 'Doe'];

let john = Re ect.construct(


Person,
args
);

console.log(john instanceof Person);


console.log(john.fullName); // John Doe

Output

true
John Doe
fl
fl
fl
fl
fl
fl
fi
fl
fi
fi
fi
fl
fl
fi
In this example:

• First, de ne a class called Person.


• Second, declare an args array that contains two strings.
• Third, create a new instance of the Person class using the Re ect.construct() method.
The john object is an instance of the Person class so it has the fullName property.

Calling a function: Re ect.apply()


Prior to ES6, you call a function with a speci ed this value and arguments by using
the Function.prototype.apply() method. For example:

let result = Function.prototype.apply.call(Math.max, Math, [10, 20, 30]);


console.log(result);

Output:

30

This syntax is quite verbose.

The Re ect.apply() provides the same feature as the Function.prototype.apply() but less
verbose and easier to understand:

let result = Re ect.apply(Math.max, Math, [10, 20, 30]);


console.log(result);

Here is the syntax of the Re ect.apply() method:

Re ect.apply(target, thisArg, args)

De ning a property: Re ect.de neProperty()


The Re ect.de neProperty() is like the Object.de neProperty(). However, it returns a Boolean
indicating whether or not the property was de ned successfully instead of throwing an
exception:

Re ect.de neProperty(target, propertyName, propertyDescriptor)


fl
fl
fi
fl
fl
fi
fi
fi
fl
fl
fl
fl
fi
fi
fi
fi
fl
See the following example:

let person = {
name: 'John Doe'
};

if (Re ect.de neProperty(person, 'age', {


writable: true,
con gurable: true,
enumerable: false,
value: 25,
})) {
console.log(person.age);
} else {
console.log('Cannot de ne the age property on the person object.');

In this tutorial, you have learned about the JavaScript re ection and the Re ect API that
contains a number of re ective methods.
fl
fi
fi
fl
fi
fl
fl

You might also like