Javascript Coding Book For Beginners Web Development Crash Course Head First Javascript Programming Book For Modern Software Engineering Javascript The Definitive Guide For Coding Interview

Download as pdf or txt
Download as pdf or txt
You are on page 1of 251

JavaScript for Dummies

Learn JavaScript Quickly by Examples


- First Edition -

Neo D. Truman
Copyright

JavaScript for Dummies: Learn JavaScript Quickly by


Examples
Copyright © 2022 by Neo D. Truman
All rights reserved.
No part of this publication may be reproduced, stored in a retrieval system, or
transmitted in any form or by any means, electronic, mechanical, photocopying,
recording, scanning, or otherwise, without the prior consent of the Author.
Table of Contents

Copyright
Table of Contents
Preface
Book Audience
How to Contact Us
Conventions Used in This Book
CHAPTER 1 Setting Up a Development Environment
1.1 Installing a Code Editor
1.1.1 Install Essential Extensions
1.1.2 Installing Live Server
1.1.3 Enable Formatting Code on Save
1.1.4 Disable Compact Folders
1.1.5 Enable Word Wrap
1.1.6 Set Tab Size Smaller
1.2 Using a Web Browser
1.2.1 Using Developer Tools in The Browser
1.2.2 Using Console Tool
1.2.3 Using Source Tool
1.3 Preparing a Workspace
CHAPTER 2 Introduction to JavaScript
2.1 Text of The JavaScript Code
2.2 JavaScript Comments
2.2.1 Single-line Comments
2.2.2 Multi-line Comments
2.3 Identifiers
2.4 Reserved Words
2.5 Semicolons
2.6 Using Strict Mode
2.6.1 Declaring Strict Mode
2.6.2 What Are Not Allowed in Strict Mode?
2.6.3 The this Keyword in Strict Mode
2.7 JavaScript Modules
CHAPTER 3 Types, Values, and Variables
3.1 Variable Declaration
3.1.1 Declarations with Keywords
3.1.2 Variable Scope
3.2 Primitive Data Types
3.2.1 undefined
3.2.2 null
3.2.3 String
3.2.3.1 Creating Strings
3.2.3.2 Working with Strings
3.2.3.3 Regular Expressions
3.2.3.4 Template Literals
3.2.3.5 Escape Sequences
3.2.4 Number
3.2.4.1 Creating Numbers
3.2.4.2 Strings and Numbers
3.2.4.3 Arithmetic in JavaScript
3.2.4.4 Not a Number
3.2.4.5 Infinity

3.2.5 BigInt
3.2.6 Boolean
3.2.7 Symbol
3.3 Object
3.3.1 Creating Objects
3.3.2 Object.assign()
3.3.3 Accessor Properties
3.3.4 Global Object
3.3.5 Upgrading Object Literal
3.3.5.1 Shorthand Properties
3.3.5.2 Shorthand Methods
3.3.5.3 Computing Property Keys

3.3.6 Prototype of Objects


3.3.7 Prototype Chain
3.4 Array
3.4.1 Creating Arrays
3.4.2 Copying an Array
3.4.3 Accessing an Array Item
3.4.4 Checking Item’s Existence
3.4.5 Adding Array Items
3.4.6 Removing Array Items
3.4.7 Array Iteration
3.4.8 Popular Array’s Methods
3.4.8.1 every()
3.4.8.2 fill()
3.4.8.3 filter()
3.4.8.4 find()
3.4.8.5 findIndex()
3.4.8.6 join()
3.4.8.7 map()
3.4.8.8 reduce()
3.4.8.9 some()
3.4.8.10 sort()
3.5 Set
3.5.1 Creating a Set
3.5.2 Iterating Sets
3.5.3 Relation with Array
3.6 Map
3.6.1 Map Iteration
3.6.2 Map vs. Object
3.7 Date
3.7.1 Creating a Date Object
3.7.2 Retrieving Date Information
3.7.3 Working with UTC Time
3.7.4 Calculating Elapsed Time
3.8 Dynamic Typing
CHAPTER 4 Operators, Expressions and Statements
4.1 Operators
4.1.1 Arithmetic Operators
4.1.2 Assignment Operators
4.1.3 Comparison Operators
4.1.4 Logical Operators
4.1.5 Bitwise Operators
4.1.5.1 Two's Complement

4.1.6 Conditional (ternary) Operator


4.1.7 Nullish Coalescing Operator
4.1.8 Optional Chaining Operator
4.1.9 Comma Operator
4.1.10 Unary Operators
4.1.10.1 delete
4.1.10.2 typeof
4.1.10.3 void
4.1.11 Relational Operators
4.1.11.1 in
4.1.11.2 instanceof

4.1.12 Operator Precedence


4.2 Expressions
4.3 Statements
4.3.1 try...catch
4.3.2 throw
4.3.3 if
4.3.4 switch
4.3.5 Loops and Iteration
4.3.5.1 for
4.3.5.2 for...in
4.3.5.3 for...of
4.3.5.4 while
4.3.5.5 do...while
4.3.5.6 label
4.3.5.7 break
4.3.5.8 continue
4.4 Destructuring Assignment
4.4.1 Destructuring Array
4.4.2 Destructuring Object
4.4.3 Destructuring Map
4.4.4 Default Value in Destructuring
4.4.5 Ignoring Some Returned Values
4.4.6 Rest Syntax
4.4.7 Spread Syntax
CHAPTER 5 Function
5.1 Creating a Function
5.1.1 Function Declaration
5.1.2 Function Expression
5.1.3 Using Function Constructor
5.2 Calling Functions
5.3 The return Statement
5.4 Function Parameters
5.4.1 Default Parameters
5.4.2 Rest Parameters
5.5 The arguments Object
5.6 Recursive Function
5.7 Arrow Function Expressions
5.8 Immediately Invoked Function Expressions
5.9 Function Scope
5.10 Passing by Value vs. by Reference
5.11 Function Overloading
5.12 First-class Function
5.13 Context Binding
5.13.1 bind
5.13.2 call
5.13.3 apply
5.14 Closure
5.14.1 Closure Introduction
5.14.2 Private Properties and Methods using Closures
5.15 Callback Function
5.16 Generator Function
CHAPTER 6 this in JavaScript
6.1 this in Functions
6.2 this in Object’s Methods
6.2.1 Implicit Binding
6.2.2 Implicit Lost Binding
6.3 globalThis
CHAPTER 7 Some Advanced Concepts
7.1 Execution Context
7.2 Scope Chain
7.2.1 Scope Chain Introduction
7.2.2 Closure Scope
7.3 Hoisting
7.3.1 Function Hoisting
7.3.2 Variable hoisting
CHAPTER 8 Promise
8.1 Promise Introduction
8.1.1 Creating Promises
8.1.2 Promise Chain
8.1.3 Chaining After a Catch
8.1.4 Error Propagation
8.2 Composition
8.2.1 Parallel Composition
8.2.1.1 Promise.all()
8.2.1.2 Promise.allSettled()
8.2.1.3 Promise.race()
8.2.1.4 Promise.any()

8.2.2 Sequential composition


8.3 Execution Timing
8.3.1 Microtask Queue
8.3.2 Task Queue
8.3.3 Task vs. Microtask
8.4 Async Function
About the Author
Other Books by The Author
Please leave a Review/Thought on Amazon
Preface

JavaScript (often abbreviated JS) is the World's Most-Used Programming


Language that is one of the core technologies of the World Wide Web, alongside
HTML and CSS.
In my 15+ years of web programming, I've always used JavaScript as the
primary programming language regardless of any chosen front-end and back-end
technologies. The reason is that whatever technologies chosen, a web server
must return a HTML page including JavaScript and CSS code if any.
Websites use JavaScript on the client side for web page behavior and user
interaction. All major web browsers have a dedicated JavaScript engine to
execute the JS code on users' devices.
Book Audience
The book targets beginners to advanced developers. It gently introduces from
basic concepts to the most difficult topics, so it’s suitable for beginners to
experts who are passionate and willing to learn JavaScript.
How to Contact Us
My email is [email protected]. If you have any questions, please send
me an email. I am always happy to hear from people who’ve read my books. I’ll
try my best to answer every email I receive. The subject should be “[JavaScript
for Dummies] <Your subject here>” so that I could filter and answer your
questions quickly.
You can also follow me on the Amazon Author page,
https://www.amazon.com/Neo-D-Truman/e/B09P6LHL2M
Conventions Used in This Book
The following typographical conventions are used in this book:
Italic
Indicates new terms, URLs, email addresses, filenames, and file
extensions.
Bold
Indicates important concepts or UI items such as menu items and buttons
to be selected or clicked.
Constant width
Indicates computer code including statements, functions, classes, objects,
methods, properties, etc.
Constant width bold
Shows commands or other text that should be typed literally by the user.
Constant width italic
Shows text that should be replaced with user-supplied values or values
determined by context.

This element signifies a general note.

This element signifies a tip or suggestion.

This element indicates a warning or caution.


CHAPTER 1
Setting Up a Development Environment

The first step in setting up your development environment is choosing your code
editor. Almost editors have essential features like autocompletion, syntax
highlighting, formatting code, and smart refactoring. Using them helps you to
prevent potential mistakes. Other features such as line-by-line debugging could
be already available in the editor. However, we will use the Developer Tools of
web browsers while debugging JavaScript code. When finished setting up the
development environment, your coding will be amazingly easy and fast.

There are several tools you might consider so that you could
always choose any of them. No matter what tools you choose,
you’ll follow a similar process to learn the lessons.
1.1 Installing a Code Editor
In this book, we’ll use Visual Studio Code (shortened to VS Code) as the
code editor. You can download this free at
https://code.visualstudio.com/download
I chose VS Code because it was ranked as the most popular code editor
across languages on Stack Overflow.

1.1.1 Install Essential Extensions


VS Code supports adding extensions which help us coding faster and easier.
After installing the editor, let’s add these essential extensions:
● Prettier - Code formatter: Enforces a consistent style by parsing your
code and re-printing it with its own rules that take the maximum line
length into account, wrapping code when necessary.
● Path Intellisense: Visual Studio Code plugin that autocompletes
filenames and paths.
● Auto Close Tag: Automatically add HTML/XML close tag.
● ESLint: Finds and fixes problems in your JavaScript code.
● Rainbow Brackets: Provides rainbow colors for the round brackets, the
square brackets and the squiggly brackets.
● vscode-icons: File and folder icons for Visual Studio Code.

This is a screenshot of the above extensions for your reference when


searching them.
To install the extensions, just click on the Extensions icon (as the below
picture); then type a name into the Search Box; select the extension that you
want and click its Install button.
1.1.2 Installing Live Server
Live Server helps us launch a local development server with a live reload
feature for static or dynamic web pages.
To install the extension, just click on the Extensions icon; then type “Live
Server” into the Search Box; select the extension in the below picture and click
its Install button.
1.1.3 Enable Formatting Code on Save
We should enable the “Format on Save” feature to have best readable code.
Please follow these steps to turn it on:
● Go to the menu and select: File > Preferences > Settings
● Type “format on save” into the Search Box
● Then check the checkbox of “Editor: Format On Save” as the below
picture.
1.1.4 Disable Compact Folders
VS Code renders folders in its explorer in a compact form. In such a form,
single child folders will be compressed in a combined tree element. It’s useful
for Java package structures but not for React apps. Therefore, we should disable
“Compact Folders” feature by following these steps:
● Go to the menu and select: File > Preferences > Settings
● Type “compact folders” into the Search Box
● Then uncheck the checkbox of “Explorer: Compact Folders” as the
below picture.
1.1.5 Enable Word Wrap
To get rid of the horizontal scroll bar, we could turn on the “Word Wrap”
feature by following these steps:
● Go to the menu and select: File > Preferences > Settings
● Type “word wrap” into the Search Box
● Then select option “on” of “Editor: Word Wrap” as the below picture.
1.1.6 Set Tab Size Smaller
The default number of spaces a tab is 4. But according to my experience, we
need only 2 spaces. If you want to change the default setting like me, then follow
these steps:
● Go to the menu and select: File > Preferences > Settings
● Type “tab size” into the Search Box
● Then set the number of “Editor: Tab Size” to 2 as the below picture.
1.2 Using a Web Browser
We will use Google Chrome as a tool to run and debug our JavaScript code
since it is the most popular browser. Moreover, it also includes amazing
developer tools that will be described below.
Anyway, you could choose any browser as your favorite one and follow a
similar process.

1.2.1 Using Developer Tools in The Browser


The developer tools are usually presented as a tabbed group of panes at the
right or bottom of the web browser window. To open the developer tools in
Google Chrome, open the Chrome Menu in the upper-right-hand corner of the
browser window and select More Tools > Developer tools.
You can also use Shift + CTRL + J (on Windows/Linux), or Option + ⌘ + J
(on macOS) to open the tools.
The developer tools will either open up in a new window or within the
Chrome window as below picture.
We can undock it into a separate window or only change the dock side
following these steps:
● Click the “Customize and control Dev Tools” button in the upper-right-
hand corner.
● Then select the dock side that you want as the below picture.
1.2.2 Using Console Tool
If the Console tool wasn’t selected, you might have to select the Console tab.
We can check values of variables or try JavaScript code directly in the Console
tool as below.

You could see the errors that occur in your web page and the debugging
messages here. The Console panel is the one that shows the messages you output
with console.log() and any unhandled errors.
1.2.3 Using Source Tool
You may have to select the Sources tab in order to open the debugging tool.
It supports line-by-line debugging with breakpoints, reviewing the call stack, etc.
The details will be introduced with code later.
1.3 Preparing a Workspace
Please follow these steps to create your workspace:
● Create the Example folder, such as “D:\Example”
● Open the Visual Studio Code
● In the VS Code, select menu File > Open Folder…
● Select your folder, “D:\Example” in my example
● Hover mouse on the EXAMPLE folder, some icons will appear as
below.

● Click the New File icon.


● Type “index.html” and press Enter
● Then type below HTML code into the editor
<!DOCTYPE html>
<html>

<body>
<h2>Testing Page</h2>
<!-- Your HTML code here -->

<script src="./app.js"></script>
</body>

</html>
● Select menu File > Save in order to save change to the index.html file.
● Hover mouse on the EXAMPLE folder and click the New File icon.
● Type “app.js” and press Enter.
● Then type below JavaScript code into the editor.
"use strict";
console.log('This is your first JS code.');
● Select menu File > Save in order to save change to the app.js file.

You will change JavaScript code in the app.js file and HTML
code in the index.html file while learning this book.
Let’s try the code in a web browser, such as Google Chrome. You should
configure Google Chrome as your default web browser before doing the
following steps.
Right click on the index.html file from Explorer Window and click on Open
with Live Server.

This will launch a local web server with live reload feature for the file
index.html.

We could try to change our HTML code a little bit and save the index.html
file. Consequently, the web page will automatically load the change for us as this
screenshot.
Then, open the Console panel to see the debugging log.
CHAPTER 2
Introduction to JavaScript

JavaScript is a programming language which works mainly in web pages. It’s


also the world's most popular programming language which is a high-level, often
just-in-time compiled language that conforms to the ECMAScript standard.
It has dynamic typing, prototype-based object-orientation, and first-class
functions. It is multi-paradigm, supporting event-driven, functional, and
imperative programming styles. It has application programming interfaces
(APIs) for working with text, dates, regular expressions, standard data structures,
and the Document Object Model (DOM).

2.1 Text of The JavaScript Code


JavaScript is a case-sensitive language. This means that language keywords,
variables, function names, and other identifiers must always be typed with a
consistent capitalization of letters. For example, product, Product , and
PRODUCT are three distinct variable names.
2.2 JavaScript Comments
Comments are used to explain code, and to make it more readable. They can
also be used to prevent execution, when testing alternative code.

2.2.1 Single-line Comments


Single-line comments start with // . Text from // to the end of the line will
not be executed.
Example:
// Declare x and give it the value of 10
let x = 10;
let y = x * 2; // Declare y, give it the value of x * 2
2.2.2 Multi-line Comments
Multi-line comments start with /* and end with */ . Any text between /*
and */ will be ignored by JavaScript.
Example:
/**
* Returns sum of two input numbers
*
* @param {number} p1
* @param {number} p2
* @returns {number} sum of p1 and p2
*/
function sum(p1, p2) {
return p1 + p2;
}
2.3 Identifiers
In JavaScript, identifiers are used to name constants, variables, properties,
functions, and classes in JavaScript code. A JavaScript identifier must begin with
a letter, an underscore ( _ ), or a dollar sign ( $ ). Subsequent characters can be
letters, digits, underscores, or dollar signs.
These are some examples:
MY_CONSTANT
myVariable
variable01
_variable
$variable
2.4 Reserved Words
Reserved words are part of the JavaScript language and must not be used as
the names of constants, variables, labels, functions, or classes. We should avoid
using them although they can all be used as the names of properties within an
object.
These are the most popular reserved words in JavaScript:
arguments,
break,
case, catch, class, const, continue,
debugger, default, delete, do,
else, enum, eval, export, extends,
false, finally, for, function,
if, implements, import, in, instanceof, interface,
let,
new, null,
package, private, protected, public,
return,
static, super, switch,
this, throw, true, try, typeof,
var, void,
while, with,
yield
2.5 Semicolons
JavaScript uses the semicolon ( ; ) to separate statements from one another. In
JavaScript, you can usually omit the semicolon between two statements if those
statements are written on separate lines.

Using semicolons to explicitly mark the ends of statements


is the best practice. This coding style makes your code clear.
2.6 Using Strict Mode
The purpose of "use strict"; is to change previously accepted "bad syntax"
into real errors. As an example, in normal JavaScript, mistyping a variable name
creates a new global variable. In strict mode, this will throw an error, making it
impossible to accidentally create a global variable.
In this book, every example starts with "use strict"; except non-strict
examples.

2.6.1 Declaring Strict Mode


Strict mode is declared by adding "use strict"; to the beginning of a script or
a function. If declared at the beginning of a script, all code in the script will
execute in strict mode.
Example:
"use strict";
x = 10; // ReferenceError: x is not defined
If declared inside a function, only the code inside the function is in strict
mode.
Example:
x = 10; // This will not cause an error

function myFunction() {
"use strict";
y = 20; // ReferenceError: y is not defined
}
myFunction();
2.6.2 What Are Not Allowed in Strict Mode?
Using a variable without declaring it will cause a Reference Error (… is not
defined).
Example:
"use strict";
x = 10; // ReferenceError: x is not defined
Deleting a variable (or function) will cause a Syntax Error (delete of an
unqualified identifier in strict mode).
Example:
"use strict";
let x = 10;
delete x; // SyntaxError: Delete of an unqualified identifier in strict mode

function myFunc() { };
delete myFunc; // SyntaxError: Delete of an unqualified identifier in strict
mode
Duplicating a parameter name will cause a Syntax Error (Duplicate parameter
name not allowed in this context).
Example:
"use strict";
function myFunc(p1, p1) { }; // SyntaxError: Duplicate parameter name not
allowed in this context
Writing to a read-only property will cause a Type Error (cannot assign to
read-only property).
Example:
"use strict";
const obj = {};
Object.defineProperty(obj, "x", { value: 10, writable: false });
obj.x = 20; // TypeError: Cannot assign to read only property 'x' of object
Writing to a get-only property will cause a Type Error (cannot set property
which has only a getter).
Example:
"use strict";
const obj = { get x() { return 10 } };
obj.x = 20; // TypeError: Cannot set property x of Object which has only a
getter
For security reasons, eval() is not allowed to create variables in the scope
from which it was called.
Example:
"use strict"
eval("let x = 10");
console.log(x); // ReferenceError: x is not defined
2.6.3 The this Keyword in Strict Mode
In Strict Mode, the this keyword refers to the object that called the function.
If the object is not specified, functions in Strict Mode will return undefined .
Example:
"use strict";
function myFunction() {
console.log(this); // will print "undefined"
}
myFunction();

In non-strict mode, the above this will be the global object


(such as Window in the web browser).

Adding "use strict"; to the beginning of a script under


maintenance (existed code) would change behavior of the this
keyword and causes many errors.
2.7 JavaScript Modules
In JavaScript programming, we often separate our code into modules in order
to maintain it easily.
JavaScript modules are always in strict mode, so we don’t add "use strict"; to
the beginning of the JavaScript modules. In the below example, both app.js and
logger.js are JavaScript modules. Therefore, source code inside them is in strict
mode.
Example 1:
index.html
<!DOCTYPE html>
<html>
<body>
<h2>Testing Page</h2>

<script type="module" src="./app.js"></script>


</body>
</html>
app.js
import doLog, { LOG_TYPE, WARN_TYPE, ERROR_TYPE } from
'./logger.js';

console.log(doLog);
doLog('A message');
doLog('Another message', LOG_TYPE);
doLog('A warning message', WARN_TYPE);
doLog('An error message', ERROR_TYPE);
logger.js
export const LOG_TYPE = 'log';
export const WARN_TYPE = 'warn';
export const ERROR_TYPE = 'error';
function doLog(message, logType = LOG_TYPE) {
console[logType](message);
}
export default doLog;

There could be only one “ export default ” in a JavaScript


module.
When using a default export, we import it using its name like this.
import doLog from './logger.js';
On the other hand, normal exported items must be imported by destructuring
object as below.
import { LOG_TYPE, WARN_TYPE, ERROR_TYPE } from './logger.js';
Example 2:
index.html is not changed
constants.js
export const LOG_TYPE = 'log';
export const WARN_TYPE = 'warn';
export const ERROR_TYPE = 'error';

const DEFAULT_CONSTANT = 'default constant';


export default DEFAULT_CONSTANT;
app.js
import doLog from './logger.js';
import * as CONSTANTS from './constants.js';
import DEFAULT_CONSTANT from './constants.js';

console.log(doLog);
doLog('A message');
doLog('Another message', CONSTANTS.LOG_TYPE);
doLog('A warning message', CONSTANTS.WARN_TYPE);
doLog('An error message', CONSTANTS.ERROR_TYPE);
console.log(CONSTANTS.DEFAULT_CONSTANT); // undefined
console.log(CONSTANTS.default); // 'default constant'

console.log(DEFAULT_CONSTANT); // 'default constant'


logger.js
import { LOG_TYPE } from './constants.js';

export default function doLog(message, logType = LOG_TYPE) {


console[logType](message);
}
Example 3a:
index.html and logger.js are not changed
constants.js
export const LOG_TYPE = 'log';
export const WARN_TYPE = 'warn';
export const ERROR_TYPE = 'error';
helper.js
import doLog from './logger.js';
export default doLog;

export function sum(x, y) {


return x + y;
}
app.js
import doLog, { sum } from './helper.js';
import * as CONSTANTS from './constants.js';

console.log(doLog);
doLog('A message');
doLog('Another message', CONSTANTS.LOG_TYPE);
doLog('A warning message', CONSTANTS.WARN_TYPE);
doLog('An error message', CONSTANTS.ERROR_TYPE);
console.log(sum(10, 20)); // 30
Example 3b:
index.html, constants.js, logger.js, and app.js are as same as example 3a.
helper.js
export { default } from './logger.js'; // export default

export function sum(x, y) {


return x + y;
}
Example 3c:
index.html, constants.js, and logger.js are as same as example 3a.
helper.js
export { default as newLog } from './logger.js'; // not export default

export function sum(x, y) {


return x + y;
}
app.js
import { newLog, sum } from './helper.js';
import * as CONSTANTS from './constants.js';

console.log(newLog);
newLog('A message');
newLog('Another message', CONSTANTS.LOG_TYPE);
newLog('A warning message', CONSTANTS.WARN_TYPE);
newLog('An error message', CONSTANTS.ERROR_TYPE);

console.log(sum(10, 20));
CHAPTER 3
Types, Values, and Variables

Computer programs work by manipulating values, such as the number 10 or the


text “Hello world.” The kinds of values are known as types. When a program
needs to retain a value for future use, it assigns the value to a variable. Variables
have names, and they allow use of those names in the program to refer to values.

3.1 Variable Declaration


3.1.1 Declarations with Keywords
You can declare a variable in two ways:
● With the var keyword. This syntax can be used to declare both local and
global variables, depending on the execution context.
var x = 10;
● With the const or let keywords. These syntaxes can be used to declare
a block-scope local variable.
const MY_CONST = 30;
let y = 20;

Always use let or const to declare variables. Do not use var


since it will result in global variables and pollute the global
namespace.
An attempt to change value of a constant variable will cause a TypeError
exception.
Example:
"use strict";
const MY_CONST = 30;
MY_CONST = 60; // TypeError: Assignment to constant variable.
An attempt to access an undeclared variable will cause a ReferenceError
exception.
Example:
"use strict";
console.log(xyz) // ReferenceError: xyz is not defined
Duplicated declarations using var will not cause any errors but let and
const do.
Example:
"use strict";
var x = 10;
var x = 20;
console.log(x); // 20
Example:
"use strict";
let y = 10;
let y = 20; // SyntaxError: Identifier 'y' has already been declared
Example:
"use strict";
const MY_CONST = 30;
const MY_CONST = 60; // SyntaxError: Identifier 'MY_CONST' has
already been declared
3.1.2 Variable Scope
A variable declared with var will have function scope. If we declare it within
a function, it is available only within that function. Otherwise, it is available to
any other code in the current document (global scope).
Example:
"use strict";
if (true) {
var x = 10;
}
console.log(x); // 10

function test() {
var y = 20;
}
console.log(y); // ReferenceError: y is not defined
Variables declared with let or const have block scope. It means that the
variables are available only within a pair of curly brackets {} . Otherwise, it is
available to any other code in the current document (global scope).
Example:
"use strict";
if (true) {
let x = 10;
}
console.log(x); // ReferenceError: x is not defined
3.2 Primitive Data Types
In JavaScript, a primitive is data that is not an object and has no methods.
There are 7 primitive data types:
● string
● number
● bigint
● boolean
● undefined
● null
● symbol

We can use the typeof operator to check data type of variables.


Except for null and undefined , all primitive values have object equivalents
that wrap around the primitive values, called Primitive Wrapper Objects:
● String for the string primitive.
● Number for the number primitive.
● BigInt for the bigint primitive.
● Boolean for the boolean primitive.
● Symbol for the symbol primitive.

The wrapper's valueOf() method returns the primitive value.

It’s a best practice to avoid using Primitive Wrapper Objects


because they slow down your program.
3.2.1 undefined
undefined is a primitive value automatically assigned to variables that have
just been declared, or to arguments for which there are no actual arguments.
Example:
"use strict";
let x; //declare a variable but don't assign value to it
console.log(typeof x); // undefined
console.log("x =", x) // "x = undefined"

function test(y) {
console.log(typeof y);
console.log("y =", y)
}
test(); // invoke the function with no argument
// undefined
// "y = undefined"
3.2.2 null
In computer programming, a null value represents a reference that points to
a nonexistent or invalid object or address. The value null represents the
intentional absence of any object value.
In JavaScript, every object is derived from null value, and therefore typeof
operator returns object for it.
Example:
"use strict";
let x = null;
console.log(typeof x); // object
console.log("x =", x) // "x = null"
x = x + 10;
console.log("x =", x) // "x = 10"

If we don’t know initial value for a variable, we should assign


null to it. You will see the reason in the below example.
"use strict";
let x; // don't assign value to x
console.log(typeof x); // undefined
console.log("x =", x) // "x = undefined"
x = x + 10; // x is not 10 as expected
console.log("x =", x) // "x = NaN"
3.2.3 String
In programming languages, a string is a sequence of characters used to
represent text. In JavaScript, a string is treated as an array-like object, where
individual characters correspond to a zero-base index.

3.2.3.1 Creating Strings


Strings can be created as primitives, from string literals. String literals can be
specified using single( ' ) or double quotes( " ), which are treated identically.
Example:
"use strict";
let s1 = "Hello";
let s2 = 'World';
let s3 = "I'm Neo"; // I'm Neo
let s4 = 'Let\'s say "Hello"'; // Let's say "Hello"
console.log(typeof s1); // string
Strings can be created as objects using the String() constructor of the
Primitive Wrapper Object.
Example:
"use strict";
let s = new String("Hello");
console.log(typeof s); // object

(s1 === s) returns false since s1 is a primitive data type but


s is an object.
We can concatenate strings using the + operator.
Example:
"use strict";
let s = "Hello " + "world"; // 'Hello world'
Sometimes, your code could include string which is very long and you may
wish to break the string into multiple lines without affecting the actual string
content. You can use the backslash character ( \ ) at the end of each line to
indicate that the string will continue on the next line. Make sure there is no space
or any other character after the backslash except for a line break.
Example:
"use strict";
let longString = "This is a very long string \
which needs to wrap across multiple lines \
because otherwise, my code is unreadable."

3.2.3.2 Working with Strings


Let’s start with some text.
"use strict";
let s = "Hello world";
We use the length property of the string to determine the length of it.
s.length // 11
Obtaining portions of a string.
● Returns a new string containing characters of the calling string from (or
between) the specified index.
s.substring(2) // "llo world"
s.substring(2, 4) // "ll"
● Get last n characters of the string
s.slice(-3) // "rld" (last 3 characters)
● Returns an array of strings populated by splitting the calling string at
occurrences of the substring.
s.split(" ") // ["Hello", "world"]
Searching a string.
● Returns the index within the calling string object of the first occurrence
of search value, or -1 if not found
s.indexOf("o") // 4
s.indexOf("oo") // -1
s.indexOf("o", 5) // 7 (position of first "o" from 5-th character to the end of
string)
● Returns the index within the calling string object of the last occurrence
of search value, or -1 if not found.
s.lastIndexOf("o") // 7
s.lastIndexOf("o", 6) // 4 (position of last "o" from begin of string to 6-th
character)
Boolean searching functions
● Determines whether the calling string begins with the characters of the
search string.
s.startsWith("He") // true
● Determines whether a string ends with the characters of the search string.
s.endsWith("o") // false
● Determines whether the calling string contains a sub-string.
s.includes("ll") // true
Creating modified versions of a string.
● replace(searchFor, replaceWith) is used to replace the first occurrence
of searchFor using replaceWith . The searchFor string may be a string
or Regular Expression, and replaceWith may be a string or function.
s.replace("o", "x") // "Hellx world"
● replaceAll(searchFor, replaceWith) is used to replace all occurrences of
searchFor using replaceWith . The searchFor string may be a string or
Regular Expression, and replaceWith may be a string or function.
s.replaceAll("o", "x") // "Hellx wxrld"
● Returns the calling string value converted to lowercase.
s.toLowerCase() // "hello world"
● Returns the calling string value converted to uppercase.
s.toUpperCase() // "HELLO WORLD"
Returns a string consisting of the elements of the object repeated count times.
"^_^ ".repeat(3) // result = "^_^ ^_^ ^_^ "
Inspecting individual characters of a string
s.charAt(0) // "H"
s.charAt(s.length - 1) // "d"
s[1]; // "e"
s[s.length - 1]; // "d"
String padding functions
● Pads the current string from the start with a given string and returns a
new string of the length
"x".padStart(3) // " x" (add spaces on the left to a length of 3)
"x".padStart(3, "0") // "00x" (add "0" on the left to a length of 3)
● Pads the current string from the end with a given string and returns a new
string of the length
"x".padEnd(3) // "x " (add spaces on the right to a length of 3)
"x".padEnd(3, "-") // "x--" (add dashes on the right to a length of 3)
Space trimming functions.
● Trims whitespace from the beginning and end of the string.
" test ".trim() // "test"
● Trims whitespace from the beginning of the string.
" test ".trimStart() // "test "
● Trims whitespace from the end of the string.
" test ".trimEnd() // " test"

The above methods such as replace() , trim() , etc. do not


change value of the s variable. We must assign the new value
to it in order to keep the change or store new value in another
variable.

3.2.3.3 Regular Expressions


A regular expression is a sequence of characters that specifies a search
pattern in text. Usually, such patterns are used by string-searching algorithms for
"find" or "find and replace" operations on strings, or for input validation.
Creating a regular expression.
● Using a regular expression literal, which consists of a pattern enclosed
between slashes( / ).
Example:
"use strict";
const reg = /<p>[^>]*<\/p>/;
const re = /<p>[^>]*<\/p>/g; // Global search

If the regular expression remains constant, using regular


expression literals can improve performance.
● Calling the constructor function of the RegExp object.
Example:
"use strict";
const reg = new RegExp("<p>[^>]*<\/p>");
const re = new RegExp("<p>[^>]*<\/p>", "g"); // Global search

Use the constructor function when you know the regular


expression pattern will be changing, or you don't know the
pattern and are getting it from another source, such as user
input.

Regular expression patterns are out of scope of this book. You


can use some online tools to learn, build, and test your Regular
Expressions or read another book of the Author.
Regular expressions are used with the exec() and test() methods of
RegExp .
● Let’s use the below string to test our regular expressions (the above reg
and re ).
const str = "<h1>This is a heading</h1>\
<p>This is the first paragraph</p>\
<p>This is the second paragraph</p>";
● The exec() method executes a search for a match in a specified string.
Returns a result array, or null .
console.log(re.exec(str)); // ['<p>This is the first paragraph</p>', index: 30]
console.log(re.exec(str)); // ['<p>This is the second paragraph</p>', index:
68]
console.log(re.exec(str)); // null
● The test() method executes a search for a match between a regular
expression and a specified string. Returns true or false .
console.log(re.test(str)); // true
console.log(re.test(str)); // true
console.log(re.test(str)); // false
Regular expressions are used with the match(), matchAll(), replace(),
replaceAll(), search() , and split() methods of String .
● The match() method retrieves the result of matching a string against a
regular expression.
console.log(str.match(reg)); // ['<p>This is the first paragraph</p>', index:
30]
● The matchAll() method returns an iterator of all results matching a
string against a regular expression, including capturing groups.
const array = [...str.matchAll(re)];
console.log(array.length); // 2
console.log(array[0]); // ['<p>This is the first paragraph</p>', index: 30]
console.log(array[1]); // ['<p>This is the second paragraph</p>', index: 68]
● The replace() method returns a new string with one or all matches (if
use global flag) of a pattern replaced by a replacement.
console.log(str.replace(reg, "<div>New element</div>")); // <h1>This is a
heading</h1> <div>New element</div> <p>This is the second
paragraph</p>
● The replaceAll() method returns a new string with all matches of a
pattern replaced by a replacement.
console.log(str.replaceAll(re, "<div>New element</div>")); // <h1>This is a
heading</h1> <div>New element</div> <div>New element</div>
● The search() method executes a search for a match between a regular
expression and a string. It returns index of the first match between the
regular expression and the given string, or -1 if no match was found.
console.log(str.search(re)); // 30
● The split() method divides a string into an ordered list of substrings,
puts these substrings into an array, and returns the array. The division is
done by searching for a pattern; where the pattern is provided as the first
parameter in the method's call.
const myRe = /<p>.*first[^>]*<\/p>/g; // Global search
console.log(str.split(myRe)); // ['<h1>This is a heading</h1>', '<p>This is
the second paragraph</p>']

3.2.3.4 Template Literals


We can use Template Literals for clearer string concatenation. Template
Literals are literals delimited with backticks ( ` ), allowing embedded
expressions called substitutions. There are two types of Template Literals:
● Untagged template literals result in strings, which makes them useful for
string interpolation (and multiline strings, since unescaped newlines are
allowed).
Example:
"use strict";
let x = 10;
let y = 20;
let s = `Thirty is ${x + y} and not ${2 * x + y}.`;
// "Thirty is 30 and not 40."

In certain cases, nesting a template is the easiest way to have


configurable strings. Within a backtick template, it is simple to
allow inner backticks by using them inside a placeholder ${ }
within the template.
● Tagged template literals call a function (the tag function) with an array of
any text segments from the literal followed by arguments with the values
of any substitutions.
Example:
"use strict";
function myTaggedTemplate(strings, person, age) {
let str0 = strings[0]; // "They said that "
let str1 = strings[1]; // " was "
let str2 = strings[2]; // "."
let ageStr;
if (age < 20) {
ageStr = 'a teenager';
} else {
ageStr = 'an adult';
}
return `${str0}${person}${str1}${ageStr}${str2}`;
}

let person = 'Tom';


let age = 30;
let str = myTaggedTemplate`They said that ${person} was ${age}.`;
// They said that Tom was an adult.

3.2.3.5 Escape Sequences


Special characters can be encoded using escape sequences.
\0 : null character
\' : single quote
\" : double quote
\\ : backslash
\n : newline
\r : carriage return
\v : vertical tab
\t : tab
\b : backspace
\f : form feed
\uXXXX …where XXXX is exactly 4 hex digits in the range 0000–FFFF;
Unicode code point between U+0000 and U+FFFF (the Unicode Basic
Multilingual Plane)
\u{X} …\u{XXXXXX} …where X…XXXXXX is 1–6 hex digits in the
range 0–10FFFF; Unicode code point between U+0000 and U+10FFFF
(the entirety of Unicode)
\xXX …where XX is exactly 2 hex digits in the range 00–FF; Unicode code
point between U+0000 and U+00FF (the Basic Latin and Latin-1 Supplement
blocks; equivalent to ISO-8859-1).
3.2.4 Number
In JavaScript, number is a numeric data type in the double-precision 64-bit
floating point format. In addition to representing floating-point numbers, the
number type has three special values: +Infinity , -Infinity , and NaN (Not a
Number). To check for the largest available value or smallest available value,
you can use the constants Number.MAX_VALUE or Number.MIN_VALUE.

Floating point arithmetic is not always 100% accurate.


"use strict";
let x = 0.1 + 0.2; // x = 0.30000000000000004
You are also able to check if an integer number is safe to use using
Number.isSafeInteger() as well as Number.MAX_SAFE_INTEGER and
Number.MIN_SAFE_INTEGER (Integers are accurate up to 15 digits).

Beyond the safe range, integers in JavaScript are not safe


anymore and will be a double-precision floating point
approximation of the value.

In order to work with a large integer, we use another numeric


type known as BigInt (please refer to the next section).

3.2.4.1 Creating Numbers


We can use numeric literals to create numbers.
Example:
"use strict";
let x = 10;
console.log(typeof x); // number
let y = 1.23;
console.log(typeof y); // number
Numbers can be created as objects using the Number() constructor of the
Primitive Wrapper Object.
Example:
"use strict";
let z = new Number("10");
console.log(typeof z); // object

(x === z) returns false since x is a primitive data type but


z is an object.
There are two global methods can help to convert strings to numbers.
● parseFloat() will parses a string and returns a floating point number
"use strict";
let f = parseFloat("1.23"); // f = 1.23
● parseInt() will parses a string and returns an integer
"use strict";
let i = parseInt("1.23"); // i = 1
let ii = parseInt("10"); // ii = 10

3.2.4.2 Strings and Numbers


Converting numbers to strings.
"use strict";
let x = 10;
let y = x.toString(); // y = "10" (string)
Adding a number and a string will return a string.
"use strict";
let x = 10;
let y = "20";
let z = x + y;
console.log(typeof z); // string
console.log(z); // "1020"

Different orders of numbers and strings will return different


results as the + operator works from left to right.
"use strict";
let x = 10;
let y = 20;
let z = "30";

let r01 = x + y + z;
// x + y returns 30 (number)
// then 30 + "30" (z) returns "3030"
console.log(typeof r01); // string
console.log(r01); // "3030"

let r02 = z + y + x;
// z + y return "3020" (string)
// then "3020" + 10 (x) returns "302010"
console.log(typeof r02); // string
console.log(r02); // "302010"
JavaScript will try to convert strings to numbers in all numeric operations
since the result cannot be a string. This is type coercion in JavaScript.
"use strict";
let x = "20";
let y = "10";

let r01 = x - y;
console.log(typeof r01); // number
console.log(r01); // 10

let r02 = x * y;
console.log(typeof r02); // number
console.log(r02); // 200

let r03 = x / y;
console.log(typeof r03); // number
console.log(r03); // 2
The + operator will concatenate strings and return a string as
the result.

3.2.4.3 Arithmetic in JavaScript


The Number.isInteger() method determines whether the passing value is an
integer.
"use strict";
Number.isInteger(1.23); // false
Number.isInteger(123); // true
Number.isInteger("123"); // false
The toFixed() method Number formats a number using fixed-point
notation.
"use strict";
let x = 123.456;
let y = x.toFixed(2);
console.log(typeof y); // string
console.log(y); // "123.46"
In order to works with the Number type, we often use Math (It doesn't
work with BigInt ). Math is a JavaScript built-in object that all properties and
methods of it are static. These are its popular methods:
● Math.round(x) returns the value of the number x rounded to the nearest
integer.
"use strict";
let x = Math.round(1.49); // x = 1
let y = Math.round(1.5); // y = 2
● Math.ceil(x) returns the smallest integer greater than or equal to x.
"use strict";
let x = Math.ceil(1.1); // x = 2
● Math.floor(x) returns the largest integer less than or equal to x.
"use strict";
let x = Math.floor(1.9); // x = 1
● Math.trunc(x) returns the integer portion of x, removing any fractional
digits.
"use strict";
let x = Math.trunc(1.9); // x = 1
let y = Math.trunc(1.1); // y = 1
● Math.abs(x) returns the absolute value of x.
"use strict";
let x = Math.abs(-10); // x = 10
● Math.max([x[, y[, …]]]) returns the largest of zero or more numbers.
"use strict";
let x = Math.max(5, 2, 1, 6, 3); // x = 6
● Math.min([x[, y[, …]]]) returns the smallest of zero or more numbers.
"use strict";
let x = Math.min(5, 2, 1, 6, 3); // x = 1
● Math.random() returns a pseudo-random number between 0 and 1.
"use strict";
let x = Math.random(); // x = 0.5129102413654056
x = Math.random(); // x = 0.3288240687193489
● Math.pow(x, y) returns base x to the exponent power y (that is, x**y).
"use strict";
let x = Math.pow(2, 3); // x = 2**3 = 8
● Math.cbrt(x) returns the cube root of x.
"use strict";
let x = Math.cbrt(8); // x = 2
● Math.sqrt(x) returns the square root of x.
"use strict";
let x = Math.sqrt(9); // x = 3

3.2.4.4 Not a Number


The global NaN property is a value representing Not-A-Number. There are
five different types of operations that return NaN:
● Number cannot be parsed, e.g., parseInt("abc")
● Math operation where the result is not a real number, e.g., Math.sqrt(-1)
● Operand of an argument is NaN (e.g., 7 * NaN )
● Indeterminate form (e.g., 0 * Infinity , or undefined + undefined )
● Any operation that involves a string and is not an addition operation,
e.g., "abc" / 2
We use isNaN() to determine whether a value is NaN.
"use strict";
isNaN("abc"); // true
isNaN("123"); // false

3.2.4.5 Infinity
Infinity is a property of the global object. The initial value of Infinity is
Number.POSITIVE_INFINITY . The value Infinity (positive infinity) is greater
than any other number.
"use strict";
console.log(1 / Infinity); // 0
console.log(1 / 0); // Infinity
3.2.5 BigInt
The bigint type is a numeric primitive in JavaScript that can represent
integers with arbitrary precision. With bigint , you can safely store and operate
on large integers even beyond the safe integer limit for Number .

A bigint value cannot be used with methods in the built-in


Math object and cannot be mixed with a Number value in
operations.
A bigint is created by appending n to the end of an integer literal, or by
calling the BigInt() function (without the new operator) and giving it a string
value.
"use strict";
let x = 12345678901234567890n;
console.log(typeof x); // bigint
let y = BigInt("12345678901234567890");
console.log(typeof y); // bigint

Giving an unsafe integer, which is a large number, to the


BigInt() will cause an error as in the below example.
let z = BigInt(12345678901234567890);
// z is not 12345678901234567890n
console.log(z); // 12345678901234567168n
3.2.6 Boolean
In computer science, a boolean is a logical data type that can have only the
values true or false .
Example:
"use strict";
let x = true;
console.log(typeof x); // boolean

let y = new Boolean(false);


console.log(typeof y); // object
In JavaScript, boolean conditionals are often used to decide which sections of
code to execute (such as in if statements) or repeat (such as in for loops).
If the input value is 0, null, undefined, NaN , or an empty string ( '' ), the
boolean operation will return false .
Example:
"use strict";
let booleanConditional = null;
if (booleanConditional) {
console.log("resolved to true");
}
else {
// the below line will be printed out
console.log("resolved to false");
}
3.2.7 Symbol
In JavaScript, a symbol value is created by invoking the function Symbol()
function (without the new operator), which dynamically produces an
anonymous, unique value. Symbol can have an optional description, but for
debugging purposes only. A symbol may be used as an object property.
Example:
"use strict";
let sym1 = Symbol("My Symbol");
console.log(typeof sym1); // symbol

Symbols are guaranteed to be unique. Even if we create many


symbols with the same description, they are different values.
let sym2 = Symbol("My Symbol");
console.log(sym1 === sym2) // false

Symbols don't auto-convert to strings.


"use strict";
let sym = Symbol("My Symbol");
alert(sym); // TypeError: Cannot convert a Symbol value to a string

If we really want to show a symbol, we need to call


.toString() on it.
"use strict";
let sym = Symbol("My Symbol");
alert(sym.toString()); // Symbol(My Symbol)
There is a global symbol registry holding all available symbols. The methods
that access the registry are Symbol.for() and Symbol.keyFor() . The method
Symbol.for(tokenString) returns a symbol value from the registry, and
Symbol.keyFor(symbolValue) returns a token string from the registry.
Example:
"use strict";
let sym1 = Symbol.for("My Symbol");
let sym2 = Symbol.for("My Symbol");
console.log(sym1 === sym2); // true
console.log(Symbol.keyFor(sym2)); // My Symbol
3.3 Object
In JavaScript, objects can be seen as a collection of properties. With the
object literal syntax, a limited set of properties are initialized; then properties can
be added and removed. Property values can be values of any type, including
other objects, which enables building complex data structures. Properties are
identified using key values. A key value is either a String value or a Symbol
value.
3.3.1 Creating Objects
An object literal is a list of zero or more pairs of property names and
associated values of an object, enclosed in curly braces ( {} ). The following is
an example of an object literal. Properties of the object can be primitive data
types, functions (called methods of the object) or objects.
"use strict";
let obj = {
firstName: "Neo",
lastName: "D. Truman",
getFullname: function() {
return this.firstName + " " + this.lastName;
}
};
console.log(typeof obj); // object
console.log(obj.getFullname()); // Neo D. Truman
In the above example, the firstName property of the obj object has value
"Neo" . There are two ways to get values of the properties:
● Object.PropertyName
console.log(obj.firstName); // "Neo"
● Object[StringOfPropertyName]
console.log(obj["firstName"]); // "Neo"
let propertyName = "lastName";
console.log(obj[propertyName]); // "D. Truman"
We can create an empty object and then add its properties like this.
"use strict";
let o1 = {};
o1.firstName = "Neo";

let o2 = new Object();


o2.lastName = "D. Truman";
3.3.2 Object.assign()
The Object.assign() function will use an existed object template to create the
new object. These two objects are different as they are located in two memory
areas.
"use strict";
let o1 = { name: "Neo" };
let o2 = Object.assign({}, o1);
o2.name = "Neo D. Truman";
console.log(o1); // { name: "Neo" }
console.log(o2); // { name: "Neo D. Truman" }
3.3.3 Accessor Properties
Sometimes it is desirable to allow access to a property that returns a
dynamically computed value, or you may want to reflect the status of an internal
variable without requiring the use of explicit method calls. This can be
accomplished with the use of a getter. The get syntax binds an object property
to a function that will be called when that property is looked up. The function
must have exactly zero parameters.
In JavaScript, a setter can be used to execute a function whenever a specified
property is attempted to be changed. Setters are most often used in conjunction
with getters to create a type of pseudo-property. It is not possible to
simultaneously have a setter on a property that holds an actual value. The set
syntax binds an object property to a function to be called when there is an
attempt to set that property. The function must have exactly one parameter.
"use strict";
const obj = {
logs: [],
get message() {
if (this.logs.length === 0) {
return undefined;
}
return this.logs[this.logs.length - 1];
},
set message(msg) {
this.logs.push(msg);
}
};
console.log(obj.message); // undefined
obj.message = "hi";
obj.message = "hello";
console.log(obj.message); // "hello"
console.log(obj.logs); // ["hi", "hello"]
3.3.4 Global Object
A global object is an object that always exists in the global scope. In
JavaScript, there's always a global object defined. In a web browser, when
scripts create global variables defined with the var keyword, they're created as
members of the global object (this does not happen with the let keyword).

Variables and functions which are not in any function are


global.
The window object is the Global Object in the web browsers. Any global
variables or functions can be accessed as properties of the window object.
"use strict";
var x = 10;
let y = 20;
console.log(window.x); // 10
console.log(window.y); // undefined

function test() {
console.log("Hi");
}
window.test(); // "Hi" (the same as invoking test())
3.3.5 Upgrading Object Literal
3.3.5.1 Shorthand Properties
We can use variables to define object’s properties. And in case that the
property name is same as name of the variable, we can use the shorthand form as
below.
"use strict";
let firstName = 'Neo';
let lastName = 'D. Truman';

let user = {
firstName,
lastName,
email: "[email protected]"
}
console.log(user);
// {
// firstName: 'Neo',
// lastName: 'D. Truman',
// email: '[email protected]'
// }

3.3.5.2 Shorthand Methods


In order to define methods of an object quickly, we can use the shorthand
methods by omitting “ : function ”.
"use strict";
let user = {
name: 'Neo D. Truman',
logInfo() {
console.log(this.name);
}
}
user.logInfo(); // Neo D. Truman
console.log(user);
// {name: 'Neo D. Truman', logInfo: f}

3.3.5.3 Computing Property Keys


We can compute the property keys like this example.
"use strict";
let objKey = 'name';
let country = {
[objKey]: 'Canada',
[objKey + 'Abb']: 'ca'
}
console.log(country);
// {name: 'Canada', nameAbb: 'ca'}
3.3.6 Prototype of Objects
Each object has a private property which holds a link to another object called
prototype. The Object.create() method creates a new object using an existing
object as the prototype of the newly created object.
Example:
"use strict";
let obj1 = {
firstName: 'Neo',
lastName: 'D. Truman',
logFullname: function() {
console.log(this.firstName + ' ' + this.lastName);
}
};

let obj2 = Object.create(obj1);


obj2.firstName = 'John';
obj2.logFullname(); // John D. Truman
We can open the Console panel to see the result and check value of the obj2
variable by typing “obj2” at cursor (>), then press Enter. Try to expand the
object and its prototype ([[Prototype]]) in order to see more information as this
picture.
In the above picture, we can see Prototype of obj2 is the obj1 object.
The following describes how obj2.logFullname(); can print “John D.
Truman” to the console panel.
● Since obj2 doesn’t have the logFullname() method, JavaScript engine
did search the method in obj2 ’s prototype.
● Because prototype of obj2 is the obj1 object, logFullname() method
was found and invoked.
● In the method, this was obj2 so that this.firstName is “John”.
● As obj2 doesn’t have property lastName , JavaScript engine again
searched the lastName property in obj2 ’s prototype.
● Because prototype of obj2 is the obj1 object, the lastName property
was found (“D. Truman”) and used. As a result, “John D. Truman” was
printed to the Console panel.
3.3.7 Prototype Chain
JavaScript is a bit confusing for developers experienced in class-based
languages (like Java or C++), as it does not provide a class implementation
officially (the class keyword is introduced in ES2015, but is syntactical sugar,
JavaScript remains prototype-based).
When it comes to inheritance, JavaScript only has one construct that is
object. Each object has a private property which holds a link to another object
called prototype. That prototype object has a prototype of its own, and so on
until an object is reached with null as its prototype. By definition, null has no
prototype, and acts as the final link in this prototype chain.
Nearly all objects in JavaScript are instances of Object which sits just below
null on the top of a prototype chain.
The below example demonstrates a prototype chain.
"use strict";
let obj1 = {
firstName: 'Neo',
lastName: 'D. Truman',
logFullname: function() {
console.log(this.firstName + ' ' + this.lastName);
}
};

let obj2 = Object.create(obj1);


obj2.firstName = 'John';

let obj3 = Object.create(obj2);


obj3.age = 39;
obj3.logFullname(); // John D. Truman
We can open the Console panel to see the result and check value of the obj3
variable by typing “obj3” at cursor (>), then press Enter. Try to expand the
object and its prototype ([[Prototype]]) in order to see more information as this
picture.
In the above picture, we can see prototype of obj3 is the obj2 object and
prototype of obj2 is the obj1 object.
The following describes how obj3.logFullname(); can print “John D.
Truman” to the console panel.
● Since obj3 doesn’t have the logFullname() method, JavaScript engine
did search the method in obj3 ’s prototype which is obj2 .
● Since obj2 doesn’t have the logFullname() method, JavaScript engine
searched the method in obj2 ’s prototype.
● Because prototype of obj2 is the obj1 object, logFullname() method
was found and invoked.
● In the method, this was obj3 but obj3 doesn’t have properties
firstName and lastName . Therefore, JavaScript engine again searched
the properties in obj3 ’s prototype which is obj2 .
● The firstName property was found in obj2 so that this.firstName was
resolved to “John”.
● As obj2 doesn’t have property lastName , JavaScript engine again
searched the lastName property in obj2 ’s prototype.
● Because prototype of obj2 is the obj1 object, the lastName property
was found (“D. Truman”) and used. As a result, “John D. Truman” was
printed to the console panel.

Searching through prototype chain could happen again and


again until it reaches null since null has no prototype.
3.4 Array
The Array object, as in other programming languages, enables storing a
collection of multiple items under a single variable name, and has members for
performing common array operations.
In JavaScript, arrays have the following core characteristics:
● They are resizable and can contain a mix of different data types.
● They are not associative arrays and so, array elements must be accessed
using integers as indexes.
● They are zero-indexed: the first element of an array is at index 0, the
second is at index 1, and so on — and the last element is at the value of
the array's length property minus 1.
● The array-copy operations only create shallow copies.
3.4.1 Creating Arrays
There are three ways to create a new array:
● using array literal []
"use strict";
let emptyArray = [];
let things = ["book", "pen", 10, "eraser"];
console.log(typeof things); // object
console.log(things); // ['book', 'pen', 10, 'eraser']
● using the Array() constructor
"use strict";
let emptyArray = new Array();
let things = new Array("book", "pen", 10, "eraser");
console.log(typeof things); // object
console.log(things); // ['book', 'pen', 10, 'eraser']
● using String.prototype.split() to build array from a string
"use strict";
let things = "book, pen, 10, eraser".split(", ");
console.log(typeof things); // object
console.log(things); // ['book', 'pen', '10', 'eraser']
3.4.2 Copying an Array
Let’s try to copy this array which contains an object.
"use strict";
let things = ["book", "pen", { index: 10 }];
There are three ways to create a new array from an existing array:
● using spread syntax
let thingsCopy = [...things];
● using the from() method
let thingsCopy = Array.from(things);
● using the slice() method
let thingsCopy = things.slice();

All built-in array-copy operations above just create shallow


copies. As a result, when you change either the source or the
copy, you may also cause the other object to change too.
// change some items in the copied array
thingsCopy[1] = "pencil";
thingsCopy[2].index = 20;
// check all items of the arrays
console.log(things); // ['book', 'pen', { index: 20 }]
console.log(thingsCopy); // ['book', 'pencil', { index: 20 }]
In the above example, we can see that the index property has been changed
to 20 in both the things and thingsCopy arrays. And this behavior is not our
expectation.

In order to avoid the shallow copies, we use JSON.stringify()


to convert the array to a JSON string, and then use
JSON.parse() to convert the string back into a new array that's
completely independent from the original array.
"use strict";
let things = ["book", "pen", { index: 10 }];
let thingsDeepCopy = JSON.parse(JSON.stringify(things));
// change some items in the copied array
thingsDeepCopy[1] = "pencil";
thingsDeepCopy[2].index = 20;
// check all items of the arrays
console.log(things); // ['book', 'pen', { index: 10 }]
console.log(thingsDeepCopy); // ['book', 'pencil', { index: 20 }]
3.4.3 Accessing an Array Item
We access an array item by its index.
"use strict";
let things = ["book", "pen", 10, "eraser"];
console.log(typeof things[0]); // string
console.log(things[0]); // "book"

console.log(typeof things[2]); // number


console.log(things[2]); // 10

console.log(things[things.length - 1]); // "eraser"


console.log(things[9]); // undefined
3.4.4 Checking Item’s Existence
We can find the first index (first occurrence) of an item in an array using the
indexOf() method. If it returns -1 , the input is not an item of the array.
"use strict";
let things = ["book", 10, "pen", 10, "eraser"];
things.indexOf(10); // 1
things.indexOf("pencil"); // -1
We can also find the last index (last occurrence) of an item in an array using
the lastIndexOf() method. If it returns -1 , the input is not an item of the array.
"use strict";
let things = ["book", 10, "pen", 10, "eraser"];
things.lastIndexOf(10); // 3
things.lastIndexOf(100); // -1
We can check if an array contains a certain item using the includes()
method.
"use strict";
let things = ["book", "pen", 10, "eraser"];
let result = things.includes("pencil"); // false
console.log(typeof result); // boolean
3.4.5 Adding Array Items
This example uses the push() method to append an item to an array.
"use strict";
let things = ["book", "pen", 10, "eraser"];
const newLength = things.push("pencil");
console.log(things); // ['book', 'pen', 10, 'eraser', 'pencil']
console.log(newLength); // 5
The below example uses the unshift() method to add a new item to an array
- making it the new first item in the array.
"use strict";
let things = ["book", "pen", 10, "eraser"];
const newLength = things.unshift("pencil");
console.log(things); // ['pencil', 'book', 'pen', 10, 'eraser']
console.log(newLength); // 5
We can merge multiple arrays together using the concat() method.
"use strict";
let things = ["book", "pen", "eraser"];
const moreThings = ["pencil", "notebook"];
const result = things.concat(moreThings);
console.log(result); // ['book', 'pen', 'eraser', 'pencil', 'notebook']

The concat() method also creates a shallow copy. As a


result, when you change either the source or the copy, you
might also cause the other object to change too.
3.4.6 Removing Array Items
The below example uses the pop() method to remove the last item from an
array.
"use strict";
let things = ["book", "pen", 10, "eraser"];
const removedItem = things.pop();
console.log(removedItem); // "eraser"
console.log(things); // ['book', 'pen', 10]
This example uses the shift() method to remove the first item from the fruits
array.
"use strict";
let things = ["book", "pen", 10, "eraser"];
const removedItem = things.shift();
console.log(removedItem); // "book"
console.log(things); // ['pen', 10, 'eraser']
This example uses the splice() method to remove multiple items from an
array by specifying the start index, along with a count of the number of total
items to remove.
"use strict";
let things = ["book", "pen", 10, "eraser"];
const start = 1;
const deleteCount = 2;
const removedItems = things.splice(start, deleteCount);
console.log(things); // ['book', 'eraser']
console.log(removedItems); // ['pen', 10]
We can remove multiple items from the beginning of an array using the
splice() method.
"use strict";
let things = ["book", "pen", 10, "eraser", "pencil"];
const start = 0;
const deleteCount = 3;
// remove 3 items from the beginning
const removedItems = things.splice(start, deleteCount);
console.log(removedItems); // ['book', 'pen', 10]
console.log(things); // ['eraser', 'pencil']
We can remove multiple items from the end of an array using the splice()
method.
"use strict";
let things = ["book", "pen", 10, "eraser", "pencil"];
// remove 3 items from the end
const removedItems = things.splice(-3);
console.log(removedItems); // [10, 'eraser', 'pencil']
console.log(things); // ['book', 'pen']
We can truncate an array down to just its first N items using the splice()
method.
"use strict";
let things = ["book", "pen", 10, "eraser", "pencil"];
const removedItems = things.splice(3); // keep first 3 items
console.log(removedItems); // ['eraser', 'pencil']
console.log(things); // ['book', 'pen', 10]
We can remove a single item by index using the splice() method by
specifying the index position of the removed item.
"use strict";
let things = ["book", "pen", 10, "eraser"];
const start = things.indexOf(10);
const deleteCount = 1;
const removedItems = things.splice(start, deleteCount);
console.log(things); // ['book', 'pen', 'eraser']
console.log(removedItems); // [10]
3.4.7 Array Iteration
We can iterate over an array using a for...of loop.
"use strict";
let things = ["book", "pen", 10, "eraser"];
for (const thing of things) {
console.log(thing);
}
// book
// pen
// 10
// eraser
This example uses the forEach() method to call a function on each element
in an array.
"use strict";
let things = ["book", "pen", 10, "eraser"];
things.forEach(function (item, index, array) {
console.log(item, index);
});
// book 0
// pen 1
// 10 2
// eraser 3
3.4.8 Popular Array’s Methods
3.4.8.1 every()
The every() method tests whether all elements in the array pass the test
implemented by the provided function. It returns a Boolean value.
Example 1:
"use strict";
function isEven(value) {
return value % 2 == 0;
}

console.log([1, 2, 3, 4].every(isEven)); // false


console.log([0, 2, 4].every(isEven)); // true
Example 2:
"use strict";
const isSubset = (array1, array2) => array2.every(element =>
array1.includes(element));

console.log(isSubset([1, 2, 3, 4, 5, 6], [5, 2, 6])); // true


console.log(isSubset([1, 2, 3, 4, 5, 6], [5, 6, 7])); // false

3.4.8.2 fill()
The fill() method changes all elements in an array to a static value , from a
start index (default 0) to an end index (default array.length ). It returns the
modified array.
fill(value, start, end)
Example:
"use strict";
[1, 2, 3, 4, 5].fill(6); // [6, 6, 6, 6, 6]
[1, 2, 3, 4, 5].fill(6, 2); // [1, 2, 6, 6, 6]
[1, 2, 3, 4, 5].fill(6, 2, 4); // [1, 2, 6, 6, 5]

3.4.8.3 filter()
The filter() method creates a new array with all elements that pass the test
implemented by the provided function. If no elements pass the test, an empty
array will be returned.
Example:
"use strict";
function isEven(value) {
return value % 2 == 0;
}

let filteredArr = [1, 2, 3, 4, 5].filter(isEven);


console.log(filteredArr); // [2, 4]

3.4.8.4 find()
The find() method returns the first element in the provided array that
satisfies the provided testing function. If no values satisfy the testing function,
undefined is returned.
Example:
"use strict";
function isEven(value) {
return value % 2 == 0;
}

let foundValue = [1, 2, 3, 4, 5].find(isEven);


console.log(foundValue); // 2

3.4.8.5 findIndex()
The findIndex() method returns the index of the first element in the array
that satisfies the provided testing function. Otherwise, it returns -1 , indicating
that no element passed the test.
Example:
"use strict";
function isEven(value) {
return value % 2 == 0;
}
let foundIndex = [1, 2, 3, 4, 5].findIndex(isEven);
console.log(foundIndex); // 1

3.4.8.6 join()
The join(separator) method creates and returns a new string by
concatenating all of the elements in an array (or an array-like object), separated
by commas (default value of separator ) or a specified separator string. If the
array has only one item, then that item will be returned without using the
separator .
Example:
"use strict";
let things = ["book", "pen", 10, "eraser"];

console.log(things.join()); // book,pen,10,eraser
console.log(things.join('; ')); // book; pen; 10; eraser

3.4.8.7 map()
The map() method creates a new array populated with the results of calling a
provided function on every element in the calling array.
Example:
"use strict";
function doubleIt(value) {
return value * 2;
}

let newArray = [1, 2, 3, 4, 5].map(doubleIt);


console.log(newArray); // [2, 4, 6, 8, 10]

3.4.8.8 reduce()
The reduce() method executes a user-supplied "reducer" callback function
on each element of the array, in order, passing in the return value from the
calculation on the preceding element. The final result of running the reducer
across all elements of the array is a single value. This is the syntax:
// Arrow function
reduce((previousValue, currentValue, currentIndex, array) => {
// Your code here
}, initialValue)
where
● the "reducer" function that takes four arguments:
1. previousValue : the value resulting from the previous call to
callbackFn . On first call, initialValue if specified, otherwise the
value of array[0] .
2. currentValue : the value of the current element. On first call, the value
of array[0] if an initialValue was specified, otherwise the value of
array[1] .
3. currentIndex (optional): the index position of currentValue in the
array. On first call, 0 if initialValue was specified, otherwise 1 .
4. array (optional): the array to traverse.
● initialValue (optional) is a value to which previousValue is initialized
the first time the callback is called. If initialValue is specified, that also
causes currentValue to be initialized to the first value in the array. If
initialValue is not specified, previousValue is initialized to the first
value in the array, and currentValue is initialized to the second value in
the array.
Example 1:
"use strict";
const arr = [1, 2, 3, 4];

const sum = arr.reduce(


(previousValue, currentValue) => previousValue + currentValue
);
console.log(sum); // 10
The callback function was invoked three times, with the arguments and return
values in each call being as follows:
Callback
previousValue currentValue currentIndex return value
Iteration
First call 1 2 1 3

Second call 3 3 2 6

Third call 6 4 3 10

Explanation for
● the first call: Since the initialValue was not specified, previousValue
was initialized to the first item in the array( 1 ), and currentValue was
initialized to the second item in the array( 2 ). As the result,
currentIndex (index of currentValue in the array) was 1 and the
callback function returned sum of previousValue and currentValue ,
which was 3 .
● the second call: As 3 was the returned value in the previous call, 3 was
assigned to previousValue . currentIndex was increased by 1 and got
value of 2 . Therefore, currentValue was the third item in the array ( 3 ).
Finally, the callback function returned sum of previousValue and
currentValue , which was 6 .
● the third call: As 6 was the returned value in the previous call, 6 was
assigned to previousValue . currentIndex was increased by 1 and got
value of 3 . Therefore, currentValue was the fourth item in the array
( 4 ). Finally, the callback function returned sum of previousValue and
currentValue , which was 10 .
The value returned by reduce() would be that of the last callback invocation
( 10 ).
Example 2:
"use strict";
const arr = [1, 2, 3, 4];
const initialValue = 10;
const sum = arr.reduce(
(previousValue, currentValue) => previousValue + currentValue,
initialValue
);
console.log(sum); // 20
The callback function would be invoked four times, with the arguments and
return values in each call being as follows:
Callback
previousValue currentValue currentIndex return value
Iteration

First call 10 1 0 11

Second call 11 2 1 13

Third call 13 3 2 16

Fourth call 16 4 3 20

Explanation for the first call: Since the initialValue was specified( 10 ),
previousValue was initialized to its value( 10 ), and currentValue was
initialized to the first value in the array( 1 ). As the result, currentIndex (index
of currentValue in the array) was 0 and the callback function returned sum of
previousValue and currentValue , which was 11 .
The other calls worked in the same way of the previous example.
The value returned by reduce() would be that of the last callback invocation
( 20 ).

3.4.8.9 some()
The some() method tests whether at least one element in the array passes the
test implemented by the provided function. It returns true if, in the array, it
finds an element for which the provided function returns true ; otherwise, it
returns false. It doesn't modify the array.
Example:
"use strict";
function isEven(value) {
return value % 2 == 0;
}
console.log([1, 2, 3, 5].some(isEven)); // true

3.4.8.10 sort()
The sort() method sorts the elements of an array in place and returns the
sorted array. The default sort order is ascending, built upon converting the
elements into strings, then comparing their sequences of UTF-16 values.
Example 1:
"use strict";
const arr = [1, 3, 9, 2, 8];
arr.sort();
console.log(arr); // [1, 2, 3, 8, 9]
Example 2:
"use strict";
let people = [
{ name: 'Tom', age: 20 },
{ name: 'Danny', age: 42 },
{ name: 'Neo', age: 38 },
{ name: 'John', age: 15 }
];

// sort by age
people.sort(function (a, b) {
return a.age - b.age;
});
console.log(people);
// [
// { name: 'John', age: 15 },
// { name: 'Tom', age: 20 },
// { name: 'Neo', age: 38 },
// { name: 'Danny', age: 42 }
// ]
3.5 Set
The Set object lets you store unique values of any type, whether primitive
values or objects. Set objects are collections of values. You can iterate through
the elements of a set in insertion order. A value in the Set only occurs once; it is
unique in the Set's collection.
The has method of Set checks if a value is in a Set object, using an
approach that is quicker than testing most of the elements that have previously
been added to the Set object. In particular, it is faster than the
Array.prototype.includes() method when an Array object has a length equal to
a Set object's size.

If we need to manipulate some unique values, using a Set


object will run faster than using an Array object.
3.5.1 Creating a Set
The Set constructor lets you create Set objects that store unique values of
any type, whether primitive values or object references.
Syntax:
new Set(iterable)
where
iterable (optional) is an iterable object. If it is passed, all of its elements will
be added to the new Set . Otherwise, the new Set is empty.
Example 1:
"use strict";
let array1 = [10, 20, 30, 20];
const set1 = new Set(array1);

console.log(set1.size); // 3
console.log(set1.has(10)); // true
console.log(set1.has(50)); // false

set1.delete(30); // removes 30 from the set


console.log(set1.size); // 2

We can use the Set constructor to remove duplicate elements


from an array as the below example.
"use strict";
const arr = [1, 2, 3, 6, 2, 4, 8, 7, 6, 1, 5];
const newArray = [...new Set(arr)];
console.log(newArray); // [1, 2, 3, 6, 4, 8, 7, 5]
Example 2:
"use strict";
let set1 = new Set()
set1.add(1); // Set [1]
set1.add(2); // Set [1, 2]
set1.add(2); // Set [1, 2]
set1.add('text'); // Set [1, 2, 'text']
let o = { x: 10, y: 20 };
set1.add(o);
console.log(set1);
// Set [1, 2, 'text', { x: 10, y: 20 }]
3.5.2 Iterating Sets
"use strict";
const set1 = new Set([10, 20, 30]);

// Iterating using for..of


for (let item of set1) {
console.log(item);
}
// 10
// 20
// 30

// Iterating using forEach()


set1.forEach(function (value) {
console.log(value)
})
// 10
// 20
// 30
3.5.3 Relation with Array
"use strict";
const set1 = new Set([10, 20, 30]);

// Use the spread operator to transform a set into an Array


let arr1 = [...set1];
console.log(arr1); // [10, 20, 30]

// convert Set object to an Array object, with Array.from


let arr2 = Array.from(set1);
console.log(arr2); // [10, 20, 30]
3.6 Map
The Map object holds key-value pairs and remembers the original insertion
order of the keys. Any value (both objects and primitive values) may be used as
either a key or a value.
"use strict";
let myMap = new Map();
myMap.set('x', 10);
myMap.set('y', 20);
myMap.set('z', 30);

console.log(myMap.get('x')); // 10

myMap.set('z', 50);
console.log(myMap.get('z')); // 50

console.log(myMap.size); // 3
myMap.delete('y');
console.log(myMap.size); // 2
3.6.1 Map Iteration
A Map object iterates its elements in insertion order by using a for...of loop
which returns an array of [key, value] for each iteration.
"use strict";
let myMap = new Map();
myMap.set('x', 10);
myMap.set('y', 20);

for (const [key, value] of myMap) {


console.log(key + ' = ' + value)
}
// x = 10
// y = 20
Maps can be iterated using the forEach() method:
"use strict";
let myMap = new Map();
myMap.set('x', 10);
myMap.set('y', 20);

myMap.forEach(function (value, key) {


console.log(key + ' = ' + value)
})
// x = 10
// y = 20
3.6.2 Map vs. Object
Object is similar to Map since both let you set values to keys, retrieve those
values, delete keys, and detect whether something is stored at a key. For this
reason (and because there were no built-in alternatives), Object has been used as
Map historically.
However, there are important differences that make Map preferable in some
cases:
Map Object

A Map does not contain any An Object has a prototype, so it


Accidental Keys keys by default. It only contains contains default keys that could
what is explicitly put into it. collide with your own keys if you're
not careful.

A Map 's keys can be any value


Key Types The keys of an Object must be
(including functions, objects, or
either a String or a Symbol.
any primitive).

Although the keys of an


The keys in Map are ordered in ordinary Object are ordered now,
a simple, straightforward way: this was not always the case, and the
Key Order A Map object iterates entries, order is complex. As a result, it's best
keys, and values in the order of not to rely on property order.
entry insertion.

The number of items in The number of items in


Size a Map is easily retrieved from an Object must be determined
its size property. manually.

Object does not implement


an iteration protocol, and so objects
Iteration A Map is an iterable, so it can are not directly iterable using the
be directly iterated. JavaScript for...of statement (by
default).

Performs better in scenarios


Not optimized for frequent additions
Performance involving frequent additions and
and removals of key-value pairs.
removals of key-value pairs.
Native support for serialization
No native support for serialization
Serialization from Object to JSON,
or parsing.
and parsing using JSON.stringify() .
Native support for parsing from JSON
to Object, using JSON.parse().
3.7 Date
A JavaScript date is fundamentally specified as the number of milliseconds
that have elapsed since the ECMAScript epoch, which is defined as January 1,
1970, UTC.

3.7.1 Creating a Date Object


The following examples show several ways to create JavaScript dates:
"use strict";
let today = new Date()
let d1 = new Date("January 15, 2022 10:35:00");
let d2 = new Date("2022-1-15T10:35:00");
let d3 = new Date(2022, 0, 15); // the month is 0-indexed
let d4 = new Date(2022, 0, 15, 10, 35, 0);
let d5 = new Date(1642210500000); // passing epoch timestamp
3.7.2 Retrieving Date Information
To get Date, Month and Year or Time, we call these methods:
"use strict";
let d = new Date("January 15, 2022 08:35:00");
console.log(d.getMonth()); // 0 since the month is 0-indexed
console.log(d.getDate()); // 15
console.log(d.getFullYear()); // 2022
console.log(d.getHours()); // 8
console.log(d.getMinutes()); // 35
console.log(d.getSeconds()); // 0
The getDay() method returns the day of the week for the specified date
according to local time, where 0 represents Sunday, 1 represents Monday, 2
represents Tuesday, and so on.
console.log(d.getDay()); // 3 is Wednesday
To get the epoch timestamp, we call the getTime() method. It returns the
numeric value of the specified date as the number of milliseconds since January
1, 1970, 00:00:00 UTC.
"use strict";
let d = new Date("January 15, 2022 08:35:00");
let epochTimestamp = d.getTime();
3.7.3 Working with UTC Time
UTC (Coordinated Universal Time) is a time standard that was designed to
provide a more accurate timekeeping system. And because UTC doesn’t include
the time zone information, it is used to store in database systems. As mentioned
above, we use the getTime() method to get the UTC value.
The scenario will be like this example:
● Assumption: a customer from Tokyo, Japan (time zone is GMT+9)
submitted a form to our system at 10:35:00 on January 15, 2022 (local
time).
● At client side, in the web browser, our JavaScript code got the local time
(January 15, 2022 10:35:00 GMT+9) and converted it to UTC time.
Then, our code sent the result in milliseconds (called t0) to the web
server.
"use strict";
let submittingTime = new Date(); // "January 15, 2022 10:35:00" as the
assumption
let utcTime = submittingTime.getTime();
// t0 = utcTime = 1642210500000
console.log(submittingTime.toString());
// Sat Jan 15 2022 10:35:00 GMT+0900 (Japan Standard Time)
● Then, our server – somewhere in the world - stored the time (t0) in a
database system.
● After that, a salesman living in Singapore (time zone is GMT+8) opened
an administrative page using a web browser to process customers’ forms.
At the time, the web browser received the submitting time (t0) and
JavaScript code converted it to local time as below.
"use strict";
let utcTime = 1642210500000; // receive t0 from server
let submittingTime = new Date(utcTime);
console.log(submittingTime.toString());
// Sat Jan 15 2022 09:35:00 GMT+0800 (Singapore Standard Time)
● The salesman could see there is a form submitted on January 15, 2022 at
09:35:00 GMT+08. It makes sense since 09:35:00 GMT+08 is as same
as 10:35:00 GMT+09.
3.7.4 Calculating Elapsed Time
The following examples show how to determine the elapsed time between
two JavaScript dates in milliseconds.
"use strict";
let start = Date.now();
// do something for a long time
let x = 1;
while (x < 1000000000) {
x++;
}
/////////////////////
let end = Date.now();
let elapsed = end - start;
console.log(elapsed); // 2277 (miliseconds)
3.8 Dynamic Typing
JavaScript is a loosely typed and dynamic language. Variables in JavaScript
are not directly associated with any particular value type, and they can be
assigned and re-assigned values of all types. We can use typeof to check current
data type of a variable.
"use strict";
let x = 10;
console.log(typeof x); // number
x = 'abc';
console.log(typeof x); // string
x = true;
console.log(typeof x); // boolean
CHAPTER 4
Operators, Expressions and Statements

4.1 Operators
JavaScript has many types of operators. This section describes the operators
and contains information about operator precedence.

4.1.1 Arithmetic Operators


An arithmetic operator takes numerical values (either literals or variables) as
their operands and returns a single numerical value. The standard arithmetic
operators are addition ( + ), subtraction ( - ), multiplication ( * ), and division
( / ). These operators work as they do in most other programming languages
when used with floating point numbers (in particular, note that division by zero
produces Infinity ).
20 + 10 // = 30
20 – 10 // = 10
20 * 10 // = 200
20 / 10 // = 2
In addition to the standard arithmetic operations ( +, -, *, / ), JavaScript
provides the arithmetic operators listed here:
● Remainder (%) returns the integer remainder of dividing the two
operands.
10 % 3 // = 1
● Increment ( ++ ) adds one to its operand. If used as a prefix operator
( ++x ), returns the value of its operand after adding one; if used as a
postfix operator ( x++ ), returns the value of its operand before adding
one.
"use strict";
let x = 10;
let y = x++;
console.log(x); // 11
console.log(y); // 10
let z = ++x;
console.log(x); // 12
console.log(z); // 12
● Decrement ( -- ) subtracts one from its operand. The return value is
analogous to that for the increment operator.
"use strict";
let x = 10;
let y = x--;
console.log(x); // 9
console.log(y); // 10
let z = --x;
console.log(x); // 8
console.log(z); // 8
● Unary negation ( - ) returns the negation of its operand.
"use strict";
let x = 10;
let y = -x;
console.log(y); // -10
● Unary plus (+) attempts to convert the operand to a number, if it is not
already.
"use strict";
let x = "10";
let y = +x; // y = 10
console.log(typeof y); // number
● Exponentiation operator ( ** ) calculates the base to the exponent power,
that is, base**exponent
2 ** 3 // = 8
4.1.2 Assignment Operators
An assignment operator assigns a value to its left operand based on the value
of its right operand. The simple assignment operator is equal ( = ), which assigns
the value of its right operand to its left operand. That is, x = y is an assignment
expression that assigns the value of y to x .
These are some common assignment operators that are shorthand:
x += y // means x = x + y
x -= y // means x=x-y
x *= y // means x = x * y
x **= y // means x = x ** y
x /= y // means x=x/y
x %= y // means x = x % y
4.1.3 Comparison Operators
A comparison operator compares its operands and returns a logical value
based on whether the comparison is true . The operands can be numerical,
string, logical, or object values. Strings are compared based on standard
lexicographical ordering, using Unicode values.
In most cases, if the two operands are not of the same type, JavaScript
attempts to convert them to an appropriate type for the comparison. This
behavior generally results in comparing the operands numerically.
The sole exceptions to type conversion within comparisons involve the ===
and !== operators, which perform strict equality and inequality comparisons.
These operators do not attempt to convert the operands to compatible types
before checking equality.
These are all comparison operators in JavaScript:
● Equal ( == ) returns true if the operands are equal.
10 == "10" // true
● Not equal ( != ) returns true if the operands are not equal.
10 != 9 // true
● Strict equal ( === ) returns true if the operands are equal and of the
same type.
10 === "10" // false
10 === 10 // true
● Strict not equal ( !== ) returns true if the operands are of the same type
but not equal, or are of different type.
10 !== "10" // true
● Greater than ( > ) returns true if the left operand is greater than the right
operand.
10 > 9 // true
9 > 10 // false
● Less than ( < ) returns true if the left operand is less than the right
operand.
10 < 9 // false
9 < 10 // true
● Greater than or equal ( >= ) returns true if the left operand is greater
than or equal to the right operand.
10 >= 9 // true
● Less than or equal ( <= ) returns true if the left operand is less than or
equal to the right operand.
9 <= 10 // true

=> is not a comparison operator but rather is the notation for


Arrow Functions.

Using === is a best practice since it checks types of the


operands before doing comparison.
4.1.4 Logical Operators
Logical operators are typically used with Boolean values; when they are, they
return a Boolean value. However, the && and || operators actually return the
value of one of the specified operands, so if these operators are used with non-
Boolean values, they may return a non-Boolean value. The logical operators are
described in the following list.
● Logical AND ( && ), when used with Boolean values, && returns
true if both operands are true ; otherwise, returns false .
true && true // = true
true && false // = false
false && true // = false
false && false // = false
● Logical OR ( || ), when used with Boolean values, || returns true if either
operand is true ; if both are false , returns false .
true || true // = true
true || false // = true
false || true // = true
false || false // = false
● Logical NOT ( ! ) returns false if its single operand that can be
converted to true ; otherwise, returns true .
!true // = false
!false // = true
4.1.5 Bitwise Operators
A bitwise operator treats their operands as a set of 32 bits (zeros and ones),
rather than as decimal, hexadecimal, or octal numbers. For example, the decimal
number 9 has a binary representation of 1001 . Bitwise operators perform their
operations on such binary representations, but they return standard JavaScript
numerical values.
The following list summarizes JavaScript's bitwise operators.
● Bitwise AND( a & b ) returns a one in each bit position for which the
corresponding bits of both operands are ones.
15 & 9 // = 9
// 1111 & 1001 = 1001
● Bitwise OR( a | b ) returns a zero in each bit position for which the
corresponding bits of both operands are zeros.
15 | 9 // = 15
// 1111 | 1001 = 1111
● Bitwise XOR( a ^ b ) returns a zero in each bit position for which the
corresponding bits are the same and returns a one in each bit position for
which the corresponding bits are different.
15 ^ 9 // = 6
// 1111 ^ 1001 = 0110
● Bitwise NOT (~ a) inverts the bits of its operand.
~15 // = -16
// ~ 0000 0000 ... 0000 1111 = 1111 1111 ... 1111 0000
● Left shift (a << b) shifts a in binary representation b bits to the left,
shifting in zeros from the right.
9 << 2 // = 36
// because 1001 shifted 2 bits to the left becomes 100100, which is 36
● Sign-propagating right shift (a >> b) shifts a in binary representation b
bits to the right, discarding bits shifted off, and shifting in the same bit
from the left.
9 (base 10): 00000000000000000000000000001001 (base 2)
9 >> 2 (base 10): 00000000000000000000000000000010 (base 2) = 2 (base
10)
9 >> 2 // = 2

-9 (base 10): 11111111111111111111111111110111 (base 2)


-9 >> 2 (base 10): 11111111111111111111111111111101 (base 2) = -3 (base
10)
-9 >> 2 // = -3
● Zero-fill right shift (a >>> b) shifts a in binary representation b bits to
the right, discarding bits shifted off, and shifting in zeros from the left.
9 (base 10): 00000000000000000000000000001001 (base 2)
9 >>> 2 (base 10): 00000000000000000000000000000010 (base 2) = 2
(base 10)
9 >>> 2 // = 2

-9 (base 10): 11111111111111111111111111110111 (base 2)


-9 >>> 2 (base 10): 00111111111111111111111111111101 (base 2) =
1073741821 (base 10)
-9 >>> 2 // = 1073741821
In order to understand -9 (base 10) is 11111111111111111111111111110111
(base 2), please refer to the “Two's complement” section.

4.1.5.1 Two's Complement


Two's complement is a mathematical operation on binary numbers, and is an
example of a radix complement. It is used in computing as a method of signed
number representation. When the Most Significant Bit is a one, the number is
signed as negative.
The two's complement of an N-bit number is defined as its complement with
respect to 2N; the sum of a number and its two's complement is 2N. For instance,
for the three-bit number 0112, the two's complement is 1012, because 0112 + 1012
= 10002 = 810 which is equal to 23. The two's complement is calculated by
inverting the bits and adding one.
To get the two's complement of a negative binary number, the bits are
inverted by using the bitwise NOT operation; the value of 1 is then added to the
resulting value, ignoring the overflow which occurs when taking the two's
complement of 0.
For example, using 1 byte (=8 bits), the decimal number 9 is represented by
0000 10012

The most significant bit is 0, so the pattern represents a non-negative value.


To convert to −9 in two's-complement notation, first, the bits are inverted, that is:
0 becomes 1 and 1 becomes 0:
1111 01102

At this point, the representation is the ones' complement of the decimal value
−9. To obtain the two's complement, 1 is added to the result, giving:
1111 01112

The result is a signed binary number representing the decimal value −9 in


two's-complement form. The most significant bit is 1, so the value represented is
negative.
The two's complement of a negative number is the corresponding positive
value, except in the special case of the most negative number. For example,
inverting the bits of −9 (above) gives:
0000 10002

And adding one gives the final value:


0000 10012
Likewise, the two's complement of zero is zero: inverting gives all ones, and
adding one changes the ones back to zeros (since the overflow is ignored).
4.1.6 Conditional (ternary) Operator
The conditional (ternary) operator is the only JavaScript operator that takes
three operands. The operator can have one of two values based on a condition.
The syntax is:
condition ? value1 : value2
If condition is true , the operator has the value of value1 . Otherwise, it has
the value of value2 . You can use the conditional operator anywhere you would
use a standard operator.
"use strict";
let age = 30;
let mature = (age >= 18) ? "adult" : "teenager";
console.log(mature); // "adult"
4.1.7 Nullish Coalescing Operator
The nullish coalescing operator ( ?? ) is a logical operator that returns its
right-hand side operand when its left-hand side operand is null or undefined ,
and otherwise returns its left-hand side operand.
"use strict";
const str = null ?? "default string";
console.log(str); // "default string"

const num = 0 ?? 10;


console.log(num); // 0
4.1.8 Optional Chaining Operator
The optional chaining operator ( ?. ) enables you to read the value of a
property located deep within a chain of connected objects without having to
check that each reference in the chain is valid.
The ?. operator is like the . chaining operator, except that instead of
causing an error if a reference is null or undefined , the expression short-
circuits with a return value of undefined . When used with function calls, it
returns undefined if the given function does not exist.
"use strict";
let obj = {
x: 10,
y: {
theValue: 20
}
};
let readValue = obj.z?.theValue;
console.log(readValue); // undefined
console.log(obj.nonExistentMethod?.()); // undefined
4.1.9 Comma Operator
The comma operator ( , ) is primarily used inside a for loop, to allow
multiple variables to be updated each time through the loop. Often two separate
statements can and should be used instead.
"use strict";
for (let i = 0, j = 8; i < j; i++, j--) {
console.log(`i = ${i}, j = ${j}`);
}
// i = 0, j = 8
// i = 1, j = 7
// i = 2, j = 6
// i = 3, j = 5
4.1.10 Unary Operators
A unary operation is an operation with only one operand.

4.1.10.1 delete
The delete operator deletes an object's property. If the delete operator
succeeds, it removes the property from the object. The delete operator returns
true if the operation is possible; it returns false if the operation is not possible.
Syntax:
delete object.property;
delete object[propertyKey];
Example:
"use strict";
let obj = {
x: 10,
y: 20
};
delete obj.x;
console.log(obj); // { y: 20 }

It's a bad practice to delete elements from an array. When you


do this, the array length is not affected and other elements are
not re-indexed. To actually manipulate the array, use the
various array methods such as splice .

4.1.10.2 typeof
The typeof operator returns a string indicating the type of the unevaluated
operand. Operand is the string, variable, keyword, or object for which the type is
to be returned.
The typeof operator is used in the following way:
typeof operand
Example:
"use strict";
typeof 62; // "number"
typeof "Hello world"; // "string"
typeof true; // "boolean"
typeof null; // "object"
typeof parseInt; // "function"

4.1.10.3 void
The void operator evaluates the given expression and then returns
undefined . The void operator is used in the following way:
void expression
The void operator is often used in these cases:
● When using an immediately-invoked function expression, void can be
used to force the function keyword to be treated as an expression instead
of a declaration.
"use strict";
void function test() {
console.log("Hello");
}();
● When a browser follows a javascript: URI, it evaluates the code in the
URI and then replaces the contents of the page with the returned value,
unless the returned value is undefined . Therefore, we use the void
operator in the following HTML code.
<a href="javascript:void(0);">
Click here to do nothing
</a>

<a href="javascript:void(document.body.style.backgroundColor='red');">
Click here for red background
</a>
4.1.11 Relational Operators
A relational operator compares its operands and returns a Boolean value
based on whether the comparison is true .

4.1.11.1 in
The in operator returns true if the specified property is in the specified
object. The syntax is:
propNameOrNumber in objectName
The following examples show some uses of the in operator.
"use strict";
// object
let obj = { x: 10, y: 20 };
"x" in obj; // true
"z" in obj; // false since "z" is not its property

// array
let things = ["book", "pen", "pencil", "eraser"];
1 in things; // true
4 in things; // false since max index is 3
"pen" in things; // returns false (you must specify the index number, not the
value at that index)
"length" in things; // returns true (length is an Array property)

4.1.11.2 instanceof
The instanceof operator returns true if the specified object is of the
specified object type. The syntax is:
objectName instanceof objectType
For example, the following code uses instanceof to determine whether the
arr variable is an Array object and the obj variable is an Object .
"use strict";
console.log(Array instanceof Object); // true
console.log(Object instanceof Array); // false
let arr = [];
console.log(arr instanceof Object); // true
console.log(arr instanceof Array); // true

let obj = {};


console.log(obj instanceof Object); // true
console.log(obj instanceof Array); // false
We can see that Array is an instance of Object but Object is not an
instance of Array .
4.1.12 Operator Precedence
The precedence of operators determines the order they are applied when
evaluating an expression. You can override operator precedence by using
parentheses.
The following table lists operators in order from highest precedence (19) to
lowest precedence (1).
Operator type Associativity Individual operators

19 Grouping (…)

Member Access left-to-right ….…

Computed Member Access left-to-right …[…]

18 new (with argument list) new … ( … )

Function Call left-to-right …(…)

Optional chaining left-to-right ?.

17 new (without argument list) right-to-left new …

Postfix Increment … ++
16
Postfix Decrement … --

Logical NOT (!) !…

Bitwise NOT (~) ~…

Unary plus (+) +…

Unary negation (-) -…

Prefix Increment ++ …
15 right-to-left
Prefix Decrement -- …

typeof typeof …

void void …

delete delete …

await await …
14 Exponentiation (**) right-to-left … ** …

Multiplication (*) …*…

13 Division (/) left-to-right …/…

Remainder (%) …%…

Addition (+) …+…


12 left-to-right
Subtraction (-) …-…

Bitwise Left Shift (<<) … << …

11 Bitwise Right Shift (>>) left-to-right … >> …

Bitwise Unsigned Right Shift (>>>) … >>> …

Less Than (<) …<…

Less Than Or Equal (<=) … <= …

Greater Than (>) …>…


10 left-to-right
Greater Than Or Equal (>=) … >= …

in … in …

instanceof … instanceof …

Equality (==) … == …

Inequality (!=) … != …
9 left-to-right
Strict Equality (===) … === …

Strict Inequality (!==) … !== …

8 Bitwise AND (&) left-to-right …&…

7 Bitwise XOR (^) left-to-right …^…

6 Bitwise OR (|) left-to-right …|…

5 Logical AND (&&) left-to-right … && …

Logical OR (||) left-to-right … || …


4
Nullish coalescing operator (??) … ?? …
left-to-right

3 Conditional (ternary) operator right-to-left …?…:…

…=…

… += …

… -= …

… **= …

… *= …

… /= …

… %= …

… <<= …
Assignment right-to-left
… >>= …
2
… >>>= …

… &= …

… ^= …

… |= …

… &&= …

… ||= …

… ??= …

yield yield …
right-to-left
yield* yield* …

1 Comma / Sequence left-to-right …,…


4.2 Expressions
An expression is any valid unit of code that resolves to a value.
Every valid expression resolves to some value but conceptually, there are two
types of expressions: with side effects (for example: those that assign value to a
variable) and those that in some sense evaluate and therefore resolve to a value.
● The expression x = 10 is an example of the first type. This expression
uses the = operator to assign the value 10 to the variable x . The
expression itself evaluates to 10 .
● The code 10 + 20 is an example of the second expression type. This
expression uses the + operator to add 10 and 20 together without
assigning the result, 30 , to a variable.
JavaScript has the following expression categories:
● Arithmetic: evaluates to a number, for example 30 . (Generally uses
arithmetic operators.)
● String: evaluates to a character string, for example, "Hello" or
"world" . (Generally uses string operators.)
● Logical: evaluates to true or false . (Often involves logical operators.)
● Primary expressions: Basic keywords and general expressions in
JavaScript.
● Left-hand-side expressions: Left values are the destination of an
assignment.
4.3 Statements
JavaScript applications consist of statements with an appropriate syntax. A
single statement may span multiple lines. Multiple statements may occur on a
single line if each statement is separated by a semicolon.

4.3.1 try...catch
The try...catch statement marks a block of statements to try and specifies a
response should an exception be thrown.
Syntax:
try {
// try statements
}
catch (exceptionVariable) {
// catch statements
}
finally {
// finally statements
}
where
● exceptionVariable is an optional identifier to hold an exception object
for the associated catch -block.
The try statement consists of a try -block, which contains one or more
statements. {} must always be used, even for single statements. A catch -block,
a finally -block, or both must be present. This gives us three forms for the try
statement:
try...catch
try...finally
try...catch...finally
A catch -block contains statements that specify what to do if an exception is
thrown in the try -block. If any statement within the try -block (or in a function
called from within the try -block) throws an exception, control is immediately
shifted to the catch -block. If no exception is thrown in the try -block, the
catch -block is skipped.
The finally -block will always execute after the try -block and catch -
block(s) have finished executing. It always executes, regardless of whether an
exception was thrown or caught.
Example:
"use strict";
try {
const MY_CONST = 10;
MY_CONST = 20;
console.log('This is an unreachable line of code');
}
catch (error) {
console.log(error);
// TypeError: Assignment to constant variable.
}
finally {
// always execute after try-block and catch-block
console.log('finally statement');
}
4.3.2 throw
The throw statement throws a user-defined exception. Execution of the
current function will stop (the statements after throw won't be executed), and
control will be passed to the first catch -block in the call stack. If no catch -
block exists among caller functions, the program will terminate.
Some examples of throw expressions:
throw 'Error here';
throw -1;
throw false;
throw new Error('Error here');
You can specify an object when you throw an exception. You can then
reference the object's properties in the catch -block. The following example
creates an object of type UserException and uses it in a throw statement.
"use strict";
function UserException(message) {
this.message = message;
this.name = 'UserException';
}

function processUserInput(data) {
if (data instanceof Object) {
// process user's input
}
else {
throw new UserException("Invalid User's Input");
}
}

let userInfo = null;


try {
// let input = {}; // try this valid input by yourself
let input = "data";
userInfo = processUserInput(input);
} catch (e) {
console.log(e.name, e.message); // UserException Invalid User's Input
}
4.3.3 if
The if statement executes a statement if a specified condition is true. If the
condition is false, another statement can be executed. The below example has
two if-branches.
"use strict";
let x = 10;
if (x > 0) {
console.log("x is positive");
}
else {
console.log("x is not positive");
}
The if statement could also have:
● Only one if-branch
"use strict";
let x = 10;
if (x > 0) {
console.log("x is positive");
}
● More than two if-branches
"use strict";
let x = -10;
if (x > 0) {
console.log("x is positive");
}
else if (x === 0) {
console.log("x is zero");
}
else {
console.log("x is negative");
}
We could always add more if-branch using else if clause.
However, switch should be considered to be used in that case.
4.3.4 switch
The switch statement evaluates an expression, matching the expression's
value to a case clause, and executes statements associated with that case, as
well as statements in cases that follow the matching case.
Syntax:
switch (expression) {
case value1:
// Statements executed when the
// result of expression matches value1
[break;]
case value2:
// Statements executed when the
// result of expression matches value2
[break;]
...
case valueN:
// Statements executed when the
// result of expression matches valueN
[break;]
[default:
// Statements executed when none of
// the values match the value of the expression
[break;]
]
}
Example 1:
"use strict";

function getDayString(day) {
switch (day) {
case 0:
console.log("Sunday");
break;
case 1:
console.log("Monday");
break;
case 2:
console.log("Tuesday");
break;
case 3:
console.log("Wednesday");
break;
case 4:
console.log("Thursday");
break;
case 5:
console.log("Friday");
break;
case 6:
console.log("Saturday");
break;
default:
console.log("Invalid day");
}
}
getDayString(5); // Friday
getDayString(7); // Invalid day
Example 2:
"use strict";
function getDayType(day) {
switch (day) {
case 1:
case 2:
case 3:
case 4:
case 5:
console.log("Working day");
break;
case 0:
case 6:
console.log("Weekend");
break;
default:
console.log("Invalid day");
}
}
getDayType(2); // Working day
getDayType(3); // Working day
getDayType(0); // Weekend
If you forget a break then the script will run from the case where the
criterion is met and will run the cases after that regardless if a criterion was met.
Example 3:
"use strict";
function test(myVar) {
switch (myVar) {
case 1:
console.log(1);
case 2:
console.log(2);
break;
case 3:
console.log(3);
break;
default:
console.log('default');
}
}
test(1);
// 1
// 2
4.3.5 Loops and Iteration
Loops offer a quick and easy way to do something repeatedly. This section
introduces the different iteration statements available to JavaScript.
The various loop mechanisms offer different ways to determine the start and
end points of the loop. There are various situations that are more easily served
by one type of loop over the others.
The statements for loops provided in JavaScript are:

4.3.5.1 for
A for loop repeats until a specified condition evaluates to false . A for
statement looks as follows:
for ([initialExp]; [conditionExp]; [incrementExp]) {
statements
}
When a for loop executes, the following occurs:
1. The initializing expression initialExp , if any, is executed. This
expression usually initializes one or more loop counters, but the syntax
allows an expression of any degree of complexity. This expression can
also declare variables.
2. The conditionExp expression is evaluated. If the value
of conditionExp is true , the loop statements execute. If the value
of condition is false , the for loop terminates. (If
the condition expression is omitted entirely, the condition is assumed
to be true .)
3. The statements are executed. To execute a single statement, we can
omit the block statement ( {} ).
4. If present, the update expression incrementExp is executed.
5. Control returns to Step 2.
Example:
"use strict";
let arr = [10, 20, 30];
for (let index = 0; index < arr.length; index++) {
console.log(arr[index]);
}
// 10
// 20
// 30

4.3.5.2 for...in
The for...in statement iterates a specified variable over all the enumerable
properties of an object. For each distinct property, JavaScript executes the
specified statements. A for...in statement looks as follows:
for (variable in object) {
statements
}
The following example prints all properties of an object and their values.
"use strict";
let obj = {
firstName: "Neo",
lastName: "D. Truman",
email: "[email protected]"
};
for (let propName in obj) {
console.log(propName + " = " + obj[propName]);
}
// "firstName = Neo"
// "lastName = D. Truman"
// "email = [email protected]"

The for...in statement is not applied to array since it can only


get the array’s item indexes.
"use strict";
let things = ["book", "pen", "eraser"];
for (let index in things) {
console.log("Item " + index + ": " + things[index]);
}
// Item 0: book
// Item 1: pen
// Item 2: eraser

4.3.5.3 for...of
The for...of statement creates a loop iterating over iterable objects (including
Array, Map, Set, arguments object and so on), invoking a custom iteration hook
with statements to be executed for the value of each distinct property.
for (variable of object) {
statements
}
Let’s try this example.
"use strict";
let things = ["book", "pen", "eraser"];
for (let item of things) {
console.log(item);
}
// book
// pen
// eraser
4.3.5.4 while
A while statement executes its statements as long as a specified condition
evaluates to true . A while statement looks as follows:
while (condition) {
statements
}
The condition test occurs before statements in the loop are executed. If the
condition returns true , statements are executed and the condition is tested
again. If the condition returns false , execution stops, and control is passed to
the statement following while .
To execute a single statement, we can omit the block statement ( {} ).
Example:
"use strict";
let i = 0;
while (i < 3) {
i++;
console.log(i);
}
// 1
// 2
// 3

4.3.5.5 do...while
The do...while statement repeats until a specified condition evaluates to
false . A do...while statement looks as follows:
do {
statements
} while (condition);
statements are always executed once before the condition is checked. To
execute a single statement, we can omit the block statement ( {} ).
If condition is true , the statement executes again. At the end of every
execution, the condition is checked. When the condition is false , execution
stops.
"use strict";
let i = 0;
do {
i++;
console.log(i);
} while (i < 3);
// 1
// 2
// 3

4.3.5.6 label
A label provides a statement with an identifier that lets you refer to it
elsewhere in your program. For example, you can use a label to identify a loop,
and then use the break or continue statements to indicate whether a program
should interrupt the loop or continue its execution.
The syntax of the labeled statement looks like the following:
label:
statement
The value of label may be any JavaScript identifier that is not a reserved
word. The statement that you identify with a label may be any statement.
In this example, the myLoop label identifies a while loop.
"use strict";
myLoop:
while (theFlag === true) {
doSomething();
}

4.3.5.7 break
Use the break statement to terminate a loop, switch , or in conjunction with
a labeled statement.
● When you use break without a label, it terminates the innermost
enclosing while, do-while, for , or switch immediately and transfers
control to the following statement.
● When you use break with a label, it terminates the specified labelled
statement.
The syntax of the break statement looks like this:
● The first form of the syntax terminates the innermost enclosing loop or
switch.
break;
● The second form of the syntax terminates the specified enclosing labeled
statement.
break [label];
The following example iterates through the elements in an array until it finds
the index of an element whose value is theValue :
"use strict";
let theValue = 3;
let arr = [1, 2, 3, 4, 5];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
if (arr[i] === theValue) {
break;
}
}
// 1
// 2
// 3
And this is an example of breaking a labelled statement.
"use strict";
for (let i = 1; i < 5; i++) {
the2ndLoop:
for (let j = 1; j < 5; j++) {
for (let k = 1; k < 5; k++) {
console.log(`[i, j, k] = [${i}, ${j}, ${k}]`);
if (k === j * 2 && j === i * 2) {
console.log('Break the 2nd loop');
break the2ndLoop;
}
}
}
}
// [i, j, k] = [1, 1, 1]
// [i, j, k] = [1, 1, 2]
// [i, j, k] = [1, 1, 3]
// [i, j, k] = [1, 1, 4]
// [i, j, k] = [1, 2, 1]
// [i, j, k] = [1, 2, 2]
// [i, j, k] = [1, 2, 3]
// [i, j, k] = [1, 2, 4]
// Break the 2nd loop
// [i, j, k] = [2, 1, 1]
// [i, j, k] = [2, 1, 2]
// [i, j, k] = [2, 1, 3]
// ...
// [i, j, k] = [4, 4, 4]

4.3.5.8 continue
The continue statement can be used to restart a while, do-while, for , or
label statement.
● When you use continue without a label, it terminates the current
iteration of the innermost enclosing while, do-while , or for statement
and continues execution of the loop with the next iteration. In contrast to
the break statement, continue does not terminate the execution of the
loop entirely. In a while loop, it jumps back to the condition . In a for
loop , it jumps to the increment-expression .
● When you use continue with a label, it applies to the looping statement
identified with that label.
The syntax of the continue statement looks like the following:
continue [label];
Example:
"use strict";
let i = 0;
while (i < 5) {
i++;
if (i === 3) {
continue;
}
console.log(i);
}
// 1
// 2
// 4
// 5
If continue is encountered, the program terminates the current iteration of
checkj and begins the next iteration. Each time continue is encountered, checkj
reiterates until its condition returns false .
"use strict";
the1stLoop:
for (let i = 1; i < 5; i++) {
for (let j = 1; j < 5; j++) {
for (let k = 1; k < 5; k++) {
console.log(`[i, j, k] = [${i}, ${j}, ${k}]`);
if (k === j * 2 && j === i * 2) {
console.log('Continue at the 1st loop');
continue the1stLoop;
}
}
}
}

// [i, j, k] = [1, 1, 1]
// [i, j, k] = [1, 1, 2]
// [i, j, k] = [1, 1, 3]
// [i, j, k] = [1, 1, 4]
// [i, j, k] = [1, 2, 1]
// [i, j, k] = [1, 2, 2]
// [i, j, k] = [1, 2, 3]
// [i, j, k] = [1, 2, 4]
// Continue at the 1st loop
// [i, j, k] = [2, 1, 1]
// [i, j, k] = [2, 1, 2]
// [i, j, k] = [2, 1, 3]
// ...
// [i, j, k] = [4, 4, 4]
4.4 Destructuring Assignment
The destructuring assignment syntax is a JavaScript expression that makes it
possible to unpack values from arrays, or properties from objects, into distinct
variables.

4.4.1 Destructuring Array


Here’s an example of how an array is destructured into variables:
"use strict";
let things = ["book", "pen", "pencil", "eraser"];
let [firstThing, secondThing, thirdThing] = things;
console.log(firstThing); // "book"
console.log(secondThing); // "pen"
console.log(thirdThing); // "pencil"
Unpacking items from an array passed as a function parameter.
"use strict";
let things = ["book", "pen", "pencil", "eraser"];
function logInfo([firstThing, secondThing, ...otherThings]) {
console.log(firstThing);
console.log(secondThing);
console.log(otherThings);
}
// ... before otherThings is the rest syntax
// which will be introduced soon
logInfo(things);
// "book"
// "pen"
// ['pencil', 'eraser']
4.4.2 Destructuring Object
Let’s try to destructure this object.
"use strict";
let obj = {
firstName: "Neo",
lastName: "D. Truman",
age: 39,
address: {
number: 123,
block: 10
}
};
Destructuring the object using its properties’ names.
let { firstName, lastName, age } = obj;
console.log(firstName); // "Neo"
console.log(lastName); // "D. Truman"
console.log(age); // 39

let { address } = obj;


let { number, block } = address;
console.log(number); // 123
console.log(block); // 10
A property can be unpacked from an object and assigned to a variable with a
different name than the object property as below.
let { firstName: forename, lastName: surname, age } = obj;
console.log(forename); // "Neo"
console.log(surname); // "D. Truman"
console.log(age); // 39
Unpacking properties from an object passed as a function parameter.
function logInfo({ firstName, lastName, age }) {
console.log(firstName);
console.log(lastName);
console.log(age);
}
logInfo(obj);
// "Neo"
// "D. Truman"
// 39
4.4.3 Destructuring Map
A Map object iterates its elements in insertion order - a for...of loop returns
an array of [key, value] for each iteration.
"use strict";
let books = new Map();
books.set('id1', { title: 'Book 1', year: 2020 });
books.set('id2', { title: 'Book 2', year: 2022 });

for (let [id, info] of books) {


let { title, year } = info; // object destructuring
console.log(`${title} was published in ${year} having id "${id}"`);
}
// Book 1 was published in 2020 having id "id1"
// Book 2 was published in 2022 having id "id2"
4.4.4 Default Value in Destructuring
A variable can be assigned a default value. In the case that the value
unpacked from the destructuring is undefined , the default value will be used.
"use strict";
// default value when destructuring array
let [x = 10, y = 20] = [123];
console.log(x); // 123
console.log(y); // 20

// default value when destructuring object


let obj = {
firstName: "Neo",
lastName: "D. Truman",
age: 39
};

let { firstName, lastName, email = "n/a" } = obj;


console.log(firstName); // "Neo"
console.log(lastName); // "D. Truman"
console.log(email); // "n/a"
4.4.5 Ignoring Some Returned Values
We can ignore return values that you're not interested in like this example:
"use strict";
const [x, , y] = [10, 20, 30];
console.log(x); // 10
console.log(y); // 30
4.4.6 Rest Syntax
When destructuring an array, we can unpack and assign the remaining part of
it to a variable using the rest syntax (…):
"use strict";
let things = ["book", "pen", "pencil", "eraser"];
let [firstThing, secondThing, ...otherThings] = things;
console.log(firstThing); // "book"
console.log(secondThing); // "pen"
console.log(otherThings); // ['pencil', 'eraser']
In object destructuring, rest syntax (…) is used to collect the remaining own
enumerable property keys that are not already picked off by the destructuring
pattern.
"use strict";
let obj = {
firstName: "Neo",
lastName: "D. Truman",
age: 39,
email: "[email protected]"
};
let { firstName, lastName, ...restInfo } = obj;
console.log(firstName); // "Neo"
console.log(lastName); // "D. Truman"
console.log(restInfo); // {age: 39, email: '[email protected]'}
4.4.7 Spread Syntax
Spread syntax (...) allows an iterable such as an array expression or string to
be expanded in places where zero or more arguments (for function calls) or
elements (for array literals) are expected, or an object expression to be expanded
in places where zero or more key-value pairs (for object literals) are expected.
"use strict";
function sum() {
let result = 0;
for (const num of arguments) {
result += num;
}
return result;
}
const numbers = [10, 20, 30, 40];
console.log(sum(...numbers)); // 100
Copying an array using the spread syntax (...).
"use strict";
let arr1 = [10, 20, 30];
let arr2 = [...arr1]; // like arr1.slice()
arr2[1] = 200;
console.log(arr1); // [10, 20, 30]
console.log(arr2); // [10, 200, 30]
The spread syntax is a better way to concatenate arrays.
"use strict";
let arr1 = [2, 3];
let arr2 = [6, 7, 8];
let arr3 = [1, ...arr1, 4, 5, ...arr2];
console.log(arr3); // [1, 2, 3, 4, 5, 6, 7, 8]
Shallow-cloning (excluding prototype) or shallow-merging of objects is now
possible using a shorter syntax using spread syntax.
● Shallow-cloning since change the cloned object could change the
original object.
"use strict";
let obj = { x: 10, y: 30, info: { id: 1 } };
let clonedObj = { ...obj };
clonedObj.info.id = 100;
console.log(clonedObj); // {x: 10, y: 30, info: {id: 100}}
console.log(obj); // {x: 10, y: 30, info: {id: 100}}
● Shallow-merging since change the merged object could change the
original object.
"use strict";
let obj1 = { x: 10, y: 30, info: { id: 1 } };
let obj2 = { x: 20, z: 40, info: { id: 2 } };
let mergedObj = { ...obj1, ...obj2 };
mergedObj.info.id = 100;
console.log(mergedObj); // {x: 20, y: 30, info: {id: 100}, z: 40}
console.log(obj1); // {x: 10, y: 30, info: {id: 1}}
console.log(obj2); // {x: 20, z: 40, info: {id: 100}}
We also can spread a string to an array of characters like this example.
"use strict";
let str = "Hello";
let arr = [...str];
console.log(arr); // ['H', 'e', 'l', 'l', 'o']

Spread syntax expands things to small pieces while rest syntax


gathers things into an array.
CHAPTER 5
Function

A function is a set of statements that come together to complete a task. These


statements are enclosed in curly braces( {} ) and executed when the function is
invoked.
Function is also an object so that we can add a property into it.
"use strict";
function myFunc() {
}
console.log(Function instanceof Object); // true
myFunc.version = 1.2;
console.log(myFunc.version); // 1.2
5.1 Creating a Function
5.1.1 Function Declaration
A function declaration or function statement is defined using the function
keyword, followed by a function name.
For example, the following code defines a simple function named sum :
"use strict";
function sum(x, y) {
return x + y;
}
console.log(typeof sum); // "function"
console.log(sum(10, 20)); // 30
The function sum takes two parameters, called x and y . The function
consists of one statement that says to return the summary of the two parameters.
The statement return specifies the value returned by the function.
5.1.2 Function Expression
A function expression is an anonymous function that does not have a name.
Therefore, it is often assigned to a variable so that we can call the function using
the variable. For example, the function sum could have been defined as:
"use strict";
let sum = function (x, y) {
return x + y;
}
console.log(typeof sum); // "function"
console.log(sum(10, 20)); // 30

Function expression can have an issue with hoisting as in the


below example.
"use strict";
console.log(typeof sum); // ReferenceError: Cannot access 'sum' before
initialization
let sum = function (x, y) {
return x + y;
}
5.1.3 Using Function Constructor
Function constructor can be used to create a function programmatically. It
means that parameters and function bodies could be defined at runtime.
The syntax is:
new Function(param1, ..., paramN, functionBody)
when
● param1, ..., paramN : Names to be used by the function as formal
parameters.
● functionBody : A string containing the JavaScript statements comprising
the function definition.
The following code creates a function that takes two parameters and return
summary of them.
"use strict";
const sum = new Function('x', 'y', 'return x + y');
console.log(sum(10, 20)); // 30
5.2 Calling Functions
A function is invoked by using the brackets () with the arguments.
A function declaration (not function expression) can be hoisted (appear below
the call in the code), as in this example:
"use strict";
console.log(sum(10, 20)); // 30
function sum(x, y) {
return x + y;
}
In the above example,
● x and y are parameters of the function;
● while 10 and 20 are arguments used when we call the function.
5.3 The return Statement
The return keyword is used to finish the processing of functions and return
something to the caller.
The syntax is:
return [expression];
where the expression whose value is to be returned. If omitted, undefined
is returned instead.
Example 1:
"use strict";
function test() {
return 100;
}
let x = test();
console.log(x); // 100
Example 2:
"use strict";
function test() {
return;
console.log('Will be ignored');
}
let x = test();
console.log(x); // undefined
Example 3:
"use strict";
function test() {
}
let x = test();
console.log(x); // undefined
5.4 Function Parameters
JavaScript doesn’t require the number of arguments must be equal to the
number of function parameters. For example, we have a function having two
parameters like this example.
"use strict";
function sum(x, y) {
return x + y;
}
And we can invoke the above function with different number of arguments
without any errors.
● One argument: lacking input will be undefined
console.log(sum(10)); // NaN (since y is undefined)
● Two arguments
console.log(sum(10, 20)); // 30
● Three arguments: redundant input will be ignored
console.log(sum(10, 20, 30)); // 30
5.4.1 Default Parameters
Default function parameters are values used when the number of arguments is
less than the number of parameters.
For example, the sum function has two parameters but when invoking it, we
input only one argument. In that case, the default parameter will be used.
"use strict";
function sum(x, y = 0) {
return x + y;
}
console.log(sum(10)); // 10 (since y = 0)
5.4.2 Rest Parameters
The rest parameter syntax helps us collect inputs into an array.
"use strict";
function sum(...numbers) {
console.log(numbers instanceof Array); // true

let result = 0;
for (let i = 0; i < numbers.length; i++) {
result += numbers[i];
}
return result;
}
console.log(sum(10, 20, 30)); // 60
console.log(sum(10, 20, 30, 40)); // 100
And this is an example of using rest parameters in combination with ordinary
parameters.
"use strict";
function multiply(multiplier, ...numbers) {
let result = [];
for (let i = 0; i < numbers.length; i++) {
result.push(numbers[i] * multiplier);
}
return result;
}
let arr = multiply(2, 10, 20, 30);
console.log(arr); // [20, 40, 60]
5.5 The arguments Object
The arguments object of a function is a special object that contains all
arguments which were inputted by users.
Example:
"use strict";
function sum() {
let total = 0;
for (let n of arguments) {
total += n;
}
return total;
}
console.log(sum(1, 2, 3, 4, 5)); // 15
5.6 Recursive Function
A recursive function calls itself in its function body.
For example, consider the following function which will calculate a summary
of integers from start number to end number. To focus on the concept, we
don’t put the validation code of the parameters.
"use strict";
function sum(start, end) {
let result = 0;
for (let i = start; i <= end; i++) {
result += i;
}
return result;
}
console.log(sum(2, 6)); // 2 + 3 + 4 + 5 + 6 = 20
We can write the above example in another way as a recursive function.
"use strict";
function sum(start, end) {
return start === end ? end : start + sum(start + 1, end);
}
console.log(sum(2, 6));
// = 2 + sum(3, 6)
// = 2 + 3 + sum(4, 6)
// = 2 + 3 + 4 + sum(5, 6)
// = 2 + 3 + 4 + 5 + sum(6, 6)
// = 2 + 3 + 4 + 5 + 6

Recursive functions push return addresses onto the Call Stack


making them run slower than normal functions. Therefore, we
should avoid using recursive functions if we can.
5.7 Arrow Function Expressions
An arrow function expression is anonymous with some limitations as below.
● Does not have its bindings to this or super , and should not be used as a
method of objects.
● Not suitable for the call , apply and bind methods, which generally
rely on establishing a scope.
● Cannot be used as constructors.
● Cannot use yield , within its body.

The syntax of arrow function expression:


● One parameter. With simple expression return is implicit:
param => expression
● Multiple parameters require parentheses. With simple expression return
is implicit:
(param1, ..., paramN) => expression
● Multiple statements require curly braces:
param => {
expressions
}
● Multiple parameters require parentheses and multiple statements require
curly braces:
(param1, ..., paramN) => {
expressions
}
Example 1:
"use strict";
let sayHi = name => console.log("Hello " + name);
sayHi('Neo'); // "Hello Neo"
Example 2:
"use strict";
let sayHi = (firstName, lastName) => console.log(`Hello ${firstName}
${lastName}`);
sayHi('Neo', 'D. Truman'); // "Hello Neo D. Truman"
Example 3:
"use strict";
let sayHi = name => {
let upperCaseName = name.toUpperCase();
console.log("Hello " + upperCaseName);
}
sayHi('Neo'); // "Hello NEO"
Example 4:
"use strict";
let sayHi = (firstName, lastName) => {
let upperCaseFirstname = firstName.toUpperCase();
let upperCaseLastname = lastName.toUpperCase();
console.log(`Hello ${upperCaseFirstname} ${upperCaseLastname}`);
}
sayHi('Neo', 'D. Truman'); // "Hello NEO D. TRUMAN"
5.8 Immediately Invoked Function Expressions
An IIFE (Immediately Invoked Function Expression) is a function that is
called immediately right after its definition code. One purpose of using IIFE is to
avoid polluting the global namespace.
The syntax is as below.
(function () {
// some code here
})(); // parentheses in this line does invoking the function
Example:
"use strict";
(() => {
let someVariable = 10;
console.log(someVariable);
// some initiation code using above variable(s)
})();
// someVariable will be discarded after the function is executed.
5.9 Function Scope
Variables defined inside a function are only available inside the function or its
child functions via the closure.
However, a function can access all variables and functions defined inside the
scope in which it is defined.
● In other words, a function defined in the global scope can access all
variables defined in the global scope.
"use strict";
// These variables are defined in the global scope
let num1 = 10,
num2 = 20;
// This function is also defined in the global scope
function sum() {
return num1 + num2;
}
console.log(sum()); // 30
● A function defined inside another function can also access to variables
defining in its parent function.
"use strict";
// These variables are defined in the global scope
let num1 = 10,
num2 = 20;

// A nested function example


function test() {
let x = 1,
y = 2;

function add() {
return num1 + num2 + x + y;
}
console.log(add()); // 33
}
test();

The above example is also an example of closure which will be


introduced soon.
5.10 Passing by Value vs. by Reference
Parameters, which are not objects, are passed to functions by value — so if
the code within the body of a function assigns new values to parameters that
were passed to the function, the change is not reflected globally or in the code
called that function.
"use strict";
let x = 10;
function test(param) {
param = 100;
}
test(x);
console.log(x); // 10
When you pass an object as a parameter, if the function changes the object's
properties, that change is visible outside the function, as shown in the following
example. We say that the param was passed by reference.
"use strict";
let o = { info: 10 };
function testObject(obj) {
obj.info = 100;
}
testObject(o);
console.log(o); // {info: 100}

let a = [1, 2, 3];


function testArray(arr) {
arr[0] = 10;
}
testArray(a);
console.log(a); // [10, 2, 3]
5.11 Function Overloading
If two or more functions have the same name, the latest function definition
will override the others.
Example 1:
"use strict";
function sum(x, y) {
return x + y;
}
function sum(x, y, z) {
return x + y + z;
}
console.log(sum(1, 2)); // NaN since z is undefined
console.log(sum(1, 2, 3)); // 6
Example 2:
"use strict";
function sum(x, y, z) {
return x + y + z;
}
function sum(x, y) {
return x + y;
}
console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3)); // 3 since the 3rd argument was ignored
5.12 First-class Function
First-class functions are functions that are treated as variables. For example,
● a function can be assigned as a value to a variable
"use strict";
const sayHello = function () {
console.log("Hello!");
}
sayHello(); // Hello!
● a function can be passed as an argument to other functions
"use strict";
function sayHello() {
return "Hello ";
}
function greeting(helloMessage, name) {
console.log(helloMessage() + name);
}
// Pass sayHello as an argument to greeting function
greeting(sayHello, "Neo"); // Hello Neo
● a function can be returned by another function
"use strict";
function sayHello() {
return function () {
console.log("Hello!");
}
}
const myFunc = sayHello();
myFunc(); // Hello!
5.13 Context Binding
5.13.1 bind
The Function.prototype.bind() method helps us change the context of the
this keyword.
The syntax is:
bind(thisArg, arg1, ... , argN)
where
● thisArg is the object that the this keyword will point to
● arg1, arg2, ...argN (optional) are arguments of the function.

Example 1:
"use strict";
let obj = {
firstName: "Neo",
lastName: "D. Truman"
};

function logInfo() {
console.log(this.firstName + " " + this.lastName);
}
let logInformation = logInfo.bind(obj);
logInformation(); // Neo D. Truman
Example 2: input arguments into bind()
"use strict";
let obj = {
firstName: "Neo",
lastName: "D. Truman"
};

function logInfo(email) {
console.log(this.firstName + " " + this.lastName);
console.log(email);
}
let logInformation = logInfo.bind(obj, "[email protected]");
logInformation();
// Neo D. Truman
// [email protected]
Example 3: does not input arguments into bind()
"use strict";
let obj = {
firstName: "Neo",
lastName: "D. Truman"
};

function logInfo(email) {
console.log(this.firstName + " " + this.lastName);
console.log(email);
}
let logInformation = logInfo.bind(obj);
logInformation("[email protected]");
// Neo D. Truman
// [email protected]
5.13.2 call
The Function.prototype.call() method works in the same way as bind()
however call() will invoke the function (as its name, “call”) but bind() does
not.
The syntax is:
call(thisArg, arg1, ... , argN)
where
● thisArg is the object that the this keyword will point to
● arg1, arg2, ...argN (optional) are arguments of the function.

Example 1:
"use strict";
let obj = {
firstName: "Neo",
lastName: "D. Truman"
};

function logInfo() {
console.log(this.firstName + " " + this.lastName);
}
logInfo.call(obj); // Neo D. Truman
Example 2:
"use strict";
let obj = {
firstName: "Neo",
lastName: "D. Truman"
};

function logInfo(email) {
console.log(this.firstName + " " + this.lastName);
console.log(email);
}
logInfo.call(obj, "[email protected]");
// Neo D. Truman
// [email protected]
call() provides a new value of this to the function/method. With call() , you
can write a method once and then inherit it in another object, without having to
rewrite the method for the new object.
"use strict";
let obj1 = {
firstName: "Neo",
lastName: "D. Truman",
getFullname: function () {
return this.firstName + " " + this.lastName;
}
};
let obj2 = {
firstName: "John",
lastName: "Conner",
}
let fullname = obj1.getFullname.call(obj2);
console.log(fullname); // John Conner
We can also use call() to chain constructors for an object like the below
example.
"use strict";
function Thing(name, price) {
this.name = name;
this.price = price;
}

function SchoolThing(name, price) {


Thing.call(this, name, price);
this.category = 'School Thing';
}

const book = new SchoolThing('Student Book', 6);


console.log(book);
// SchoolThing {name: 'Student Book', price: 6, category: 'School Thing'}
5.13.3 apply
The Function.prototype.apply() method works in the same way as call()
however apply() will receive an array of arguments of the function. Accepting
an array of arguments helps us generate the arguments at runtime and “send” to
the function.
The syntax is:
apply(thisArg, argsArray)
where
● thisArg is the object that the this keyword will point to
● argsArray (optional) is the array of arguments of the function.

Example:
"use strict";
let obj = {
firstName: "Neo",
lastName: "D. Truman"
};

function logInfo(email) {
console.log(this.firstName + " " + this.lastName);
console.log(email);
}
// We can get email from user and push it into the array of arguments
let args = ["[email protected]"];
logInfo.apply(obj, args);
// Neo D. Truman
// [email protected]
5.14 Closure
5.14.1 Closure Introduction
A closure gives you access to an outer function's scope from an inner
function. In JavaScript, closure is created every time a function is created inside
another function and the function does use its parent’s variables.
"use strict";

function sayHi(firstName) {
let lastName = 'D. Truman';
return function doLog(message) {
debugger;
console.log(
`Hello ${firstName} ${lastName}. ${message}`
);
}
}

let greetingFunc = sayHi('Neo');


greetingFunc('How are you?');
// Hello Neo D. Truman. How are you?
When we run the above code, debugger of the browser will stop at the
debugger breakpoint (line 6) as the below picture.
We can see in the above picture, the doLog() function can access to these
variables:
● Local variables

message : parameter of the doLog() function


● Closure variables from the sayHi() function

firstName : parameter of the sayHi() function


lastName : local variable of the sayHi() function
The reason is that nested functions in JavaScript could form closures. A
closure is actually a function scope that consists of all local variables of the
function so that the inner function could refer to them. In this case,
greetingFunc is a reference to the instance of the function doLog() that is
created when sayHi() is run. The instance of doLog() having references
(fisrtName and lastName) to its parent function. For this reason, the fisrtName
and lastName variables are available in the Closure (sayHi) . As a result, when
greetingFunc is invoked, "Hello Neo D. Truman. How are you?" is printed to
the console panel.

Closure will negatively affect script performance both in terms


of processing speed and memory consumption. Therefore, we
should avoid creating nested functions if we don’t need them.
The below example shows that there are nested functions but there is no
closure since the inner function doesn’t refer to its parent’s variables.
"use strict";

function sayHi(firstName) {
let lastName = 'D. Truman';
return function doLog(message) {
debugger;
console.log(message);
}
}

let greetingFunc = sayHi('Neo');


greetingFunc('How are you?');
When we run the above code, debugger of the browser will stop at the
debugger breakpoint (line 6) as below picture.
We can see in the above picture, the doLog() function didn’t access to any
variables of the sayHi() function. Therefore, in the Scope Chain, there is no
Closure (sayHi) as the previous example.
5.14.2 Private Properties and Methods using Closures
We can emulate private properties and methods of objects using closures
since the object can access to variables and functions in its closure but does not
own them.
The following code illustrates how to use closures to define private properties
and methods of an object.
"use strict";
function makeUserObject() {
let name = 'Neo';
function allCap(str) {
return str.toUpperCase();
}

return {
getUpperCaseName: function () {
debugger;
return allCap(name);
},
getName: function () {
return name;
},
setName: function (newName) {
name = newName;
}
}
}
let u = makeUserObject();
console.log(u);

console.log(u.name); // undefined
console.log(u.allCap); // undefined

console.log(u.getUpperCaseName()); // 'NEO'
u.setName('John');
console.log(u.getName()); // 'John'
When we run the above code, debugger of the browser will stop at the
debugger breakpoint (line 10) as the below picture.

The getUpperCaseName() function can access variable name and invoke


function allCap() of the Closure. However, the u variable (user instance)
cannot access the name variable and the allCap() function. This behavior
emulates private properties and methods of the object.
5.15 Callback Function
A callback function is a function treated as a parameter of another function so
that the parent function will invoke it later.
Example:
"use strict";
function doLog(info) {
console.log(info);
}
function doAlert(info) {
alert(info);
}

function processName(callback) {
let name = 'Neo';
callback(name);
}
processName(doLog);
processName(doAlert);
5.16 Generator Function
The function* is exactly a generator that returns different values based on
the yield expressions inside the function body or the yield* , to another
generator function.
The next() method of generators returns an object with value and done
properties.
Example 1:
"use strict";
function* generator(x) {
yield x;
yield x * 2;
yield x * 5;
}

const gen = generator(10);

console.log(gen.next()); // {value: 10, done: false}


console.log(gen.next()); // {value: 20, done: false}
console.log(gen.next()); // {value: 50, done: false}
console.log(gen.next()); // {value: undefined, done: true}
Example 2:
"use strict";
function* anotherGenerator(y) {
yield y * 3;
yield y * 4;
}

function* generator(x) {
yield x;
yield x * 2;
yield* anotherGenerator(x);
yield x * 5;
}

const gen = generator(10);

console.log(gen.next()); // {value: 10, done: false}


console.log(gen.next()); // {value: 20, done: false}
console.log(gen.next()); // {value: 30, done: false}
console.log(gen.next()); // {value: 40, done: false}
console.log(gen.next()); // {value: 50, done: false}
console.log(gen.next()); // {value: undefined, done: true}
A return statement in a generator, when executed, will make the generator
finish (i.e., the done property of the object returned by it will be set to true ). If
a value is returned, it will be set as the value property of the object returned by
the generator.
Example 3:
"use strict";
function* generator(x) {
yield x;
return x * 2;
yield x * 5; // unreachable
}

const gen = generator(10);

console.log(gen.next()); // {value: 10, done: false}


console.log(gen.next()); // {value: 20, done: true}
console.log(gen.next()); // {value: undefined, done: true}
Much like a return statement, an error thrown inside the generator will make
the generator finished — unless caught within the generator's body. When a
generator is finished, subsequent next() calls will not execute any of that
generator's code, they will just return an object of this form: {value: undefined,
done: true} .
Example 4:
"use strict";
function* generator(x) {
yield x;
throw 'Error here';
yield x * 5; // unreachable
}

const gen = generator(10);

console.log(gen.next()); // {value: 10, done: false}


try {
console.log(gen.next());
} catch (error) {
console.log(error); // Error here
}
console.log(gen.next());// {value: undefined, done: true}
Provide a parameter to the next() method to send a value to the generator.
The syntax is:
next(value)
where
● variable = yield expression , the value passed to the next() function will
be assigned to variable .
Example:
"use strict";
function* idGenerator() {
let counter = 0;
while (true) {
let seeker = yield ++counter;
if (seeker) {
counter += seeker;
}
}
}
const generateId = idGenerator();
console.log(generateId.next().value); // 1
console.log(generateId.next().value); // 2
console.log(generateId.next().value); // 3
console.log(generateId.next(2).value); // 6 (skip two ids)
console.log(generateId.next().value); // 7
console.log(generateId.next(-3).value); // 5 (rollback three ids)
console.log(generateId.next().value); // 6
CHAPTER 6
this in JavaScript

6.1 this in Functions


In non-strict mode, this in functions (all kinds of functions) is the Window
object when working on a Web Browser, as the below example.
// function declaration
function testFunc1() {
console.log(this); // Window object
}
testFunc1();

// function expression
let testFunc2 = function () {
console.log(this); // Window object
}
testFunc2();

// nested functions
function test3() {
// function declaration
function testFunc3() {
console.log(this); // Window object
}
testFunc3();
}
test3();

function test4() {
// arrow function expression
let testFunc4 = () => {
console.log(this); // Window objects
}
testFunc4();
}
test4();
this in arrow function expressions, which is not in another function, is
always (in both strict mode and non-strict mode) the Window object when
working on a Web Browser.
"use strict";
// arrow function expression
let testFunc5 = () => {
console.log(this); // Window object
}
testFunc5();
In strict mode, this in functions (except the arrow functions) is undefined in
as the below example.
"use strict";
// function declaration
function testFunc1() {
console.log(this); // undefined
}
testFunc1();

// function expression
let testFunc2 = function () {
console.log(this); // undefined
}
testFunc2();

// nested functions
function test3() {
// function declaration
function testFunc3() {
console.log(this); // undefined
}
testFunc3();
}
test3();

function test4() {
// arrow function expression
let testFunc4 = () => {
console.log(this); // undefined
}
testFunc4();
}
test4();
6.2 this in Object’s Methods
Since object’s methods are also functions, the this keyword in the methods
behaves the same as in functions (described in the previous section) except
methods that are defined using function expressions.
this in methods that are defined using function expressions is always the
object as in the below examples.
Example 1:
"use strict";
let user = {
name: 'Neo',
logInfo: function () { // function expression
console.log(this);
},
showInfo: () => {
console.log(this);
}
}
user.logInfo(); // {name: 'Neo', logInfo: f, showInfo: f}
user.showInfo(); // Window object
Example 2:
"use strict";
function testUser() {
let user = {
name: 'Neo',
logInfo: function () { // function expression
console.log(this);
},
showInfo: () => {
console.log(this);
}
}
user.logInfo(); // {name: 'Neo', logInfo: f, showInfo: f}
user.showInfo(); // undefined
}
testUser();
Example 3: this in nested function will be undefined
"use strict";
let user = {
firstName: 'Neo',
lastName: 'D. Truman',
showFullname: function () {
console.log(this.firstName + ' ' + this.lastName);
function sayHi() {
console.log(this); // undefined
console.log('Hi ' + this.firstName + ' ' + this.lastName); // TypeError
}
sayHi();
}
}
user.showFullname();
Example 4: a solution for example 3 is saving the pointer to object at outer
function and use it in the inner function (a closure)
"use strict";
let user = {
firstName: 'Neo',
lastName: 'D. Truman',
showFullname: function () {
console.log(this.firstName + ' ' + this.lastName);
let self = this;
function sayHi() {
console.log(self);
// self is {firstName: 'Neo', lastName: 'D. Truman', showFullname: f}
console.log('Hi ' + self.firstName + ' ' + self.lastName);
// Hi Neo D. Truman
}
sayHi();
}
}
user.showFullname();
We should not use arrow functions in these cases:
● Case #1: Define methods in object or class. Examples are as above.
● Case #2: Define callback events of DOM’s elements.
● Case #3: Define methods in object’s prototype.

since this might refer to the Window object or could be undefined as


above examples.
Example for case #2:
HTML code:
<button id="btn-test">Test</button>
JavaScript code:
"use strict";
let btnTest = document.getElementById('btn-test');
// on clicked
btnTest.addEventListener('click', () => {
// this is not the btnTest button as expected
console.log(this); // Window object
});
// on double-clicked
btnTest.addEventListener('dblclick', function () {
console.log(this); // the 'btn-test' button
});
Example for case #3:
"use strict";
function User(name) {
this.name = name;
}

User.prototype.logInfo = () => {
console.log(this);
}
User.prototype.showInfo = function () {
console.log(this);
}

let user = new User('Neo');


user.logInfo(); // Window object
user.showInfo(); // {name: 'Neo'}
6.2.1 Implicit Binding
The below example actually defines object’s method using function
expression. Therefore, the this keyword refers to the object.
"use strict";
function welcome() {
console.log(this); // {name: 'Neo', sayHi: f}
console.log('Hi ' + this.name); // Hi Neo
}

let user = {
name: 'Neo',
sayHi: welcome
}
user.sayHi();
6.2.2 Implicit Lost Binding
Continue the example of implicit binding, we assign the object’s method to a
variable and invoke it as a function expression. As a result, this in function
expression will be undefined .
"use strict";
function welcome() {
console.log(this); // undefined
console.log('Hi ' + this.name); // TypeError
}
let user = {
name: 'Neo',
sayHi: welcome
}
let greetingFunc = user.sayHi;
greetingFunc();
6.3 globalThis
The globalThis property is used to ensure working on the global object of the
current environment. In the case of web browsers, globalThis is always the
Window object.
An example that is run on a web page:
"use strict";

function test() {
console.log(this); // undefined
console.log(globalThis); // Window object
}
test();
CHAPTER 7
Some Advanced Concepts

It's time to introduce some advanced concepts since we already know many
concepts about the JavaScript language.

7.1 Execution Context


In JavaScript, there are two phases of the execution context:
● Creation phase
● Execution phase

In the creation phase, a Syntax Parser reads our code line-by-line and create
space/memory for variable and function declarations (not the expressions).
Right after the creation phase, JavaScript engine will do the execution phase.
Since JavaScript is a single-threaded language, JavaScript engine will execute
our code line-by-line from the beginning to the end of the code.
Let’s try this example.
"use strict";
debugger; // #1

function aFooFunc() {
let myVar = 10;
aBarFunc();
console.log(myVar); // 10
}

function aBarFunc() {
myFunc();
console.log(myVar); // 100
}
let myFunc = function() {
console.log('myFunc');
debugger; // #2
}

let myVar = 100;


aFooFunc();
When we run the above code, debugger of the browser will stop at the first
debugger breakpoint (line 2) as the below picture.
At this point of time, the creation phase had just finished its job and the
execution phase was started running. We can check the Scope Chain in the
Scope panel:
● Script scope: both myFunc and myVar are undefined. myFunc is not
function yet because we wrote it as a function expression.
● Global scope: aBarFunc and aFooFunc are functions already because
we wrote them using function declarations.
On the Source panel, click the “Step over next function call” button (or
press F10 in Google Chrome — Windows OS), we can see that the next line of
code will be 16 since there is no execution code from line 4 to line 13 (these
lines of code are function declarations). And our variables are not changed at this
point of time.
Click the “Step over next function call” button again, current execution line
is 20 as the below picture.
At this point of time, myFunc (in Script scope) was changed from
undefined to a function as the function expression had been executed and
assigned to the myFunc variable.
Click the “Step over next function call” button one more time, we can see
the execution line moving to line 21 and myVar (in Script scope) changed from
undefined to 100 .
Click the “Step over next function call” button again and check the Call
Stack panel as in the below picture.
The Call Stack shows that:
● aFooFunc() function was invoked at line 21
● in the function body of aFooFunc() , aBarFunc() function was invoked
at line 6
● in the function body of aBarFunc() , myFunc() function was invoked at
line 11
● and currently, the execution line is at line 17 inside the myFunc()
function.
Click the “Step over next function call” button two times in order to finish
execution of myFunc() function. Now, the execution line is at line 12 inside of
the aBarFunc() function.
In the Local scope, there is no myVar variable but there is myVar having
value of 100 in the Script scope. Therefore, the aBarFunc() function will use
the myVar in the Script scope and print “100” to the Console panel after
executing line 12.
Click the “Step over next function call” button two times in order to finish
execution of aBarFunc() function. Now, the execution line is at line 7 inside of
the aFooFunc() function.

In the Local scope, there is myVar variable having value of 10 . Therefore,


the aFooFunc() function will print “10” to the Console panel after executing
line 7 since it ignores the myVar variable in Script scope.
7.2 Scope Chain
JavaScript resolves variables within a particular context by traversing through
the Scope Chain, moving from Local scope to Global scope.

7.2.1 Scope Chain Introduction


Every function has three scopes:
● Local scope
● Script scope
● Global scope

Example:
"use strict";
function sayHi(firstName) {
let lastName = 'D. Truman';
console.log(`Hello ${firstName} ${lastName}`);
debugger;
}

let aScriptVar = 10;


var aGlobalVar = 100;

let firstName = 'John';


let lastName = 'Conner';

sayHi('Neo'); // Hello Neo D. Truman


When we run the above code, debugger of the browser will stop at the
debugger breakpoint (line 5) as the below picture.
We can see variables in the scope chain inside the Scope panel:
● Both Local scope and Script scope have firstName and lastName
variables. However, as the rule of traversing, the sayHi() function used
the variables in Local scope and printed “Hello Neo D. Truman” to the
Console panel.
● let keyword creates variables in Script scope but var keyword creates
variables in Global scope. Therefore, we should avoid using the var
keyword as it could pollute the global namespace.

7.2.2 Closure Scope


The nested functions could have these scopes:
● Local scope
● Closure scope(s)
● Script scope
● Global scope

A common mistake is not realizing that in the case where the outer function is
itself a nested function, access to the outer function's scope includes the
enclosing scope of the outer function — effectively creating a chain of function
scopes. To demonstrate, consider the following example code.
"use strict";

function sayHi(firstName) {
let lastName = 'D. Truman';
return function doGreeting(greeting) {
return function doLog(message) {
let name = `${firstName} ${lastName}`;
console.log(
`${greeting} ${name}. ${message}`
);
debugger;
}
}
}

let firstName = 'John';


let lastName = 'Conner';

let setGreeting = sayHi('Neo');


let greetingFunc = setGreeting('Hello');
greetingFunc('How are you?');
// Hello Neo D. Truman. How are you?
When we run the above code, debugger of the browser will stop at the
debugger breakpoint (line 11) as below picture.

In the above picture, the doLog() function can access to these variables in
the below scope chain:
● Local variables

message : parameter of the doLog() function


● Closure variables from the doGreeting() function

greeting : parameter of the doGreeting() function


● Closure variables from the sayHi() function

firstName : parameter of the sayHi() function


lastName : local variable of the sayHi() function
● Script variables
firstName : a script variable
lastName : a script variable
setGreeting : is the doGreeting() function
greetingFunc : is the doLog() function
As the rule of traversing, the doLog() function used the firstName and
lastName variables in Closure (sayHi) scope and printed “Hello Neo D.
Truman. How are you?” to the Console panel. The other variables, such as
greeting , could be resolved while traversing the scope chain although it was not
available in the Local scope.
In the example above, there's a series of nested functions, some of which
have access to the outer functions' scope. In this context, we can say that nested
functions have access to all outer function scopes.
7.3 Hoisting
Hoisting allows functions or variables to be used in code before they are
declared. The hoisting can be explained using the execution context in the
previous section.

7.3.1 Function Hoisting


One of the advantages of hoisting is that it lets you use a function before you
declare it in your code.
Example:
"use strict";
test(); // 'Hello'

function test() {
console.log('Hello');
}
Since the function definition was created in the Creation Phase, the test()
function could be invoked in the Execution Phase.

Function expression will not be hoisted since it is not a


declaration.
7.3.2 Variable hoisting
Hoisting works with variables too, so you can use a variable in code before it
is declared and/or initialized.
Example:
"use strict";
console.log(x); // undefined
var x = 10;
However, JavaScript only hoists declarations using var keyword, not
initializations. Therefore, the above code print “undefined” to the Console panel.

Variable hoisting does not work with the let and const
keywords. It will raise an error if we write code like the below
example.
"use strict";
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 10;
CHAPTER 8
Promise

8.1 Promise Introduction


A Promise is an object representing the eventual completion or failure of an
asynchronous operation. Since most people are consumers of already-created
promises, this guide will explain consumption of returned promises before
explaining how to create them.
Essentially, a promise is a returned object to which you attach callbacks,
instead of passing callbacks into a function.
Unlike old-fashioned passed-in callbacks, a promise comes with some
guarantees:
● Callbacks added with then() will never be invoked before the
completion of the current run of the JavaScript event loop.
● These callbacks will be invoked even if they were added after the
success or failure of the asynchronous operation that the promise
represents.
● Multiple callbacks may be added by calling then() several times. They
will be invoked one after another, in the order in which they were
inserted.
A Promise is in one of these states:
● pending: initial state.
● fulfilled: meaning that the operation was completed successfully.
● rejected: meaning that the operation failed.

A pending promise can either be fulfilled with a value or rejected with a


reason (error). When either of these options happens, the associated handlers
queued up by a promise's then() method are called. If the promise has already
been fulfilled or rejected when a corresponding handler is attached, the
handler will be called, so there is no race condition between an asynchronous
operation completing and its handlers being attached.
8.1.1 Creating Promises
We use the Promise() constructor to create a new Promise object. The
constructor is primarily used to wrap functions that do not already support
promises.
Besides, Promise.resolve() and Promise.reject() are shortcuts to manually
create an already resolved or rejected promise respectively.
Example:
"use strict";
const promise1 = new Promise((resolve, reject) => {
const rand = Math.random();
if (rand > 0.5) {
resolve('A successful case');
}
else {
reject('An error case');
}
});

promise1
.then((value) => {
console.log(value);
})
.catch(error => {
console.log(error);
});
8.1.2 Promise Chain
One of the great things about using promises is chaining. A common need is
to execute two or more asynchronous operations, where each subsequent
operation starts when the previous operation succeeds, with the result from the
previous step. We accomplish this by creating a promise chain.
Example:
"use strict";
new Promise((resolve, reject) => {
console.log('Initial');
resolve();
})
.then(() => {
console.log('Do this');
})
.then(() => {
console.log('Do that');
})
.finally(() => {
console.log('Completed');
});
This will output the following text:
Initial
Do this
Do that
Completed
8.1.3 Chaining After a Catch
It's possible to chain after a failure (i.e., a catch) which is useful to
accomplish new actions even after an action failed in the chain. Try the
following example:
"use strict";
new Promise((resolve, reject) => {
console.log('Initial');
resolve();
})
.then(() => {
throw new Error('Something failed');
console.log('Do that');
})
.catch(() => {
console.error('Caught the error');
})
.then(() => {
console.log('Do this, no matter what happened before');
})
.finally(() => {
console.log('Completed');
});
This will output the following text:
Initial
Caught the error
Do this, no matter what happened before
Completed
8.1.4 Error Propagation
If there's an exception, the browser will look down the chain for the catch()
handlers or onRejected .
"use strict";
new Promise((resolve, reject) => {
console.log('Initial');
resolve();
})
.then(() => {
console.log('Do this');
})
.then(() => {
console.log('Do that');
})
.then(() => {
throw new Error('Something failed');
})
.then(() => {
console.log('Is this line printed?'); // No
})
.catch(() => {
console.error('Caught the error');
});
This will output the following text:
Initial
Do this
Do that
Caught the error
8.2 Composition
8.2.1 Parallel Composition
8.2.1.1 Promise.all()
The Promise.all() method takes an iterable of promises as an input, and
returns a single Promise that resolves to an array of the results of the input
promises.
This method can be useful for aggregating the results of multiple promises. It
is typically used when there are multiple related asynchronous tasks that the
overall code relies on to work successfully — all of whom we want to fulfill
before the code execution continues.
Example 1:
"use strict";

let p1 = new Promise((resolve, reject) => {


setTimeout(() => resolve('fast'), 2000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('faster'), 1000);
});
let p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('slow'), 4000);
});

const promises = [p1, p2, p3];


Promise.all(promises)
.then((res) => {
console.log(res); // ['fast', 'faster', 'slow']
});
We can see in the above example, returned values will be in order of the
Promises passed, regardless of completion order.
Promise.all() will reject immediately upon any of the input promises
rejecting as the below example.
Example 2:
"use strict";
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('A rejected case'));
}, 2000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('faster'), 1000);
});
let p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('slow'), 4000);
});

const promises = [p1, p2, p3];


Promise.all(promises)
.then((res) => {
// this code block was not executed
console.log(res);
})
.catch(error => {
console.log(error); // Error: A rejected case
});

8.2.1.2 Promise.allSettled()
In comparison to Promise.all() , the promise returned by
Promise.allSettled() will wait for all input promises to complete, regardless of
whether or not one rejects as the Example 3. Consequently, it will always return
the final result of every promise and function from the input iterable.
Example 3:
"use strict";
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('A rejected case'));
}, 2000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('faster'), 1000);
});
let p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('slow'), 4000);
});

const promises = [p1, p2, p3];


Promise.allSettled(promises)
.then((res) => {
console.log(res);
})
.catch(error => {
// this code block was not executed
console.log(error);
});
// [
// {status: "rejected", reason: Error: A rejected case},
// {status: "fulfilled", value: 'faster'},
// {status: "fulfilled", value: 'slow'}
// ]

8.2.1.3 Promise.race()
The Promise.race() method returns a promise that fulfills or rejects as soon
as one of the promises in an iterable fulfills or rejects, with the value or reason
from that promise.
Example 4:
"use strict";
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('A rejected case'));
}, 2000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('faster'), 1000);
});
let p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('slow'), 4000);
});

const promises = [p1, p2, p3];


Promise.race(promises)
.then((res) => {
console.log(res); // faster
})
.catch(error => {
console.log(error);
});
“ faster ” was printed to the console panel because the p2 promise had been
resolved first.
Example 5:
"use strict";
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('A rejected case'));
}, 200);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('faster'), 1000);
});
let p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('slow'), 4000);
});
const promises = [p1, p2, p3];
Promise.race(promises)
.then((res) => {
console.log(res);
})
.catch(error => {
console.log(error); // Error: A rejected case
});
“ Error: A rejected case ” was printed to the console panel because the p1
promise had been resolved first.

8.2.1.4 Promise.any()
Promise.any() takes an iterable of Promise objects. It returns a single
promise that resolves as soon as any of the promises in the iterable fulfills, with
the value of the fulfilled promise. If no promises in the iterable fulfill (if all of
the given promises are rejected), then the returned promise is rejected with an
AggregateError , a new subclass of Error that groups together individual errors.
Example 6:
"use strict";
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('A rejected case'));
}, 2000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('faster'), 1000);
});
let p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('slow'), 4000);
});

const promises = [p1, p2, p3];


Promise.any(promises)
.then((res) => {
console.log(res); // faster
})
.catch(error => {
console.log(error);
});
Example 7:
"use strict";
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('A rejected case'));
}, 200);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('faster'), 1000);
});
let p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('slow'), 4000);
});

const promises = [p1, p2, p3];


Promise.any(promises)
.then((res) => {
console.log(res); // faster
})
.catch(error => {
console.log(error);
});
8.2.2 Sequential composition
Sequential composition is possible using some clever JavaScript as the below
example.
"use strict";
let f1 = function () {
console.log('f1');
return 11;
};
let f2 = function () {
console.log('f2');
return 22;
};
let f3 = function () {
console.log('f3');
return 33;
};

[f1, f2, f3].reduce((p, f) => p.then(f), Promise.resolve())


.then(result3 => { console.log(result3) });
Basically, the bold code reduces an array of functions down to a promise
chain equivalent to:
Promise.resolve().then(f1).then(f2).then(f3)
.then(result3 => { console.log(result3) });
8.3 Execution Timing
To avoid surprises, functions passed to then() will never be called
synchronously, even with an already-resolved promise:
"use strict";
Promise.resolve().then(() => console.log(1));
console.log(2);
// 2
// 1
Instead of running immediately, the passed-in function is put on a microtask
queue, which means it runs later, just before control is returned to the event loop.

8.3.1 Microtask Queue


A microtask is a short function which is executed after the function or
program which created it exits and only if the JavaScript execution stack is
empty, but before returning control to the event loop being used by the user
agent to drive the script's execution environment.
JavaScript promises and the Mutation Observer API both use the microtask
queue to run their callbacks, but there are other times when the ability to defer
work until the current event loop pass is wrapping up. In order to allow
microtasks to be used by third-party libraries, frameworks, and polyfills, the
queueMicrotask() method is exposed on the Window and Worker interfaces.
8.3.2 Task Queue
A task is any JavaScript scheduled to be run by the standard mechanisms
such as initially starting to execute a program, an event triggering a callback, and
so forth. Other than by using events, you can enqueue a task by using
setTimeout() or setInterval() .
8.3.3 Task vs. Microtask
The difference between the task queue and the microtask queue is simple but
very important:
● When executing tasks from the task queue, the runtime executes each
task that is in the queue at the moment a new iteration of the event loop
begins. Tasks added to the queue after the iteration begins will not run
until the next iteration.
● Each time a task exits, and the execution context stack is empty, each
microtask in the microtask queue is executed, one after another. The
difference is that execution of microtasks continues until the queue is
empty — even if new ones are scheduled in the interim. In other words,
microtasks can enqueue new microtasks and those new microtasks will
execute before the next task begins to run, and before the end of the
current event loop iteration.
Let’s try the below example to understand the difference between microtask
queue and task queue.
"use strict"; // line 1
const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); // line
2

wait(0).then(() => console.log(1)); // line 4

Promise.resolve()
.then(() => console.log(2)) // line 7
.then(() => console.log(3)); // line 8

wait(0).then(() => console.log(4)); // line 10

console.log(5); // line 12
console.log(6); // line 13
// 5
// 6
// 2
// 3
// 1
// 4
Explanation:
Execute Microtask queue Task queue
Line 4 console.log(1);
Line 7 console.log(2);
Line 8 console.log(2);
console.log(3);
Line 10 console.log(1);
console.log(4);
Line 12 Print “5” to the console panel.
Line 13 Print “6” to the console panel.
After line Execute all microtasks (before execute the next task if any).
13
Print “2” to the console panel.
Print “3” to the console panel.
Execute all remain tasks.
Print “1” to the console panel.
Print “4” to the console panel.
8.4 Async Function
An async function is a function declared with the async keyword, and the
await keyword is permitted within it. The async and await keywords enable
asynchronous, promise-based behavior to be written in a cleaner style, avoiding
the need to explicitly configure promise chains.
Async functions can contain zero or more await expressions. Await
expressions make promise-returning functions behave as though they're
synchronous by suspending execution until the returned promise is fulfilled or
rejected. The resolved value of the promise is treated as the return value of the
await expression.
Example:
"use strict";
function downloadImage() {
return new Promise(resolve => {
setTimeout(() => {
resolve('Finished downloading');
}, 2000);
});
}

async function asyncCall() {


console.log('Call downloadImage()');
const result = await downloadImage();
console.log(result); // Finished downloading
console.log('End of asyncCall()');
}

asyncCall();
About the Author

Neo D. Truman has been using computers since 1998 when he was a child. He’s
got a Master of Science in Information Technology degree in 2011. He has 15+
years of experience in software development, especially full-stack web
development.
Other Books by The Author

https://www.amazon.com/dp/B0B1JCCP3S

https://www.amazon.com/dp/B09X7FB99L
Please leave a Review/Thought on Amazon

Thank you for purchasing and reading my book. I am extremely grateful and
hope you found value in reading it.
Please consider sharing it with friends or family and leaving a review on the
Amazon website. Your feedback and support are always appreciated, and inspire
me to continue doing what I love. Please go to Amazon and leave a review if
you’d like my book.
My email is [email protected]. If you have any questions, please send
me an email. I am always happy to hear from people who’ve read my books. I’ll
try my best to answer every email I receive. The subject should be “[JavaScript
for Dummies] <Your subject here>” so that I could filter and answer your
questions quickly.
You can also follow me on the Amazon Author page,
https://www.amazon.com/Neo-D-Truman/e/B09P6LHL2M

You might also like