2 Es6
2 Es6
Summary: in this tutorial, you will learn how to use the JavaScript let keyword to declare
block-scoped variables.
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
}
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
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.
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
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:
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:
To make the code completely ES6 style, you can use an arrow function as follows:
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);
{
console.log(counter); //
let counter = 10;
}
In this example, accessing the counter variable before declaring it causes a ReferenceError.
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.
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.
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.
Output:
The following example uses the let keyword instead of the var keyword:
In this case, the code shows four numbers from 0 to 4 inside a loop and a reference error:
The error:
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.
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:
However, if you redeclare a variable with the let keyword, you will get an error:
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.
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:
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:
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:
If you want the value of the person object to be immutable, you have to freeze it by using
the Object.freeze() method:
Note that Object.freeze() is shallow, meaning that it can freeze the properties of the object,
not the objects referenced by the properties.
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:
colors.pop();
colors.pop();
console.log(colors); // []
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.
If you don’t intend to modify the score variable inside the loop, you can use the const keyword instead:
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:
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.
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.
function say(message) {
console.log(message);
}
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:
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.
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:
console.log(put('Toy Car'));
// -> ['Toy Car']
console.log(put('Teddy Bear'));
// -> ['Teddy Bear'], not ['Toy Car','Teddy Bear']
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
function subtract( x = y, y = 1 ) {
return x - y;
}
subtract(10);
Error message:
Using functions
You can use a return value of a function as a default value for a parameter. For example:
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.
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:
[3,'A','B','C']
If you pass only the rst two parameters, the rest parameter will be an empty array:
fn(1,2);
[]
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:
function sum(...args) {
let total = 0;
for (const a of args) {
total += a;
}
return total;
}
sum(1, 2, 3);
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:
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:
Output:
The combine() function is an arrow that takes an inde nite number of arguments and
concatenates these arguments.
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.
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.
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:
Output:
[ 1, 3, 5, 2, 4, 6 ]
Or
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.
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:
However, by using the spread operator, you can pass an array of two numbers to
the compare() function:
The spread operator spreads out the elements of the array so a is 1 and b is 2 in this case.
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:
[ ].push.apply(rivers, moreRivers);
console.log(rivers);
The following example uses the spread operator to improve the readability of the code:
rivers.push(...moreRivers);
fi
JavaScript spread operator and array manipulation
2) Concatenating arrays
Also, you can use the spread operator to concatenate two or more arrays:
3) Copying an array
In addition, you can copy an array instance by using the spread operator:
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:
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.
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
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 machine = {
name,
status
};
The square brackets allow you to use the string literals and variables as the property names.
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 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.
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.
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:
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:
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 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;
}
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.
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.
Output:
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
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
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:
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);
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:
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:
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.
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}
The greeting variable then holds the result of the substitutions. The following example
substitutes an expression instead:
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:
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:
In this function:
let quantity = 9,
priceEach = 8.99,
result = format`${quantity} items cost $${(quantity * priceEach).toFixed(2)}.`;
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.
function getScores() {
return [70, 80, 90];
}
The following invokes the getScores() function and assigns the returned value to a variable:
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:
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];
}
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];
}
console.log(x); // 70
console.log(y); // 80
console.log(z); // 90
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
function getItems() {
return [10, 20];
}
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:
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;
}
console.log(a); // 10
console.log(b); // 20
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();
1) Swapping variables
The array destructuring makes it easy to swap values of variables without using a temporary
variable:
let a = 10,
b = 20;
console.log(a); // 20
console.log(b); // 10
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:
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.
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:
ES6 introduces the object destructuring syntax that provides an alternative way to
assign properties of an object to variables:
In this syntax:
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:
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:
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:
In this example, the middleName property doesn’t exist in the person object, therefore,
the middleName variable is unde ned.
let person = {
rstName: 'John',
lastName: 'Doe',
currentAge: 28
};
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
};
console.log(middleName); // 'C.'
console.log(age); // 28
function getPerson() {
return null;
}
To avoid this, you can use the OR operator (||) to fallback the null object to an empty object:
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;
let employee = {
id: 1001,
name: {
rstName: 'John',
lastName: 'Doe'
}
};
let {
name: {
rstName,
lastName
},
name
} = employee;
let person = {
rstName: 'John',
lastName: 'Doe'
};
display(person);
It’s possible to destructure the object argument passed into the function like this:
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.
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.
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';
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:
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.
// greeting.js
export let message = 'Hi';
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:
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.
// foo.js
export foo = 10;
// 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:
// cal.js
export let a = 10,
b = 20,
result = 0;
And you want to import these bindings from the cal.js, you can explicitly list them as follows:
multiply();
console.log(result); // 200
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:
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.
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';
}
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;
}
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:
If you want to use a different name when you import, you can use the as keyword as follows:
Re-exporting a binding
It’s possible to export bindings that you have imported. This is called re-exporting. For example:
In this example, we imported sum from the math.js module and re-export it. The following
statement is equivalent to the statements above:
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.
If you want to export all the bindings from another module, you can use the asterisk (*):
// 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.
// 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.
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:
To rename the default export, you also use the as keyword as follows:
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.
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
};
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:
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:
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:
To verify the fact that classes are special functions, you can use the typeof operator of to
check the type of the Person class.
The john object is also an instance of the Person and Object types:
For example, if you place the following code above the Person class declaration section, you
will get a ReferenceError.
Error:
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.
Error:
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.
class Person {
constructor(name) {
this.name = name;
}
}
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;
}
}
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 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:
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:
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 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:
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.
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.
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.
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.
function factory(aClass) {
return new aClass();
}
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:
The class expression has a method called sayHi(). And the greeting variable is an instance of
the class expression.
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:
How it works.
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.
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.
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:
To call the createAnonymous() method, you use the Person type instead of its instances:
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);
}
}
If you attempt to call the static method from an instance of the class, you’ll get an error. For
example:
Error:
className.staticMethodName();
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.
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:
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.
class Item {
constructor(name, quantity) {
this.name = name;
this.quantity = quantity;
this.constructor.count++;
}
static count = 0;
static getCount() {
return Item.count;
}
}
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.
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:
class Person {
constructor( rstName, lastName) {
this. rstName = rstName;
this.lastName = lastName;
}
get [name]() {
return `${this. rstName} ${this.lastName}`;
}
}
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.
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');
}
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');
}
}
bird.walk();
bird. y();
How it works.
First, use the extends keyword to make the Bird class inheriting from the Animal class:
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:
However, the child class has a constructor, it needs to call super(). For example, the following
code results in an error:
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;
}
}
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:
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`);
}
}
class Animal {
constructor(legs) {
this.legs = legs;
}
walk() {
console.log('walking on ' + this.legs + ' legs');
}
static helloWorld() {
console.log('Hello World');
}
}
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:
The following Queue class extends the Array reference type. The syntax is much cleaner
than the Queue implemented using the constructor/prototype pattern.
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.
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.
function Person(name) {
this.name = name;
}
You can create a new object from the Person function by using the new operator as follows:
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.
class Person {
constructor(name) {
this.name = name;
console.log(new.target.name);
}
}
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.
The following example de nes a function expression that returns the sum of two numbers:
console.log(add(10, 20)); // 30
The following example is equivalent to the above add() function expression but use an arrow
function instead:
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:
The typeof operator returns function indicating the type of arrow function.
The arrow function is also an instance of the Function type as shown in the following
example:
=> expression
For example, to sort an array of numbers in the descending order, you use the sort() method
of the array object 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.
console.log(lengths);
Output:
[ 4, 3, 5 ]
() => { statements }
For example:
let multiply = (
x,
y
) =>
x * y;
10 + 20;
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.
However, if you use a statement, you must wrap it inside a pair of curly braces as in the
following example:
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}
Since both block and object literal use curly brackets, the JavasScript engine cannot
distinguish between a block and an object.
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;
};
}
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;
};
}
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;
};
}
function show() {
return x => x + arguments[0];
}
The arrow function inside the showMe() function references the arguments object. However,
this arguments object belongs to the show() function, not the arrow function.
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:
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:
However, when you execute the code, you will get the following message regardless of
whatever you type:
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.
console.log(counter.next());
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;
}
};
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.
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
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:
The Symbol() function accepts a description as an optional argument. The description argument will
make your symbol more descriptive.
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:
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:
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:
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.
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.
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);
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:
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.
// 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:
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;
}
}
}
// A
// B
// C
Symbol.isConcatSpreadable
To concatenate two arrays, you use the concat() method as shown in the following example:
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.
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.
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.
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 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:
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:
• 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:
If you call the next() method after the last value has been returned, the next() returns the
result object as follows:
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.
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 };
}
}
}
};
Output:
2
4
6
8
10
You can explicitly access the [Symbol.iterator]() method as shown in the following script:
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.
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:
• 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.
Output:
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.
result = gen.next();
console.log(result);
Output:
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:
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.
Since generators are iterables, they can help you simplify the code for implementing iterator.
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:
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.
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;
}
}
}
bag.add(1);
bag.add(2);
bag.add(3);
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.
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.
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:
yield 1;
function* bar() {
yield;
}
let b = bar();
console.log(b.next());
Output:
function* generate() {
let result = yield;
console.log(`result is ${result}`);
}
let g = generate();
console.log(g.next());
console.log(g.next(1000));
Output:
result is 1000
{ value: unde ned, done: true }
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:
The second call z.next() sets the second of the arr array to 2 and returns the following object:
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());
In this case, yield sets the array [ 20, 30, 40 ] as the value of the value property of the return
object.
function* yieldArrayElements() {
yield 1;
yield* [ 20, 30, 40 ];
}
let a = yieldArrayElements();
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]:
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.
function getUsers() {
return [
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
];
}
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;
}
function ndUser(username) {
const users = getUsers();
const user = users. nd((user) => user.username === username);
return user;
}
console.log( ndUser('john'));
Output:
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 = [];
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);
}
ndUser('john', console.log);
Output:
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.
• 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 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.
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.
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);
});
}
[
{ 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);
});
}
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:
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);
});
}
How it works.
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.
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
// ...
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:
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);
});
}
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:
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:
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.
https://www.javascripttutorial.net/sample/promise/api.json
{
"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();
});
}
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.
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.
First, create a new promise that resolves to the number 10 after 3 seconds:
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:
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:
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.
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.
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:
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));
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:
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);
});
}
getUser(100)
.then(getServices)
.then(getServiceCost)
.then(console.log);
Output
Promise.all()
Summary: in this tutorial, you will learn how to use the Promise.all() static method to
aggregate results from multiple asynchronous operations.
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.
console.log(`Results: ${results}`);
console.log(`Total: ${total}`);
});
fi
Output
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.
});
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);
});
Output:
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.
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.
In this diagram:
In this diagram:
});
Promise.race([p1, p2])
.then(value => console.log(`Resolved: ${value}`))
.catch(reason => console.log(`Rejected: ${reason}`));
fi
fi
fi
fi
Output:
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:
});
Promise.race([p1, p2])
.then(value => console.log(`Resolved: ${value}`))
.catch(reason => console.log(`Rejected: ${reason}`));
Output
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.
<!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:
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;
}
Third, de ne the timeout() function that returns a promise. The promise will reject when a
speci ed TIMEOUT is passed.
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:
btn.addEventListener('click', () => {
// reset UI if users click the 2nd, 3rd, ... time
reset();
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 = '';
}
btn.addEventListener('click', () => {
// reset UI if users click the second time
reset();
// 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');
}
Second, handle the promise by using both then() and catch() methods:
getUserById('a')
.then(user => console.log(user.username))
.catch(err => console.log(err));
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:
function getUserById(id) {
return new Promise((resolve, reject) => {
if (!authorized) {
throw new Error('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}`);
}
Output:
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.
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.
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:
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.
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.
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.
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.
Assuming that you have to create a map of users and roles. In this case, you use the
following code:
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');
userRoles.get(john); // admin
If you pass a key that does not exist, the get() method will return unde ned.
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
The following example displays the username of the users in the userRoles map object.
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:
Output:
admin
editor
subscriber
In addition to for...of loop, you can use the forEach() method of the map object:
The following example converts keys for each element into an array of keys:
Output:
Output
userRoles.delete(john);
userRoles.clear();
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.
• get(key)
• set(key, value)
• has(key)
• delete(key)
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.
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:
• 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
All elements in the set must be unique therefore the chars only contains 3 distinct
elements a, b and c.
console.log(chars);
Output:
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.
chars.add('d');
console.log(chars);
Output:
Since the add() method is chainable, you can add multiple items to a set using a chain
statement:
chars.add('e')
.add('f');
The following statement returns false because the chars set does not contain the 'z' value.
exist = chars.has('z');
console.log(exist); // false
chars.delete('f');
console.log(chars); // Set {"a", "b", "c", "d", "e"}
Output:
chars.clear();
console.log(chars); // Set{}
The following example uses the for…of loop to iterate over the chars set.
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:
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.
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:
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.
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.
let widget = {
color: 'red'
};
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'
};
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'
};
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:
Output:
true
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:
Output:
False
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.
The fundamental operations can be the property lookup, assignment, enumeration, and
function invocations, etc.
In this syntax:
const user = {
rstName: 'John',
lastName: 'Doe',
email: '[email protected]',
}
const handler = {
get(target, property) {
console.log(`Property ${property} has been read.`);
return target[property];
}
}
fi
fi
fi
fl
Third, create a proxy object:
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:
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:
Output:
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];
}
};
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;
}
};
proxyUser.age = 'foo';
Output:
proxyUser.age = '16';
Output:
proxyUser.age = 21;
No error occurred.
const user = {
rstName: 'John',
lastName: 'Doe'
}
console.log(getFullNameProxy(user)); //
Output
JOHN DOE
fi
fi
More traps
The following are more traps:
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.
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}`;
}
};
Output
true
John Doe
fl
fl
fl
fl
fl
fl
fi
fl
fi
fi
fi
fl
fl
fi
In this example:
Output:
30
The Re ect.apply() provides the same feature as the Function.prototype.apply() but less
verbose and easier to understand:
let person = {
name: 'John Doe'
};
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