Impatient Js Preview Book
Impatient Js Preview Book
Impatient Js Preview Book
2019
“An exhaustive resource, yet cuts out the fluff that clutters many
programming books – with explanations that are understandable and to
the point, as promised by the title! The quizzes and exercises are a very
useful feature to check and lock in your knowledge. And you can
definitely tear through the book fairly quickly, to get up and running in
JavaScript.”
— Pam Selle, thewebivore.com
I Background 9
1 About this book (ES2019 edition) 11
1.1 What’s in this book? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2 What is not covered by this book? . . . . . . . . . . . . . . . . . . . . . . 11
1.3 About the author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.4 Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
4 FAQ: JavaScript 23
4.1 What are good references for JavaScript? . . . . . . . . . . . . . . . . . . 23
4.2 How do I find out what JavaScript features are supported where? . . . . . 23
4.3 Where can I look up what features are planned for JavaScript? . . . . . . 24
4.4 Why does JavaScript fail silently so often? . . . . . . . . . . . . . . . . . 24
4.5 Why can’t we clean up JavaScript, by removing quirks and outdated fea-
tures? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.6 How can I quickly try out a piece of JavaScript code? . . . . . . . . . . . 24
II First steps 25
5 The big picture 27
5.1 What are you learning in this book? . . . . . . . . . . . . . . . . . . . . . 27
5.2 The structure of browsers and Node.js . . . . . . . . . . . . . . . . . . . . 27
5.3 JavaScript references . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3
4 CONTENTS
6 Syntax 29
6.1 An overview of JavaScript’s syntax . . . . . . . . . . . . . . . . . . . . . 30
6.2 (Advanced) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
6.3 Identifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
6.4 Statement vs. expression . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
6.5 Ambiguous syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
6.6 Semicolons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
6.7 Automatic semicolon insertion (ASI) . . . . . . . . . . . . . . . . . . . . 39
6.8 Semicolons: best practices . . . . . . . . . . . . . . . . . . . . . . . . . . 41
6.9 Strict mode vs. sloppy mode . . . . . . . . . . . . . . . . . . . . . . . . . 41
8 Assertion API 51
8.1 Assertions in software development . . . . . . . . . . . . . . . . . . . . . 51
8.2 How assertions are used in this book . . . . . . . . . . . . . . . . . . . . 51
8.3 Normal comparison vs. deep comparison . . . . . . . . . . . . . . . . . . 52
8.4 Quick reference: module assert . . . . . . . . . . . . . . . . . . . . . . . 53
11 Values 77
11.1 What’s a type? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
11.2 JavaScript’s type hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . 78
11.3 The types of the language specification . . . . . . . . . . . . . . . . . . . 78
11.4 Primitive values vs. objects . . . . . . . . . . . . . . . . . . . . . . . . . . 79
11.5 The operators typeof and instanceof: what’s the type of a value? . . . . 81
11.6 Classes and constructor functions . . . . . . . . . . . . . . . . . . . . . . 83
CONTENTS 5
12 Operators 87
12.1 Making sense of operators . . . . . . . . . . . . . . . . . . . . . . . . . . 87
12.2 The plus operator (+) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
12.3 Assignment operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
12.4 Equality: == vs. === . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
12.5 Ordering operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
12.6 Various other operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
IV Primitive values 95
13 The non-values undefined and null 97
13.1 undefined vs. null . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
13.2 Occurrences of undefined and null . . . . . . . . . . . . . . . . . . . . . 98
13.3 Checking for undefined or null . . . . . . . . . . . . . . . . . . . . . . . 99
13.4 undefined and null don’t have properties . . . . . . . . . . . . . . . . . 99
13.5 The history of undefined and null . . . . . . . . . . . . . . . . . . . . . 100
14 Booleans 101
14.1 Converting to boolean . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
14.2 Falsy and truthy values . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
14.3 Conditional operator (? :) . . . . . . . . . . . . . . . . . . . . . . . . . . 104
14.4 Binary logical operators: And (x && y), Or (x || y) . . . . . . . . . . . . 105
14.5 Logical Not (!) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
15 Numbers 109
15.1 JavaScript only has floating point numbers . . . . . . . . . . . . . . . . . 110
15.2 Number literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
15.3 Arithmetic operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
15.4 Converting to number . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
15.5 Error values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
15.6 Error value: NaN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
15.7 Error value: Infinity . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
15.8 The precision of numbers: careful with decimal fractions . . . . . . . . . 117
15.9 (Advanced) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
15.10Background: floating point precision . . . . . . . . . . . . . . . . . . . . 117
15.11 Integers in JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
15.12Bitwise operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
15.13Quick reference: numbers . . . . . . . . . . . . . . . . . . . . . . . . . . 124
16 Math 129
16.1 Data properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
16.2 Exponents, roots, logarithms . . . . . . . . . . . . . . . . . . . . . . . . . 130
16.3 Rounding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
16.4 Trigonometric Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
16.5 Various other functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
16.6 Sources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
6 CONTENTS
18 Strings 143
18.1 Plain string literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
18.2 Accessing characters and code points . . . . . . . . . . . . . . . . . . . . 144
18.3 String concatenation via + . . . . . . . . . . . . . . . . . . . . . . . . . . 145
18.4 Converting to string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
18.5 Comparing strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
18.6 Atoms of text: Unicode characters, JavaScript characters, grapheme clusters148
18.7 Quick reference: Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
20 Symbols 169
20.1 Use cases for symbols . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
20.2 Publicly known symbols . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
20.3 Converting symbols . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
VI Modularity 211
24 Modules 213
24.1 JavaScript source code formats . . . . . . . . . . . . . . . . . . . . . . . . 214
24.2 Before we had modules, we had scripts . . . . . . . . . . . . . . . . . . . 214
24.3 Module systems created prior to ES6 . . . . . . . . . . . . . . . . . . . . 216
24.4 ECMAScript modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
24.5 Exporting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
24.6 Importing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
24.7 npm packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
24.8 Naming modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
24.9 Module specifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
24.10Loading modules dynamically via import() . . . . . . . . . . . . . . . . 227
24.11 Preview: import.meta.url . . . . . . . . . . . . . . . . . . . . . . . . . . 229
24.12Quick reference: exporting and importing . . . . . . . . . . . . . . . . . . 231
Background
9
Chapter 1
Contents
1.1 What’s in this book? . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2 What is not covered by this book? . . . . . . . . . . . . . . . . . . . 11
1.3 About the author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.4 Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Highlights:
No prior knowledge of JavaScript is required, but you should know how to program.
11
12 1 About this book (ES2019 edition)
1.4 Acknowledgements
• Cover by Fran Caye.
• Thanks for answering questions, discussing language topics, etc.:
– Allen Wirfs-Brock (@awbjs)
– Daniel Ehrenberg (@littledan)
– Mathias Bynens (@mathias)
• Thanks for reviewing:
– Johannes Weber (@jowe)
[Generated: 2019-06-10 17:07]
Chapter 2
Contents
2.1 How to read this book . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.1.1 Isn’t this book too long if you are impatient? . . . . . . . . . . 13
2.1.2 Why are some chapters and sections marked with “(advanced)”? 14
2.1.3 Why are some chapters marked with “(bonus)”? . . . . . . . . 14
2.2 Digital editions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.2.1 How can I buy a digital edition of this book? . . . . . . . . . . 14
2.2.2 How do I submit feedback and corrections? . . . . . . . . . . . 14
2.2.3 How do I get updates for the downloads I bought at Payhip? . 14
2.3 Notations and conventions . . . . . . . . . . . . . . . . . . . . . . . 14
2.3.1 What is a type signature? Why am I seeing static types in this
book? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.3.2 What do the notes with icons mean? . . . . . . . . . . . . . . . 15
This chapter answers questions you may have and gives tips for reading this book.
13
14 2 FAQ: book and supplementary material
• It serves as a reference. If there is a topic that you are interested in, you can find in-
formation on it via the table of contents or via the index. Due to basic and advanced
content being mixed, everything you need is usually in a single location.
The quizzes and exercises play an important part in helping you practice and retain what
you have learned.
2.1.2 Why are some chapters and sections marked with “(advanced)”?
Several chapters and sections are marked with “(advanced)”. The idea is that you can
initially skip them. That is, you can get a quick working knowledge of JavaScript by only
reading the basic (non-advanced) content.
As your knowledge evolves, you can later come back to some or all of the advanced
content.
That is called the type signature of Number.isFinite(). This notation, especially the static
types number of num and boolean of the result, are not real JavaScript. The notation
is borrowed from the compile-to-JavaScript language TypeScript (which is mostly just
JavaScript plus static typing).
Why is this notation being used? It helps give you a quick idea of how a function works.
The notation is explained in detail in a 2ality blog post, but is usually relatively intuitive.
Reading instructions
Explains how to best read the content.
External content
Points to additional, external, content.
Tip
Gives a tip related to the current content.
Question
Asks and answers a question pertinent to the current content (think FAQ).
Warning
Warns about pitfalls etc.
Details
Provides additional details, complementing the current content. It is similar to a
footnote.
16 2 FAQ: book and supplementary material
Exercise
Mentions the path of a test-driven exercise that you can do at that point.
Quiz
Indicates that there is a quiz for the current (part of a) chapter.
Chapter 3
Contents
3.1 How JavaScript was created . . . . . . . . . . . . . . . . . . . . . . . 17
3.2 Standardizing JavaScript . . . . . . . . . . . . . . . . . . . . . . . . 18
3.3 Timeline of ECMAScript versions . . . . . . . . . . . . . . . . . . . 18
3.4 Ecma Technical Committee 39 (TC39) . . . . . . . . . . . . . . . . . . 19
3.5 The TC39 process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.5.1 Tip: think in individual features and stages, not ECMAScript
versions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.6 FAQ: TC39 process . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.6.1 How is [my favorite proposed feature] doing? . . . . . . . . . 21
3.6.2 Is there an official list of ECMAScript features? . . . . . . . . . 21
3.7 Evolving JavaScript: don’t break the web . . . . . . . . . . . . . . . 21
17
18 3 History and evolution of JavaScript
The language described by these standards is called ECMAScript, not JavaScript. A differ-
ent name was chosen, because Sun (now Oracle) had a trademark for the latter name. The
“ECMA” in “ECMAScript” comes from the organization that hosts the primary standard.
The original name of that organization was ECMA, an acronym for European Computer
Manufacturers Association. It was later changed to Ecma International (with “Ecma” being
a proper name, not an acronym), because the organization’s activities had expanded be-
yond Europe. The initial all-caps acronym explains the spelling of ECMAScript.
In principle, JavaScript and ECMAScript mean the same thing. Sometimes, the following
distinction is made:
Every two months, TC39 has meetings that are attended by member-appointed delegates
and invited experts. The minutes of those meetings are public, in a GitHub repository.
• If too much time passes between releases then features that are ready early, have
to wait a long time until they can be released. And features that are ready late, risk
being rushed to make the deadline.
• Features were often designed long before they were implemented and used. De-
sign deficiencies related to implementation and use were therefore discovered too
late.
The result: smaller, incremental releases, whose features have already been field-tested.
Fig. 3.1 illustrates the TC39 process.
ES2016 was the first ECMAScript version that was designed according to the TC39 pro-
cess.
3.5.1 Tip: think in individual features and stages, not ECMAScript ver-
sions
Up to and including ES6, it was most common to think about JavaScript in terms of
ECMAScript versions. For example: “Does this browser support ES6, yet?”
Starting with ES2016, it’s better to think in individual features: Once a feature reaches
stage 4, you can safely use it (if it’s supported by the JavaScript engines you are targeting).
You don’t have to wait until the next ECMAScript release.
20 3 History and evolution of JavaScript
Pick champions
Spec complete
Figure 3.1: Each ECMAScript feature proposal goes through stages that are numbered
from 0 to 4. Champions are TC39 members that support the authors of a feature. Test
262 is a suite of tests that checks JavaScript engines for compliance with the language
specification.
3.6 FAQ: TC39 process 21
Quiz
See quiz app.
22 3 History and evolution of JavaScript
Chapter 4
FAQ: JavaScript
Contents
4.1 What are good references for JavaScript? . . . . . . . . . . . . . . . . 23
4.2 How do I find out what JavaScript features are supported where? . . 23
4.3 Where can I look up what features are planned for JavaScript? . . . 24
4.4 Why does JavaScript fail silently so often? . . . . . . . . . . . . . . . 24
4.5 Why can’t we clean up JavaScript, by removing quirks and outdated
features? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.6 How can I quickly try out a piece of JavaScript code? . . . . . . . . . 24
23
24 4 FAQ: JavaScript
Second example: If an arithmetic computation fails, you get an error value, not an excep-
tion.
> 1 / 0
Infinity
The reason for the silent failures is historical: JavaScript did not have exceptions until
ECMAScript 3. Since then, its designers have tried to avoid silent failures.
First steps
25
Chapter 5
Contents
5.1 What are you learning in this book? . . . . . . . . . . . . . . . . . . 27
5.2 The structure of browsers and Node.js . . . . . . . . . . . . . . . . . 27
5.3 JavaScript references . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.4 Further reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
In this chapter, I’d like to paint the big picture: What are you learning in this book and
how does it fit into the overall landscape of web development?
27
28 5 The big picture
JS standard
Platform API
library
Figure 5.1: The structure of the two JavaScript platforms web browser and Node.js. The
APIs “standard library” and “platform API” are hosted on top of a foundational layer
with a JavaScript engine and a platform-specific “core”.
Syntax
Contents
6.1 An overview of JavaScript’s syntax . . . . . . . . . . . . . . . . . . . 30
6.1.1 Basic syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
6.1.2 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
6.1.3 Legal variable and property names . . . . . . . . . . . . . . . 33
6.1.4 Casing styles . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.1.5 Capitalization of names . . . . . . . . . . . . . . . . . . . . . . 33
6.1.6 More naming conventions . . . . . . . . . . . . . . . . . . . . 34
6.1.7 Where to put semicolons? . . . . . . . . . . . . . . . . . . . . 34
6.2 (Advanced) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
6.3 Identifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
6.3.1 Valid identifiers (variable names etc.) . . . . . . . . . . . . . . 35
6.3.2 Reserved words . . . . . . . . . . . . . . . . . . . . . . . . . . 35
6.4 Statement vs. expression . . . . . . . . . . . . . . . . . . . . . . . . . 36
6.4.1 Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
6.4.2 Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
6.4.3 What is allowed where? . . . . . . . . . . . . . . . . . . . . . 37
6.5 Ambiguous syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
6.5.1 Same syntax: function declaration and function expression . . 37
6.5.2 Same syntax: object literal and block . . . . . . . . . . . . . . 38
6.5.3 Disambiguation . . . . . . . . . . . . . . . . . . . . . . . . . . 38
6.6 Semicolons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
6.6.1 Rule of thumb for semicolons . . . . . . . . . . . . . . . . . . 38
6.6.2 Semicolons: control statements . . . . . . . . . . . . . . . . . . 39
6.7 Automatic semicolon insertion (ASI) . . . . . . . . . . . . . . . . . . 39
6.7.1 ASI triggered unexpectedly . . . . . . . . . . . . . . . . . . . 40
6.7.2 ASI unexpectedly not triggered . . . . . . . . . . . . . . . . . 40
6.8 Semicolons: best practices . . . . . . . . . . . . . . . . . . . . . . . . 41
6.9 Strict mode vs. sloppy mode . . . . . . . . . . . . . . . . . . . . . . . 41
29
30 6 Syntax
/*
Comment with
multiple lines
*/
// Booleans
true
false
An assertion describes what the result of a computation is expected to look like and throws
an exception if those expectations aren’t correct. For example, the following assertion
states that the result of the computation 7 plus 1 must be 8:
assert.equal(7 + 1, 8);
assert.equal() is a method call (the object is assert, the method is .equal()) with two
arguments: the actual result and the expected result. It is part of a Node.js assertion API
that is explained later in this book.
Logging to the console of a browser or Node.js:
// Printing a value to standard out (another method call)
console.log('Hello!');
Operators:
// Operators for booleans
assert.equal(true && false, false); // And
6.1 An overview of JavaScript’s syntax 31
// Comparison operators
assert.equal(3 < 4, true);
assert.equal(3 <= 4, true);
assert.equal('abc' === 'abc', true);
assert.equal('abc' !== 'def', true);
Declaring variables:
// Conditional statement
if (x < 0) { // is x less than zero?
x = -x;
}
Arrow function expressions (used especially as arguments of function calls and method
calls):
// Equivalent to add2:
const add3 = (a, b) => a + b;
32 6 Syntax
The previous code contains the following two arrow functions (the terms expression and
statement are explained later in this chapter):
Objects:
6.1.2 Modules
Each module is a single file. Consider, for example, the following two files with modules
in them:
file-tools.mjs
main.mjs
The module in main.mjs imports the whole module path and the function is-
TextFilePath():
Some words have special meaning in JavaScript and are called reserved. Examples include:
if, true, const.
const if = 123;
// SyntaxError: Unexpected token if
Lowercase:
Uppercase:
34 6 Syntax
• Classes: MyClass
• Constants: MY_CONSTANT
– Constants are also often written in camel case: myConstant
If the name of a parameter starting with an underscore (or is an underscore) means that
this parameter is not used. For example:
arr.map((_x, i) => i)
If the name of a property of an object starts with an underscore then that property is
considered private:
class ValueWrapper {
constructor(value) {
this._value = value;
}
}
const x = 123;
func();
while (false) {
// ···
} // no semicolon
function func() {
// ···
} // no semicolon
However, adding a semicolon after such a statement is not a syntax error – it is interpreted
as an empty statement:
Quiz: basic
See quiz app.
6.2 (Advanced) 35
6.2 (Advanced)
All remaining sections of this chapter are advanced.
6.3 Identifiers
6.3.1 Valid identifiers (variable names etc.)
First character:
• Unicode letter (including accented characters such as é and ü and characters from
non-latin alphabets, such as α)
• $
• _
Subsequent characters:
Examples:
const ε = 0.0001;
const строка = '';
let _tmp = 0;
const $foo2 = true;
await break case catch class const continue debugger default delete
do else export extends finally for function if import in instanceof
let new return static super switch this throw try typeof var void while
with yield
The following tokens are also keywords, but currently not used in the language:
Technically, these words are not reserved, but you should avoid them, too, because they
effectively are keywords:
You shouldn’t use the names of global variables (String, Math, etc.) for your own vari-
ables and parameters, either.
36 6 Syntax
6.4.1 Statements
A statement is a piece of code that can be executed and performs some kind of action. For
example, if is a statement:
let myStr;
if (myBool) {
myStr = 'Yes';
} else {
myStr = 'No';
}
function twice(x) {
return x + x;
}
6.4.2 Expressions
An expression is a piece of code that can be evaluated to produce a value. For example, the
code between the parentheses is an expression:
The operator _?_:_ used between the parentheses is called the ternary operator. It is the
expression version of the if statement.
Let’s look at more examples of expressions. We enter expressions and the REPL evaluates
them for us:
function max(x, y) {
if (x > y) {
return x;
} else {
return y;
}
}
However, expressions can be used as statements. Then they are called expression state-
ments. The opposite is not true: when the context requires an expression, you can’t use a
statement.
The following code demonstrates that any expression bar() can be either expression or
statement – it depends on the context:
function f() {
console.log(bar()); // bar() is expression
bar(); // bar(); is (expression) statement
}
function id(x) {
return x;
}
{
}
6.5.3 Disambiguation
The ambiguities are only a problem in statement context: If the JavaScript parser encoun-
ters ambiguous syntax, it doesn’t know if it’s a plain statement or an expression statement.
For example:
To resolve the ambiguity, statements starting with function or { are never interpreted as
expressions. If you want an expression statement to start with either one of these tokens,
you must wrap it in parentheses:
// Output:
// 'abc'
In this code:
Later in this book, we’ll see more examples of pitfalls caused by syntactic ambiguity:
6.6 Semicolons
6.6.1 Rule of thumb for semicolons
Each statement is terminated by a semicolon.
6.7 Automatic semicolon insertion (ASI) 39
const x = 3;
someFunction('abc');
i++;
function foo() {
// ···
}
if (y > 0) {
// ···
}
The whole const declaration (a statement) ends with a semicolon, but inside it, there is
an arrow function expression. That is: It’s not the statement per se that ends with a curly
brace; it’s the embedded arrow function expression. That’s why there is a semicolon at
the end.
while (condition)
statement
But blocks are also statements and therefore legal bodies of control statements:
while (a > 0) {
a--;
}
If you want a loop to have an empty body, your first option is an empty statement (which
is just a semicolon):
That is, an empty return statement, followed by a code block, followed by an empty
statement.
Why does JavaScript do this? It protects against accidentally returning a value in a line
after a return.
Parsed as:
a = b + c(d + e).print();
Parsed as:
a = b / hi / g.exec(c).map(d);
Executed as:
const propKey = ('ul','ol'); // comma operator
assert.equal(propKey, 'ol');
'use strict';
The neat thing about this “directive” is that ECMAScript versions before 5 simply ignore
it: it’s an expression statement that does nothing.
You can also switch on strict mode for just a single function:
function functionInStrictMode() {
'use strict';
}
6.9.2.1 Sloppy mode pitfall: changing an undeclared variable creates a global vari-
able
Strict mode does it better and throws a ReferenceError. That makes it easier to detect
typos.
function strictFunc() {
'use strict';
undeclaredVar2 = 123;
}
assert.throws(
() => strictFunc(),
{
name: 'ReferenceError',
message: 'undeclaredVar2 is not defined',
});
The assert.throws() states that its first argument, a function, throws a ReferenceError
when it is called.
In strict mode, a variable created via a function declaration only exists within the inner-
most enclosing block:
function strictFunc() {
'use strict';
6.9 Strict mode vs. sloppy mode 43
{
function foo() { return 123 }
}
return foo(); // ReferenceError
}
assert.throws(
() => strictFunc(),
{
name: 'ReferenceError',
message: 'foo is not defined',
});
function sloppyFunc() {
{
function foo() { return 123 }
}
return foo(); // works
}
assert.equal(sloppyFunc(), 123);
6.9.2.3 Sloppy mode doesn’t throw exceptions when changing immutable data
In strict mode, you get an exception if you try to change immutable data:
function strictFunc() {
'use strict';
true.prop = 1; // TypeError
}
assert.throws(
() => strictFunc(),
{
name: 'TypeError',
message: "Cannot create property 'prop' on boolean 'true'",
});
function sloppyFunc() {
true.prop = 1; // fails silently
return true.prop;
}
assert.equal(sloppyFunc(), undefined);
Quiz: advanced
See quiz app.
Chapter 7
Contents
7.1 Trying out JavaScript code . . . . . . . . . . . . . . . . . . . . . . . . 45
7.1.1 Browser consoles . . . . . . . . . . . . . . . . . . . . . . . . . 45
7.1.2 The Node.js REPL . . . . . . . . . . . . . . . . . . . . . . . . . 47
7.1.3 Other options . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
7.2 The console.* API: printing data and more . . . . . . . . . . . . . . 47
7.2.1 Printing values: console.log() (stdout) . . . . . . . . . . . . 48
7.2.2 Printing error information: console.error() (stderr) . . . . . 49
7.2.3 Printing nested objects via JSON.stringify() . . . . . . . . . 49
45
46 7 Consoles: interactive JavaScript command lines
Figure 7.1: The console of the web browser “Google Chrome” is open (in the bottom half
of window) while visiting a web page.
7.2 The console.* API: printing data and more 47
Figure 7.2: Starting and using the Node.js REPL (interactive command line).
• There are many web apps that let you experiment with JavaScript in web browsers.
For example, Babel’s REPL.
• There are also native apps and IDE plugins for running JavaScript.
The full console.* API is documented on MDN web docs and on the Node.js website. It
is not part of the JavaScript language standard, but much functionality is supported by
both browsers and Node.js.
In this chapter, we only look at the following two methods for printing data. “Printing”
means displaying in the console.
• console.log()
• console.error()
The first variant prints (text representations of) values on the console:
console.log('abc', 123, true);
// Output:
// abc 123 true
At the end, console.log() always prints a newline. Therefore, if you call it with zero
arguments, it just prints a newline.
These are some of the directives you can use for substitutions:
• %s converts the corresponding value to a string and inserts it.
console.log('%s %s', 'abc', 123);
// Output:
// abc 123
• %% inserts a single %.
console.log('%s%%', 99);
// Output:
// 99%
Output:
{
"first": "Jane",
"last": "Doe"
}
50 7 Consoles: interactive JavaScript command lines
Chapter 8
Assertion API
Contents
8.1 Assertions in software development . . . . . . . . . . . . . . . . . . 51
8.2 How assertions are used in this book . . . . . . . . . . . . . . . . . . 51
8.2.1 Documenting results in code examples via assertions . . . . . 52
8.2.2 Implementing test-driven exercises via assertions . . . . . . . 52
8.3 Normal comparison vs. deep comparison . . . . . . . . . . . . . . . 52
8.4 Quick reference: module assert . . . . . . . . . . . . . . . . . . . . 53
8.4.1 Normal equality . . . . . . . . . . . . . . . . . . . . . . . . . . 53
8.4.2 Deep equality . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
8.4.3 Expecting exceptions . . . . . . . . . . . . . . . . . . . . . . . 53
8.4.4 Another tool function . . . . . . . . . . . . . . . . . . . . . . . 54
This assertion states that the expected result of 3 plus 5 is 8. The import statement uses
the recommended strict version of assert.
51
52 8 Assertion API
function id(x) {
return x;
}
assert.equal(id('abc'), 'abc');
For more information, consult §9 “Getting started with quizzes and exercises”.
assert.equal(3+3, 6);
assert.notEqual(3+3, 22);
The optional last parameter message can be used to explain what is asserted. If the asser-
tion fails, the message is used to set up the AssertionError that is thrown.
let e;
try {
const x = 3;
assert.equal(x, 8, 'x must be equal to 8')
} catch (err) {
assert.equal(String(err), 'AssertionError [ERR_ASSERTION]: x must be equal to 8');
}
assert.deepEqual([1,2,3], [1,2,3]);
assert.deepEqual([], []);
assert.notDeepEqual([1,2,3], [1,2]);
assert.throws(
() => {
null.prop;
}
);
assert.throws(
() => {
null.prop;
},
TypeError
);
assert.throws(
() => {
null.prop;
},
/^TypeError: Cannot read property 'prop' of null$/
);
assert.throws(
() => {
null.prop;
},
{
name: 'TypeError',
message: `Cannot read property 'prop' of null`,
}
);
try {
functionThatShouldThrow();
assert.fail();
} catch (_) {
// Success
}
8.4 Quick reference: module assert 55
Quiz
See quiz app.
56 8 Assertion API
Chapter 9
Contents
9.1 Quizzes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
9.2 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
9.2.1 Installing the exercises . . . . . . . . . . . . . . . . . . . . . . 57
9.2.2 Running exercises . . . . . . . . . . . . . . . . . . . . . . . . . 58
9.3 Unit tests in JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . 58
9.3.1 A typical test . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
9.3.2 Asynchronous tests in AVA . . . . . . . . . . . . . . . . . . . . 59
Throughout most chapters, there are quizzes and exercises. These are a paid feature, but a
comprehensive preview is available. This chapter explains how to get started with them.
9.1 Quizzes
Installation:
9.2 Exercises
9.2.1 Installing the exercises
To install the exercises:
57
58 9 Getting started with quizzes and exercises
The key thing here is: everything you want to test must be exported. Otherwise, the test
code can’t access it.
The core of this test file is line E – an assertion: assert.equal() specifies that the expected
result of id('abc') is 'abc'.
npm t demos/quizzes-exercises/id_test.mjs
The t is an abbreviation for test. That is, the long version of this command is:
Reading
You can postpone reading this section until you get to the chapters on asynchronous
programming.
Writing tests for asynchronous code requires extra work: The test receives its results later
and has to signal to AVA that it isn’t finished, yet, when it returns. The following subsec-
tions examine three ways of doing so.
test.cb('divideCallback', t => {
divideCallback(8, 4, (error, result) => {
60 9 Getting started with quizzes and exercises
if (error) {
t.end(error);
} else {
assert.strictEqual(result, 2);
t.end();
}
});
});
You don’t need to explicitly return anything: The implicitly returned undefined is used
to fulfill the Promise returned by this async function. And if the test code throws an
exception then the async function takes care of rejecting the returned Promise.
Part III
61
Chapter 10
Contents
10.1 let . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
10.2 const . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
10.2.1 const and immutability . . . . . . . . . . . . . . . . . . . . . 64
10.2.2 const and loops . . . . . . . . . . . . . . . . . . . . . . . . . . 65
10.3 Deciding between let and const . . . . . . . . . . . . . . . . . . . . 65
10.4 The scope of a variable . . . . . . . . . . . . . . . . . . . . . . . . . . 65
10.4.1 Shadowing variables . . . . . . . . . . . . . . . . . . . . . . . 66
10.5 (Advanced) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
10.6 Terminology: static vs. dynamic . . . . . . . . . . . . . . . . . . . . . 67
10.6.1 Static phenomenon: scopes of variables . . . . . . . . . . . . . 67
10.6.2 Dynamic phenomenon: function calls . . . . . . . . . . . . . . 67
10.7 Global variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
10.7.1 The global object . . . . . . . . . . . . . . . . . . . . . . . . . 68
10.7.2 Avoid the global object! . . . . . . . . . . . . . . . . . . . . . . 69
10.8 Declarations: scope and activation . . . . . . . . . . . . . . . . . . . 69
10.8.1 const and let: temporal dead zone . . . . . . . . . . . . . . . 70
10.8.2 Function declarations and early activation . . . . . . . . . . . 71
10.8.3 Class declarations are not activated early . . . . . . . . . . . . 73
10.8.4 var: hoisting (partial early activation) . . . . . . . . . . . . . . 73
10.9 Closures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
10.9.1 Bound variables vs. free variables . . . . . . . . . . . . . . . . 74
10.9.2 What is a closure? . . . . . . . . . . . . . . . . . . . . . . . . . 74
10.9.3 Example: A factory for incrementors . . . . . . . . . . . . . . 75
10.9.4 Use cases for closures . . . . . . . . . . . . . . . . . . . . . . . 75
10.10Further reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
63
64 10 Variables and assignment
10.1 let
Variables declared via let are mutable:
let i;
i = 0;
i = i + 1;
assert.equal(i, 1);
10.2 const
Variables declared via const are immutable. You must always initialize immediately:
const i = 0; // must initialize
assert.throws(
() => { i = i + 1 },
{
name: 'TypeError',
message: 'Assignment to constant variable.',
}
);
}
);
Exercise: const
exercises/variables-assignment/const_exrc.mjs
const z = 2;
assert.equal(x, 0);
assert.equal(y, 1);
assert.equal(z, 2);
}
}
}
// Outside. Not accessible: x, y, z
assert.throws(
() => console.log(x),
{
name: 'ReferenceError',
message: 'x is not defined',
}
);
Why eval()?
eval() delays parsing (and therefore the SyntaxError), until the callback of as-
sert.throws() is executed. If we didn’t use it, we’d already get an error when this
code is parsed and assert.throws() wouldn’t even be executed.
You can, however, nest a block and use the same variable name x that you used outside
the block:
const x = 1;
assert.equal(x, 1);
{
10.5 (Advanced) 67
const x = 2;
assert.equal(x, 2);
}
assert.equal(x, 1);
Inside the block, the inner x is the only accessible variable with that name. The inner x is
said to shadow the outer x. Once you leave the block, you can access the old value again.
Quiz: basic
See quiz app.
10.5 (Advanced)
All remaining sections are advanced.
x is statically (or lexically) scoped. That is, its scope is fixed and doesn’t change at runtime.
Whether or not the function call in line A happens, can only be decided at runtime.
Function calls form a dynamic tree (via dynamic calls).
68 10 Variables and assignment
• The outermost global scope is special: its variables can be accessed via the proper-
ties of an object, the so-called global object. The global object is referred to by window
and self in browsers. Variables in this scope are created via:
• Nested in that scope is the global scope of scripts. Variables in this scope are created
by let, const and class at the top level of a script.
• Nested in that scope are the scopes of modules. Each module has its own global
scope. Variables in that scope are created by declarations at the top level of the
module.
Global object
Script scope of var, function declarations
M1 M2 M3 ···
• If you create a variable in the outermost global scope, the global object gets a new
property. If you change such a global variable, the property changes.
• If you create or delete a property of the global object, the corresponding global
variable is created or deleted. If you change a property of the global object, the
corresponding global variable changes.
• window: is the classic way of referring to the global object. But it only works in
normal browser code, not in Node.js and not in Web Workers (processes running
concurrently to normal browser code; consult $full for details).
• self: is available everywhere in browsers, including in Web Workers. But it isn’t
supported by Node.js.
• global: is only available in Node.js.
Let’s examine how window works:
// At the top level of a script
var myGlobalVariable = 123;
assert.equal('myGlobalVariable' in window, true);
delete window.myGlobalVariable;
assert.throws(() => console.log(myGlobalVariable), ReferenceError);
import is described in §24.4 “ECMAScript modules”. The following sections describe the
other constructs in more detail.
For JavaScript, TC39 needed to decide what happens if you access a constant in its direct
scope, before its declaration:
{
console.log(x); // What happens here?
const x;
}
(1) was rejected, because there is no precedent in the language for this approach. It would
therefore not be intuitive to JavaScript programmers.
(2) was rejected, because then x wouldn’t be a constant – it would have different values
before and after its declaration.
let uses the same approach (3) as const, so that both work similarly and it’s easy to
switch between them.
The time between entering the scope of a variable and executing its declaration is called
the temporal dead zone (TDZ) of that variable:
• During this time, the variable is considered to be uninitialized (as if that were a
special value it has).
• If you access an uninitialized variable, you get a ReferenceError.
10.8 Declarations: scope and activation 71
• Once you reach a variable declaration, the variable is set to either the value of the
initializer (specified via the assignment symbol) or undefined – if there is no ini-
tializer.
The next example shows that the temporal dead zone is truly temporal (related to time):
Even though func() is located before the declaration of myVar and uses that variable, we
can call func(). But we have to wait until the temporal dead zone of myVar is over.
A function declaration is always executed when entering its scope, regardless of where it
is located within that scope. That enables you to call a function foo() before it is declared:
assert.equal(foo(), 123); // OK
function foo() { return 123; }
The early activation of foo() means that the previous code is equivalent to:
If you declare a function via const or let, then it is not activated early: In the following
example, you can only use bar() after its declaration.
assert.throws(
() => bar(), // before declaration
ReferenceError);
Even if a function g() is not activated early, it can be called by a preceding function f()
(in the same scope) – if we adhere to the following rule: f() must be invoked after the
declaration of g().
const f = () => g();
const g = () => 123;
The functions of a module are usually invoked after its complete body was executed.
Therefore, in modules, you rarely need to worry about the order of functions.
Lastly, note how early activation automatically keeps the aforementioned rule: When
entering a scope, all function declarations are executed first, before any calls are made.
If you rely on early activation to call a function before its declaration, then you need to
be careful that it doesn’t access data that isn’t activated early.
funcDecl();
The problem goes away if you make the call to funcDecl() after the declaration of MY_-
STR.
We have seen that early activation has a pitfall and that you can get most of its benefits
without using it. Therefore, it is better to avoid early activation. But I don’t feel strongly
about this and, as mentioned before, often use function declarations, because I like their
syntax.
10.8 Declarations: scope and activation 73
class MyClass {}
The operand of extends is an expression. Therefore, you can do things like this:
const identity = x => x;
class MyClass extends identity(Object) {}
Evaluating such an expression must be done at the location where it is mentioned. Any-
thing else would be confusing. That explains why class declarations are not activated
early.
10.9 Closures
Before we can explore closures, we need to learn about bound variables and free vari-
ables.
• Bound variables are declared within the scope. They are parameters and local vari-
ables.
• Free variables are declared externally. They are also called non-local variables.
function func(x) {
const y = 123;
console.log(z);
}
A closure is a function plus a connection to the variables that exist at its “birth
place”.
What is the point of keeping this connection? It provides the values for the free variables
of the function. For example:
function funcFactory(value) {
return () => {
return value;
};
}
funcFactory returns a closure that is assigned to func. Because func has the connection
to the variables at its birth place, it can still access the free variable value when it is called
in line A (even though it “escaped” its scope).
function createInc(startValue) {
return (step) => { // (A)
startValue += step;
return startValue;
};
}
const inc = createInc(5);
assert.equal(inc(2), 7);
We can see that the function created in line A keeps its internal number in the free variable
startValue. This time, we don’t just read from the birth scope, we use it to store data
that we change and that persists across function calls.
We can create more storage slots in the birth scope, via local variables:
function createInc(startValue) {
let index = -1;
return (step) => {
startValue += step;
index++;
return [index, startValue];
};
}
const inc = createInc(5);
assert.deepEqual(inc(2), [0, 7]);
assert.deepEqual(inc(2), [1, 9]);
assert.deepEqual(inc(2), [2, 11]);
• For starters, they are simply an implementation of static scoping. As such, they
provide context data for callbacks.
• They can also be used by functions to store state that persists across function calls.
createInc() is an example of that.
• And they can provide private data for objects (produced via literals or classes). The
details of how that works are explained in “Exploring ES6”.
Quiz: advanced
See quiz app.
76 10 Variables and assignment
Values
Contents
11.1 What’s a type? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
11.2 JavaScript’s type hierarchy . . . . . . . . . . . . . . . . . . . . . . . 78
11.3 The types of the language specification . . . . . . . . . . . . . . . . 78
11.4 Primitive values vs. objects . . . . . . . . . . . . . . . . . . . . . . . 79
11.4.1 Primitive values (short: primitives) . . . . . . . . . . . . . . . 79
11.4.2 Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
11.5 The operators typeof and instanceof: what’s the type of a value? . 81
11.5.1 typeof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
11.5.2 instanceof . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
11.6 Classes and constructor functions . . . . . . . . . . . . . . . . . . . . 83
11.6.1 Constructor functions associated with primitive types . . . . . 83
11.7 Converting between types . . . . . . . . . . . . . . . . . . . . . . . . 84
11.7.1 Explicit conversion between types . . . . . . . . . . . . . . . . 84
11.7.2 Coercion (automatic conversion between types) . . . . . . . . 85
77
78 11 Values
(any)
null number
Array Function
string
Map RegExp
symbol
Set Date
Figure 11.1: A partial hierarchy of JavaScript’s types. Missing are the classes for errors,
the classes associated with primitive types, and more. The diagram hints at the fact that
not all objects are instances of Object.
Fig. 11.1 shows JavaScript’s type hierarchy. What do we learn from that diagram?
• JavaScript distinguishes two kinds of values: primitive values and objects. We’ll
see soon what the difference is.
• The diagram differentiates objects and instances of class Object. Each instance
of Object is also an object, but not vice versa. However, virtually all objects that
you’ll encounter in practice are instances of Object. For example, objects created
via object literals, are. More details on this topic are explained in §26.4.3.4 “Objects
that aren’t instances of Object”.
• Primitive values are the elements of the types undefined, null, boolean, number,
string, symbol.
• All other values are objects.
In contrast to Java (that inspired JavaScript here), primitive values are not second-class
citizens. The difference between them and objects is more subtle. In a nutshell, it is:
Other than that, primitive values and objects are quite similar: They both have properties
(key-value entries) and can be used in the same locations.
Primitives are passed by value: Variables (including parameters) store the contents of the
primitives. When assigning a primitive value to a variable or passing it as an argument
to a function, its content is copied.
let x = 123;
let y = x;
assert.equal(y, 123);
80 11 Values
Primitives are compared by value: When comparing two primitive values, we compare
their contents.
To see what’s so special about this way of comparing, read on and find out how objects
are compared.
11.4.2 Objects
Objects are covered in detail in §25 “Single objects” and the following chapter. Here, we
mainly focus on how they differ from primitive values.
• Object literal:
const obj = {
first: 'Jane',
last: 'Doe',
};
The object literal starts and ends with curly braces {}. It creates an object with
two properties. The first property has the key 'first' (a string) and the value
'Jane'. The second property has the key 'last' and the value 'Doe'. For more
information on object literals, consult §25.2.1 “Object literals: properties”.
• Array literal:
The Array literal starts and ends with square brackets []. It creates an Array with
two elements: 'foo' and 'bar'. For more information on Array literals, consult
$full.
By default, you can freely change, add and remove the properties of objects:
Objects are passed by identity (my term): Variables (including parameters) store the identi-
ties of objects.
11.5 The operators typeof and instanceof: what’s the type of a value? 81
The identity of an object is like a pointer (or a transparent reference) to the object’s actual
data on the heap (think shared main memory of a JavaScript engine).
Now the old value { prop: 'value' } of obj is garbage (not used anymore). JavaScript
will automatically garbage-collect it (remove it from memory), at some point in time (pos-
sibly never if there is enough free memory).
Objects are compared by identity (my term): Two variables are only equal if they contain
the same object identity. They are not equal if they refer to different objects with the same
content.
11.5.1 typeof
x typeof x
undefined 'undefined'
null 'object'
Boolean 'boolean'
Number 'number'
String 'string'
Symbol 'symbol'
Function 'function'
All other objects 'object'
Tbl. 11.1 lists all results of typeof. They roughly correspond to the 7 types of the language
specification. Alas, there are two differences and they are language quirks:
• typeof null returns 'object' and not 'null'. That’s a bug. Unfortunately, it
can’t be fixed. TC39 tried to do that, but it broke too much code on the web.
• typeof of a function should be 'object' (functions are objects). Introducing a
separate category for functions is confusing.
11.5.2 instanceof
This operator answers the question: has a value x been created by a class C?
x instanceof C
For example:
11.6 Classes and constructor functions 83
Exercise: instanceof
exercises/values/instanceof_exrc.mjs
ES6 introduced classes, which are mainly better syntax for constructor functions.
In this book, I’m using the terms constructor function and class interchangeably.
Classes can be seen as partitioning the single type object of the specification into sub-
types – they give us more types than the limited 7 ones of the specification. Each class is
the type of the objects that were created by it.
assert.equal(Number('123'), 123);
assert.equal((123).toString, Number.prototype.toString);
• Number is a namespace/container object for tool functions for numbers. For exam-
ple:
assert.equal(Number.isInteger(123), true);
• Lastly, you can also use Number as a class and create number objects. These objects
are different from real numbers and should be avoided.
The constructor functions related to primitive types are also called wrapper types, because
they provide the canonical way of converting primitive values to objects. In the process,
primitive values are “wrapped” in objects.
Wrapping rarely matters in practice, but it is used internally in the language specification,
to give primitives properties.
> Boolean(0)
false
> Number('123')
123
> String(123)
'123'
Many built-in functions coerce, too. For example, parseInt() coerces its parameter to
string (parsing stops at the first character that is not a digit):
> parseInt(123.45)
123
Quiz
See quiz app.
86 11 Values
Chapter 12
Operators
Contents
12.1 Making sense of operators . . . . . . . . . . . . . . . . . . . . . . . . 87
12.1.1 Operators coerce their operands to appropriate types . . . . . 88
12.1.2 Most operators only work with primitive values . . . . . . . . 88
12.2 The plus operator (+) . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
12.3 Assignment operators . . . . . . . . . . . . . . . . . . . . . . . . . . 89
12.3.1 The plain assignment operator . . . . . . . . . . . . . . . . . . 89
12.3.2 Compound assignment operators . . . . . . . . . . . . . . . . 89
12.3.3 A list of all compound assignment operators . . . . . . . . . . 89
12.4 Equality: == vs. === . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
12.4.1 Loose equality (== and !=) . . . . . . . . . . . . . . . . . . . . 90
12.4.2 Strict equality (=== and !==) . . . . . . . . . . . . . . . . . . . 91
12.4.3 Recommendation: always use strict equality . . . . . . . . . . 91
12.4.4 Even stricter than ===: Object.is() . . . . . . . . . . . . . . . 92
12.5 Ordering operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
12.6 Various other operators . . . . . . . . . . . . . . . . . . . . . . . . . 93
12.6.1 Comma operator . . . . . . . . . . . . . . . . . . . . . . . . . 93
12.6.2 void operator . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
87
88 12 Operators
First, the multiplication operator can only work with numbers. Therefore, it converts
strings to numbers before computing its result.
Second, the square brackets operator ([ ]) for accessing the properties of an object can
only handle strings and symbols. All other values are coerced to string:
Why? The plus operator first coerces its operands to primitive values:
> String([1,2,3])
'1,2,3'
> String([4,5,6])
'4,5,6'
• First, it converts both operands to primitive values. Then it switches to one of two
modes:
– String mode: If one of the two primitive values is a string, then it converts
the other one to a string, concatenates both strings and returns the result.
– Number mode: Otherwise, it converts both operands to numbers, adds them
and returns the result.
Number mode means that if neither operand is a string (or an object that becomes a string)
then everything is coerced to numbers:
> 4 + true
5
Number(true) is 1.
const x = value;
let y = value;
If, for example, op is + then we get the operator += that works as follows.
assert.equal(str, '<b>Hello!</b>');
+= -= *= /= %= **=
• Bitwise operators:
> '' == 0
true
Objects are coerced to primitives if (and only if!) the other operand is primitive:
If both operands are objects, they are only equal if they are the same object:
An object is only equal to another value if that value is the same object:
The === operator does not consider undefined and null to be equal:
Let’s look at two use cases for == and what I recommend to do instead.
== lets you check if a value x is a number or that number as a string – with a single
comparison:
if (x == 123) {
// x is either 123 or '123'
}
You can also convert x to a number when you first encounter it.
92 12 Operators
if (x == null) {
// x is either null or undefined
}
The problem with this code is that you can’t be sure if someone meant to write it that way
or if they made a typo and meant === null.
The second alternative is even more sloppy than using ==, but it is a well-established
pattern in JavaScript (to be explained in detail in §14.2 “Falsy and truthy values”).
if (x != null) ···
if (x !== undefined && x !== null) ···
if (x) ···
It is even stricter than ===. For example, it considers NaN, the error value for computations
involving numbers, to be equal to itself:
That is occasionally useful. For example, you can use it to implement an improved ver-
sion of the Array method .indexOf():
The result -1 means that .indexOf() couldn’t find its argument in the Array.
Operator name
< less than
<= Less than or equal
> Greater than
>= Greater than or equal
JavaScript’s ordering operators (tbl. 12.1) work for both numbers and strings:
> 5 >= 2
true
> 'bar' < 'foo'
true
The next two subsections discuss two operators that are rarely used.
> void (3 + 2)
undefined
Quiz
See quiz app.
Part IV
Primitive values
95
Chapter 13
Contents
13.1 undefined vs. null . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
13.2 Occurrences of undefined and null . . . . . . . . . . . . . . . . . . . 98
13.2.1 Occurrences of undefined . . . . . . . . . . . . . . . . . . . . 98
13.2.2 Occurrences of null . . . . . . . . . . . . . . . . . . . . . . . . 98
13.3 Checking for undefined or null . . . . . . . . . . . . . . . . . . . . . 99
13.4 undefined and null don’t have properties . . . . . . . . . . . . . . . 99
13.5 The history of undefined and null . . . . . . . . . . . . . . . . . . . 100
Many programming languages have one “non-value” called null. It indicates that a vari-
able does not currently point to an object. For example, when it hasn’t been initialized,
yet.
• undefined means “not initialized” (e.g. a variable) or “not existing” (e.g. a property
of an object).
• null means “the intentional absence of any object value” (a quote from the lan-
guage specification).
97
98 13 The non-values undefined and null
• null means “explicitly switched off”. That is, it helps implement a type that com-
prises both meaningful values and a meta-value that stands for “no meaningful
value”. Such a type is called option type or maybe type in functional programming.
let myVar;
assert.equal(myVar, undefined);
function func(x) {
return x;
}
assert.equal(func(), undefined);
If you don’t explicitly specify the result of a function via a return statement, JavaScript
returns undefined for you:
function func() {}
assert.equal(func(), undefined);
> Object.getPrototypeOf(Object.prototype)
null
If you match a regular expression (such as /a/) against a string (such as 'x'), you either
get an object with matching data (if matching was successful) or null (if matching failed):
> /a/.exec('x')
null
The JSON data format does not support undefined, only null:
Truthy means “is true if coerced to boolean”. Falsy means “is false if coerced to boolean”.
Both concepts are explained properly in §14.2 “Falsy and truthy values”.
undefined and null are the two only JavaScript values where you get an exception if
you try to read a property. To explore this phenomenon, let’s use the following function,
which reads (“gets”) property .foo and returns the result.
function getFoo(x) {
return x.foo;
}
If we apply getFoo() to various value, we can see that it only fails for undefined and
null:
> getFoo(undefined)
TypeError: Cannot read property 'foo' of undefined
> getFoo(null)
TypeError: Cannot read property 'foo' of null
> getFoo(true)
undefined
> getFoo({})
undefined
100 13 The non-values undefined and null
Quiz
See quiz app.
Chapter 14
Booleans
Contents
14.1 Converting to boolean . . . . . . . . . . . . . . . . . . . . . . . . . . 101
14.2 Falsy and truthy values . . . . . . . . . . . . . . . . . . . . . . . . . 102
14.2.1 Pitfall: truthiness checks are imprecise . . . . . . . . . . . . . 103
14.2.2 Checking for truthiness or falsiness . . . . . . . . . . . . . . . 103
14.2.3 Use case: was a parameter provided? . . . . . . . . . . . . . . 104
14.2.4 Use case: does a property exist? . . . . . . . . . . . . . . . . . 104
14.3 Conditional operator (? :) . . . . . . . . . . . . . . . . . . . . . . . . 104
14.4 Binary logical operators: And (x && y), Or (x || y) . . . . . . . . . . 105
14.4.1 Logical And (x && y) . . . . . . . . . . . . . . . . . . . . . . . 106
14.4.2 Logical Or (||) . . . . . . . . . . . . . . . . . . . . . . . . . . 106
14.4.3 Default values via logical Or (||) . . . . . . . . . . . . . . . . 107
14.5 Logical Not (!) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
The primitive type boolean comprises two values – false and true:
• Boolean(x)
Most descriptive; recommended.
• x ? true : false
Uses the conditional operator (explained later in this chapter).
101
102 14 Booleans
• !!x
Uses the logical Not operator (!). This operator coerces its operand to boolean. It
is applied a second time to get a non-negated result.
Tbl. 14.1 describes how various values are converted to boolean.
x Boolean(x)
undefined false
null false
boolean value x (no change)
number value 0 → false, NaN → false
other numbers → true
string value '' → false
other strings → true
object value always true
To simplify this check, we can use the fact that the if statement always converts its con-
ditional value to boolean:
Therefore, we can use the following code to check if obj.prop exists. That is less precise
than comparing with undefined, but also shorter:
if (obj.prop) {
// obj has property .prop
}
This simplified check is so popular that the following two names were introduced:
• undefined, null
14.2 Falsy and truthy values 103
• Booleans: false
• Numbers: 0, NaN
• Strings: ''
> Boolean('abc')
true
> Boolean([])
true
> Boolean({})
true
if (obj.prop) {
// obj has property .prop
}
• obj.prop is undefined.
• obj.prop is any other falsy value (null, 0, '', etc.).
In practice, this rarely causes problems, but you have to be aware of this pitfall.
if (!x) {
// x is falsy
}
if (x) {
// x is truthy
} else {
// x is falsy
}
The conditional operator that is used in the last line, is explained later in this chapter.
104 14 Booleans
On the plus side, this pattern is established and short. It correctly throws errors for un-
defined and null.
On the minus side, there is the previously mentioned pitfall: the code also throws errors
for all other falsy values.
An alternative is to check for undefined:
if (x === undefined) {
throw new Error('Missing parameter x');
}
This pattern is also established and has the usual caveat: it not only throws if the property
is missing, but also if it exists and has any of the falsy values.
If you truly want to check if the property exists, you have to use the in operator:
if (! ('path' in fileDesc)) {
throw new Error('Missing property: .path');
}
Exercise: Truthiness
exercises/booleans/truthiness_exrc.mjs
It is evaluated as follows:
The conditional operator is also called ternary operator, because it has three operands.
Examples:
The following code demonstrates that, whichever of the two branches “then” and “else”
is chosen via the condition – only that branch is evaluated. The other branch isn’t.
// Output:
// 'then'
> 12 || 'hello'
12
> 0 || 'hello'
'hello'
Short-circuiting means: If the first operand already determines the result, then the second
operand is not evaluated. The only other operator that delays evaluating its operands
is the conditional operator: Usually, all operands are evaluated before performing an
operation.
For example, logical And (&&) does not evaluate its second operand if the first one is falsy:
// Output:
// 'hello'
106 14 Booleans
• Evaluate a.
• Is the result falsy? Return it.
• Otherwise, evaluate b and return the result.
a && b
!a ? a : b
Examples:
• Evaluate a.
• Is the result truthy? Return it.
• Otherwise, evaluate b and return the result.
a || b
a ? a : b
Examples:
If there are one or more matches for regex inside str then .match() returns an Array. If
there are no matches, it unfortunately returns null (and not the empty Array). We fix
that via the || operator.
• Evaluate x.
• Is it truthy? Return false.
• Otherwise, return true.
Examples:
> !false
true
> !true
false
> !0
true
> !123
false
> !''
true
> !'abc'
false
108 14 Booleans
Quiz
See quiz app.
Chapter 15
Numbers
Contents
15.1 JavaScript only has floating point numbers . . . . . . . . . . . . . . 110
15.2 Number literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
15.2.1 Integer literals . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
15.2.2 Floating point literals . . . . . . . . . . . . . . . . . . . . . . . 111
15.2.3 Syntactic pitfall: properties of integer literals . . . . . . . . . . 111
15.3 Arithmetic operators . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
15.3.1 Binary arithmetic operators . . . . . . . . . . . . . . . . . . . 111
15.3.2 Unary plus (+) and negation (-) . . . . . . . . . . . . . . . . . 112
15.3.3 Incrementing (++) and decrementing (--) . . . . . . . . . . . . 112
15.4 Converting to number . . . . . . . . . . . . . . . . . . . . . . . . . . 113
15.5 Error values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
15.6 Error value: NaN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
15.6.1 Checking for NaN . . . . . . . . . . . . . . . . . . . . . . . . . 115
15.6.2 Finding NaN in Arrays . . . . . . . . . . . . . . . . . . . . . . . 115
15.7 Error value: Infinity . . . . . . . . . . . . . . . . . . . . . . . . . . 116
15.7.1 Infinity as a default value . . . . . . . . . . . . . . . . . . . 116
15.7.2 Checking for Infinity . . . . . . . . . . . . . . . . . . . . . . 116
15.8 The precision of numbers: careful with decimal fractions . . . . . . 117
15.9 (Advanced) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
15.10Background: floating point precision . . . . . . . . . . . . . . . . . . 117
15.10.1 A simplified representation of floating point numbers . . . . . 118
15.11Integers in JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . 119
15.11.1 Converting to integer . . . . . . . . . . . . . . . . . . . . . . . 119
15.11.2 Ranges of integers in JavaScript . . . . . . . . . . . . . . . . . 120
15.11.3 Safe integers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
15.12Bitwise operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
15.12.1 Internally, bitwise operators work with 32-bit integers . . . . . 121
15.12.2 Binary bitwise operators . . . . . . . . . . . . . . . . . . . . . 122
109
110 15 Numbers
However, there is only a single type for all numbers: They are all doubles, 64-bit floating
point numbers implemented according to the IEEE Standard for Floating-Point Arith-
metic (IEEE 754).
Integers are simply floating point numbers without a decimal fraction:
> 98 === 98.0
true
Note that, under the hood, most JavaScript engines are often able to use real integers,
with all associated performance and storage size benefits.
// Octal (base 8)
assert.equal(0o10, 8);
Note that % is a remainder operator (not a modulo operator) – its result has the sign of
112 15 Numbers
Table 15.2: The operators unary plus (+) and negation (-).
Prefix ++ and prefix -- change their operands and then return them.
let foo = 3;
assert.equal(++foo, 4);
assert.equal(foo, 4);
let bar = 3;
assert.equal(--bar, 2);
assert.equal(bar, 2);
Suffix ++ and suffix -- return their operands and then change them.
let foo = 3;
assert.equal(foo++, 3);
assert.equal(foo, 4);
let bar = 3;
assert.equal(bar--, 3);
assert.equal(bar, 2);
x Number(x)
undefined NaN
null 0
boolean false → 0, true → 1
number x (no change)
string '' → 0
other → parsed number, ignoring leading/trailing whitespace
object configurable (e.g. via .valueOf())
Examples:
assert.equal(Number(123.45), 123.45);
assert.equal(Number(''), 0);
assert.equal(Number('\n 123.45 \t'), 123.45);
assert.equal(Number('xyz'), NaN);
How objects are converted to numbers can be configured. For example, by overriding
.valueOf():
• NaN
• Infinity
> Number('$$$')
NaN
> Number(undefined)
NaN
> Math.log(-1)
NaN
> Math.sqrt(-1)
NaN
> NaN - 3
NaN
> 7 ** NaN
NaN
const n = NaN;
assert.equal(n === n, false);
const x = NaN;
> [NaN].indexOf(NaN)
-1
Others can:
> [NaN].includes(NaN)
true
> [NaN].findIndex(x => Number.isNaN(x))
0
> [NaN].find(x => Number.isNaN(x))
NaN
Alas, there is no simple rule of thumb, you have to check for each method, how it handles
NaN.
116 15 Numbers
> 5 / 0
Infinity
> -5 / 0
-Infinity
Infinity is larger than all other numbers (except NaN), making it a good default value:
function findMinimum(numbers) {
let min = Infinity;
for (const n of numbers) {
if (n < min) min = n;
}
return min;
}
const x = Infinity;
You therefore need to take rounding errors into consideration when performing arith-
metic in JavaScript.
Read on for an explanation of this phenomenon.
Quiz: basic
See quiz app.
15.9 (Advanced)
All remaining sections of this chapter are advanced.
To understand why, we need to explore how JavaScript represents floating point numbers
internally. It uses three integers to do so, which take up a total of 64 bits of storage (double
precision):
This representation can’t encode a zero, because its second component (involving the
fraction) always has a leading 1. Therefore, a zero is encoded via the special exponent
−1023 and a fraction 0.
• Instead of base 2 (binary), we use base 10 (decimal), because that’s what most peo-
ple are more familiar with.
• The fraction is a natural number that is interpreted as a fraction (digits after a point).
We switch to a mantissa, an integer that is interpreted as itself. As a consequence,
the exponent is used differently, but its fundamental role doesn’t change.
• As the mantissa is an integer (with its own sign), we don’t need a separate sign,
anymore.
mantissa × 10exponent
Let’s try out this representation for a few floating point numbers.
• For the number 1.5, we imagine there being a point after the mantissa. We use a
negative exponent to move that point one digit to the left:
• For the number 0.25, we move the point two digits to the left:
Representations with negative exponents can also be written as fractions with positive
exponents in the denominators:
These fractions help with understanding why there are numbers that our encoding can-
not represent:
• 1/10 can be represented. It already has the required format: a power of 10 in the
denominator.
• 1/2 can be represented as 5/10. We turned the 2 in the denominator into a power
of 10, by multiplying numerator and denominator with 5.
15.11 Integers in JavaScript 119
• 1/4 can be represented as 25/100. We turned the 4 in the denominator into a power
of 10, by multiplying numerator and denominator with 25.
• 1/3 cannot be represented. There is no way to turn the denominator into a power
of 10. (The prime factors of 10 are 2 and 5. Therefore, any denominator that only
has these prime factors can be converted to a power of 10, by multiplying both
numerator and denominator with enough twos and fives. If a denominator has a
different prime factor, then there’s nothing we can do.)
• 0.5 = 1/2 can be represented with base 2, because the denominator is already a
power of 2.
• 0.25 = 1/4 can be represented with base 2, because the denominator is already a
power of 2.
• 0.1 = 1/10 cannot be represented, because the denominator cannot be converted
to a power of 2.
• 0.2 = 2/10 cannot be represented, because the denominator cannot be converted
to a power of 2.
Now we can see why 0.1 + 0.2 doesn’t produce a correct result: Internally, neither of
the two operands can be represented precisely.
The only way to compute precisely with decimal fractions is by internally switching to
base 10. For many programming languages, base 2 is the default and base 10 an option.
For example, Java has the class BigDecimal and Python has the module decimal. There
are tentative plans to add something similar to JavaScript: The ECMAScript proposal
“Decimal” is currently at stage 0.
In this section, we’ll look at a few tools for working with these pseudo-integers.
> Math.floor(2.1)
2
> Math.floor(2.9)
2
120 15 Numbers
> Math.ceil(2.1)
3
> Math.ceil(2.9)
3
• Math.round(n): returns the integer that is “closest” to n. 0.5 is rounded up. For
example:
> Math.round(2.4)
2
> Math.round(2.5)
3
• Math.trunc(n): removes any decimal fraction (after the point) that n has, therefore
turning it into an integer.
> Math.trunc(2.1)
2
> Math.trunc(2.9)
2
• Safe integers: can be represented “safely” by JavaScript (more on what that means
in the next subsection)
– Precision: 53 bits plus sign
– Range: (−253 , 253 )
• Array indices
– Precision: 32 bits, unsigned
– Range: [0, 232 −1) (excluding the maximum length)
– Typed Arrays have a larger range of 53 bits (safe and unsigned)
• Bitwise operators (bitwise Or etc.)
– Precision: 32 bits
– Range of unsigned right shift (>>>): unsigned, [0, 232 )
– Range of all other bitwise operators: signed, [−231 , 231 )
> 18014398509481984
18014398509481984
> 18014398509481985
18014398509481984
> 18014398509481986
18014398509481984
> 18014398509481987
18014398509481988
assert.equal(Number.isSafeInteger(5), true);
assert.equal(Number.isSafeInteger('5'), false);
assert.equal(Number.isSafeInteger(5.1), false);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER), true);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER+1), false);
The following result is incorrect and unsafe, even though both of its operands are safe.
> 9007199254740990 + 3
9007199254740992
The following result is safe, but incorrect. The first operand is unsafe, the second operand
is safe.
> 9007199254740995 - 10
9007199254740986
• Input (JavaScript numbers): The 1–2 operands are first converted to JavaScript
numbers (64-bit floating point numbers) and then to 32-bit integers.
• Computation (32-bit integers): The actual operation processes 32-bit integers and
produces a 32-bit integer.
• Output (JavaScript number): Before returning the result, it is converted back to a
JavaScript number.
For each bitwise operator, this book mentions the types of its operands and its result.
Each type is always one of the following two:
Considering the previously mentioned steps, I recommend to pretend that bitwise oper-
ators internally work with unsigned 32-bit integers (step “computation”). And that Int32
and Uint32 only affect how JavaScript numbers are converted to and from integers (steps
“input” and “output”).
While exploring the bitwise operators, it occasionally helps to display JavaScript numbers
as unsigned 32-bit integers in binary notation. That’s what b32() does (whose implemen-
tation is shown later):
assert.equal(
b32(-1),
'11111111111111111111111111111111');
assert.equal(
b32(1),
'00000000000000000000000000000001');
assert.equal(
b32(2 ** 31),
'10000000000000000000000000000000');
The binary bitwise operators (tbl. 15.7) combine the bits of their operands to produce
their results:
> (0b1010 & 0b0011).toString(2).padStart(4, '0')
'0010'
> (0b1010 | 0b0011).toString(2).padStart(4, '0')
'1011'
> (0b1010 ^ 0b0011).toString(2).padStart(4, '0')
'1001'
The bitwise Not operator (tbl. 15.8) inverts each binary digit of its operand:
> b32(~0b100)
'11111111111111111111111111111011'
The shift operators (tbl. 15.9) move binary digits to the left or to the right:
/**
* Return a string representing n as a 32-bit unsigned integer,
* in binary notation.
*/
function b32(n) {
// >>> ensures highest bit isn’t interpreted as a sign
return (n >>> 0).toString(2).padStart(32, '0');
}
assert.equal(
b32(6),
'00000000000000000000000000000110');
n >>> 0 means that we are shifting n zero bits to the right. Therefore, in principle, the
>>> operator does nothing, but it still coerces n to an unsigned 32-bit integer:
> 12 >>> 0
12
> -12 >>> 0
4294967284
> (2**32 + 1) >>> 0
1
> Number.isNaN(NaN)
true
> Number.isNaN(123)
false
> Number.isNaN('abc')
false
Coerces its parameter to string and parses it as a floating point number. For con-
verting strings to numbers, Number() (which ignores leading and trailing whites-
pace) is usually a better choice than Number.parseFloat() (which ignores leading
whitespace and illegal trailing characters and can hide problems).
Coerces its parameter to string and parses it as an integer, ignoring leading whites-
pace and illegal trailing characters:
> Number.parseInt('101', 2)
5
> Number.parseInt('FF', 16)
255
Do not use this method to convert numbers to integers: Coercing to string is ineffi-
cient. And stopping before the first non-digit is not a good algorithm for removing
the fraction of a number. Here is an example where it goes wrong:
Returns a string that represents the number via exponential notation. With frac-
tionDigits, you can specify, how many digits should be shown of the number that
is multiplied with the exponent (the default is to show as many digits as necessary).
> 1234..toString()
'1234'
Example: fraction not small enough to get a negative exponent via .toString().
> 0.003.toString()
'0.003'
> 0.003.toExponential()
'3e-3'
Works like .toString(), but precision specifies how many digits should be
shown. If precision is missing, .toString() is used.
> 1234..toPrecision(4)
'1234'
128 15 Numbers
> 1234..toPrecision(5)
'1234.0'
> 1.234.toPrecision(3)
'1.23'
If you want the numeral to have a different base, you can specify it via radix:
> 4..toString(2) // binary (base 2)
'100'
> 4.5.toString(2)
'100.1'
> 1234567890..toString(36)
'kf12oi'
15.13.5 Sources
• Wikipedia
• TypeScript’s built-in typings
• MDN web docs for JavaScript
• ECMAScript language specification
Quiz: advanced
See quiz app.
Chapter 16
Math
Contents
16.1 Data properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
16.2 Exponents, roots, logarithms . . . . . . . . . . . . . . . . . . . . . . 130
16.3 Rounding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
16.4 Trigonometric Functions . . . . . . . . . . . . . . . . . . . . . . . . . 132
16.5 Various other functions . . . . . . . . . . . . . . . . . . . . . . . . . 134
16.6 Sources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Math is an object with data properties and methods for processing numbers. You can see
it as a poor man’s module: It was created long before JavaScript had modules.
129
130 16 Math
> Math.cbrt(8)
2
> Math.exp(0)
1
> Math.exp(1) === Math.E
true
Returns the natural logarithm of x (to base e, Euler’s number). The inverse of
Math.exp().
> Math.log(1)
0
> Math.log(Math.E)
1
> Math.log(Math.E ** 2)
2
> Math.log10(1)
0
> Math.log10(10)
1
> Math.log10(100)
2
> Math.log2(1)
0
> Math.log2(2)
1
> Math.log2(4)
2
> Math.pow(2, 3)
8
> Math.pow(25, 0.5)
5
> Math.sqrt(9)
3
16.3 Rounding
Rounding means converting an arbitrary number to an integer (a number without a dec-
imal fraction). The following functions implement different approaches to rounding.
> Math.ceil(2.1)
3
> Math.ceil(2.9)
3
> Math.floor(2.1)
2
> Math.floor(2.9)
2
> Math.round(2.4)
2
> Math.round(2.5)
3
Tbl. 16.1 shows the results of the rounding functions for a few representative inputs.
Table 16.1: Rounding functions of Math. Note how things change with
negative numbers, because “larger” always means “closer to positive in-
finity”.
Math.floor -3 -3 -3 2 2 2
Math.ceil -2 -2 -2 3 3 3
Math.round -3 -2 -2 2 3 3
Math.trunc -2 -2 -2 2 2 2
function degreesToRadians(degrees) {
return degrees / 180 * Math.PI;
}
assert.equal(degreesToRadians(90), Math.PI/2);
function radiansToDegrees(radians) {
return radians / Math.PI * 180;
}
assert.equal(radiansToDegrees(Math.PI), 180);
16.4 Trigonometric Functions 133
> Math.acos(0)
1.5707963267948966
> Math.acos(1)
0
> Math.asin(0)
0
> Math.asin(1)
1.5707963267948966
> Math.cos(0)
1
> Math.cos(Math.PI)
-1
Returns the square root of the sum of the squares of values (Pythagoras’ theorem):
> Math.hypot(3, 4)
5
134 16 Math
> Math.sin(0)
0
> Math.sin(Math.PI / 2)
1
> Math.tan(0)
0
> Math.tan(1)
1.5574077246549023
> Math.abs(3)
3
> Math.abs(-3)
3
> Math.abs(0)
0
Counts the leading zero bits in the 32-bit integer x. Used in DSP algorithms.
> Math.clz32(0b01000000000000000000000000000000)
1
> Math.clz32(0b00100000000000000000000000000000)
2
> Math.clz32(2)
30
> Math.clz32(1)
31
16.6 Sources
• Wikipedia
• TypeScript’s built-in typings
• MDN web docs for JavaScript
• ECMAScript language specification
136 16 Math
Chapter 17
Contents
17.1 Code points vs. code units . . . . . . . . . . . . . . . . . . . . . . . . 137
17.1.1 Code points . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
17.1.2 Encoding Unicode code points: UTF-32, UTF-16, UTF-8 . . . . 138
17.2 Encodings used in web development: UTF-16 and UTF-8 . . . . . . 140
17.2.1 Source code internally: UTF-16 . . . . . . . . . . . . . . . . . . 140
17.2.2 Strings: UTF-16 . . . . . . . . . . . . . . . . . . . . . . . . . . 140
17.2.3 Source code in files: UTF-8 . . . . . . . . . . . . . . . . . . . . 140
17.3 Grapheme clusters – the real characters . . . . . . . . . . . . . . . . 140
Unicode is a standard for representing and managing text in most of the world’s writing
systems. Virtually all modern software that works with text, supports Unicode. The
standard is maintained by the Unicode Consortium. A new version of the standard is
published every year (with new Emojis etc.). Unicode version 1.0.0 was published in
October 1991.
137
138 17 Unicode – a brief introduction (advanced)
> 'A'.codePointAt(0).toString(16)
'41'
> 'ü'.codePointAt(0).toString(16)
'fc'
> 'π'.codePointAt(0).toString(16)
'3c0'
> '☺'.codePointAt(0).toString(16)
'1f642'
The hexadecimal numbers of the code points tell us that the first three characters reside
in plane 0 (within 16 bits), while the emoji resides in plane 1.
UTF-32 uses 32 bits to store code units, resulting in one code unit per code point. This
format is the only one with fixed-length encoding; all others use a varying number of code
units to encode a single code point.
17.1 Code points vs. code units 139
UTF-8 has 8-bit code units. It uses 1–4 code units to encode a code point:
Notes:
• The bit prefix of each code unit tells us:
– Is it first in a series of code units? If yes, how many code units will follow?
– Is it second or later in a series of code units?
• The character mappings in the 0000–007F range are the same as ASCII, which leads
to a degree of backward-compatibility with older software.
Three examples:
140 17 Unicode – a brief introduction (advanced)
For more information on Unicode and strings, consult §18.6 “Atoms of text: Unicode
characters, JavaScript characters, grapheme clusters”.
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
···
For HTML modules loaded in web browsers, the standard encoding is also UTF-8.
On the other hand, there are grapheme clusters. A grapheme cluster corresponds most
closely to a symbol displayed on screen or paper. It is defined as “a horizontally seg-
mentable unit of text”. Therefore, official Unicode documents also call it a user-perceived
character. One or more code point characters are needed to encode a grapheme cluster.
For example, the Devanagari kshi is encoded by 4 code points. We use spreading (...) to
split a string into an Array with code point characters (for details, consult §18.6.1 “Work-
ing with code points”):
Flag emojis are also grapheme clusters and composed of two code point characters. For
example, the flag of Japan:
Quiz
See quiz app.
142 17 Unicode – a brief introduction (advanced)
Chapter 18
Strings
Contents
18.1 Plain string literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
18.1.1 Escaping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
18.2 Accessing characters and code points . . . . . . . . . . . . . . . . . . 144
18.2.1 Accessing JavaScript characters . . . . . . . . . . . . . . . . . 144
18.2.2 Accessing Unicode code point characters via for-of and
spreading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
18.3 String concatenation via + . . . . . . . . . . . . . . . . . . . . . . . . 145
18.4 Converting to string . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
18.4.1 Stringifying objects . . . . . . . . . . . . . . . . . . . . . . . . 146
18.4.2 Customizing the stringification of objects . . . . . . . . . . . . 147
18.4.3 An alternate way of stringifying values . . . . . . . . . . . . . 147
18.5 Comparing strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
18.6 Atoms of text: Unicode characters, JavaScript characters, grapheme
clusters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
18.6.1 Working with code points . . . . . . . . . . . . . . . . . . . . 148
18.6.2 Working with code units (char codes) . . . . . . . . . . . . . . 149
18.6.3 Caveat: grapheme clusters . . . . . . . . . . . . . . . . . . . . 150
18.7 Quick reference: Strings . . . . . . . . . . . . . . . . . . . . . . . . . 150
18.7.1 Converting to string . . . . . . . . . . . . . . . . . . . . . . . 150
18.7.2 Numeric values of characters . . . . . . . . . . . . . . . . . . . 150
18.7.3 String operators . . . . . . . . . . . . . . . . . . . . . . . . . . 150
18.7.4 String.prototype: finding and matching . . . . . . . . . . . . 151
18.7.5 String.prototype: extracting . . . . . . . . . . . . . . . . . . 153
18.7.6 String.prototype: combining . . . . . . . . . . . . . . . . . . 154
18.7.7 String.prototype: transforming . . . . . . . . . . . . . . . . 154
18.7.8 Sources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Strings are primitive values in JavaScript and immutable. That is, string-related opera-
tions always produce new strings and never change existing strings.
143
144 18 Strings
Single quotes are used more often, because it makes it easier to mention HTML, where
double quotes are preferred.
The next chapter covers template literals, which give you:
• String interpolation
• Multiple lines
• Raw string literals (backslash has no special meaning)
18.1.1 Escaping
The backslash lets you create special characters:
• Unix line break: '\n'
• Windows line break: '\r\n'
• Tab: '\t'
• Backslash: '\\'
The backslash also lets you use the delimiter of a string literal inside that literal:
assert.equal(
'She said: "Let\'s go!"',
"She said: \"Let's go!\"");
18.2.2 Accessing Unicode code point characters via for-of and spread-
ing
Iterating over strings via for-of or spreading (...) visits Unicode code point characters.
Each code point character is encoded by 1–2 JavaScript characters. For more information,
18.3 String concatenation via + 145
see §18.6 “Atoms of text: Unicode characters, JavaScript characters, grapheme clusters”.
This is how you iterate over the code point characters of a string via for-of:
And this is how you convert a string into an Array of code point characters via spreading:
The assignment operator += is useful if you want to assemble a string, piece by piece:
• String(x)
• ''+x
• x.toString() (does not work for undefined and null)
146 18 Strings
Examples:
assert.equal(String(undefined), 'undefined');
assert.equal(String(null), 'null');
assert.equal(String(false), 'false');
assert.equal(String(true), 'true');
assert.equal(String(123.45), '123.45');
Pitfall for booleans: If you convert a boolean to a string via String(), you generally can’t
convert it back via Boolean():
> String(false)
'false'
> Boolean('false')
true
The only string for which Boolean() returns false, is the empty string.
Arrays have a better string representation, but it still hides much information:
> String([true])
'true'
> String(['true'])
'true'
> String(true)
'true'
const obj = {
toString() {
return 'hello';
}
};
assert.equal(String(obj), 'hello');
The caveat is that JSON only supports null, booleans, numbers, strings, Arrays and ob-
jects (which it always treats as if they were created by object literals).
Tip: The third parameter lets you switch on multi-line output and specify how much to
indent. For example:
{
"first": "Jane",
"last": "Doe"
}
There is one important caveat to consider: These operators compare based on the numeric
values of JavaScript characters. That means that the order that JavaScript uses for strings
is different from the one used in dictionaries and phone books:
Properly comparing text is beyond the scope of this book. It is supported via the ECMA-
Script Internationalization API (Intl).
> String.fromCodePoint(0x1F642)
'☺'
> '☺'.codePointAt(0).toString(16)
'1f642'
You can iterate over a string, which visits Unicode characters (not JavaScript characters).
Iteration is described later in this book. One way of iterating is via a for-of loop:
// Output:
// '☺'
// 'a'
Spreading (...) into Array literals is also based on iteration and visits Unicode characters:
> [...'☺a']
[ '☺', 'a' ]
> [...'☺a'].length
2
> '☺a'.length
3
To specify a code unit hexadecimally, you can use a code unit escape:
> '\uD83D\uDE42'
'☺'
And you can use String.fromCharCode(). Char code is the standard library’s name for
code unit:
> '☺'.charCodeAt(0).toString(16)
'd83d'
150 18 Strings
x String(x)
undefined 'undefined'
null 'null'
Boolean value false → 'false', true → 'true'
Number value Example: 123 → '123'
String value x (input, unchanged)
An object Configurable via, e.g., toString()
Returns true if the string would end with searchString if its length were endPos.
Returns false, otherwise.
> 'foo.txt'.endsWith('.txt')
true
> 'abcde'.endsWith('cd', 4)
true
Returns true if the string contains the searchString and false, otherwise. The
search starts at startPos.
> 'abc'.includes('b')
true
> 'abc'.includes('b', 2)
false
Returns the lowest index at which searchString appears within the string, or -1,
otherwise. Any returned index will be minIndex or higher.
> 'abab'.indexOf('a')
0
> 'abab'.indexOf('a', 1)
2
> 'abab'.indexOf('c')
-1
Returns the highest index at which searchString appears within the string, or -1,
otherwise. Any returned index will be maxIndex or lower.
> 'abab'.lastIndexOf('ab', 2)
2
> 'abab'.lastIndexOf('ab', 1)
0
> 'abab'.lastIndexOf('ab')
2
If regExp is a regular expression with flag /g not set, then .match() returns the
first match for regExp within the string. Or null if there is no match. If regExp is a
string, it is used to create a regular expression (think parameter of new RegExp())
before performing the previously mentioned steps.
Numbered capture groups become Array indices (which is why this type extends
Array). Named capture groups (ES2018) become properties of .groups. In this
mode, .match() works like RegExp.prototype.exec().
Examples:
> 'ababb'.match(/a(b+)/)
{ 0: 'ab', 1: 'b', index: 0, input: 'ababb', groups: undefined }
> 'ababb'.match(/a(?<foo>b+)/)
{ 0: 'ab', 1: 'b', index: 0, input: 'ababb', groups: { foo: 'b' } }
> 'abab'.match(/x/)
null
If flag /g of regExp is set, .match() returns either an Array with all matches or null
if there was no match.
> 'ababb'.match(/a(b+)/g)
[ 'ab', 'abb' ]
> 'ababb'.match(/a(?<foo>b+)/g)
[ 'ab', 'abb' ]
> 'abab'.match(/x/g)
null
Returns the index at which regExp occurs within the string. If regExp is a string, it
is used to create a regular expression (think parameter of new RegExp()).
> 'a2b'.search(/[0-9]/)
1
> 'a2b'.search('[0-9]')
1
> '.gitignore'.startsWith('.')
true
> 'abcde'.startsWith('bc', 1)
true
Returns the substring of the string that starts at (including) index start and ends
at (excluding) index end. If an index is negative, it is added to .length before they
are used (-1 means this.length-1, etc.).
> 'abc'.slice(1, 3)
'bc'
> 'abc'.slice(1)
'bc'
> 'abc'.slice(-2)
'bc'
Splits the string into an Array of substrings – the strings that occur between the
separators. The separator can be a string:
The last invocation demonstrates that captures made by groups in the regular ex-
pression become elements of the returned Array.
> '☺X☺'.split('')
[ '\uD83D', '\uDE42', 'X', '\uD83D', '\uDE42' ]
> [...'☺X☺']
[ '☺', 'X', '☺' ]
> '#'.padStart(2)
' #'
> 'abc'.padStart(2)
'abc'
> '#'.padStart(5, 'abc')
'abca#'
If the second parameter is a function, occurrences are replaced with the strings it
returns. Its parameters args are:
– matched: string. The complete match
– g1: string|undefined. The capture of numbered group 1
– g2: string|undefined. The capture of numbered group 2
– (Etc.)
– offset: number. Where was the match found in the input string?
– input: string. The whole input string
Named capture groups (ES2018) are supported, too. If there are any, an argument
is added at the end, with an object whose properties contain the captures:
Returns a copy of the string, in which all lowercase alphabetic characters are con-
verted to uppercase. How well that works for various alphabets, depends on the
JavaScript engine.
> '-a2b-'.toUpperCase()
'-A2B-'
> 'αβγ'.toUpperCase()
'ΑΒΓ'
Returns a copy of the string, in which all uppercase alphabetic characters are con-
verted to lowercase. How well that works for various alphabets, depends on the
JavaScript engine.
> '-A2B-'.toLowerCase()
'-a2b-'
> 'ΑΒΓ'.toLowerCase()
'αβγ'
Returns a copy of the string, in which all leading and trailing whitespace (spaces,
tabs, line terminators, etc.) is gone.
18.7.8 Sources
• TypeScript’s built-in typings
• MDN web docs for JavaScript
• ECMAScript language specification
Quiz
See quiz app.
158 18 Strings
Chapter 19
Contents
19.1 Disambiguation: “template” . . . . . . . . . . . . . . . . . . . . . . 159
19.2 Template literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
19.3 Tagged templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
19.3.1 Cooked vs. raw template strings (advanced) . . . . . . . . . . 161
19.3.2 Tag function library: lit-html . . . . . . . . . . . . . . . . . . . 162
19.3.3 Tag function library: re-template-tag . . . . . . . . . . . . . . 162
19.3.4 Tag function library: graphql-tag . . . . . . . . . . . . . . . . 163
19.4 Raw string literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
19.5 (Advanced) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
19.6 Multi-line template literals and indentation . . . . . . . . . . . . . . 164
19.6.1 Fix: template tag for dedenting . . . . . . . . . . . . . . . . . 164
19.6.2 Fix: .trim() . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
19.7 Simple templating via template literals . . . . . . . . . . . . . . . . 165
19.7.1 A more complex example . . . . . . . . . . . . . . . . . . . . . 165
19.7.2 Simple HTML-escaping . . . . . . . . . . . . . . . . . . . . . 167
Before we dig into the two features template literal and tagged template, let’s first examine
the multiple meanings of the term template.
159
160 19 Using template literals and tagged templates
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{body}}
</div>
</div>
This template has two blanks to be filled in: title and body. It is used like this:
// First step: retrieve the template text, e.g. from a text file.
const tmplFunc = Handlebars.compile(TEMPLATE_TEXT); // compile string
const data = {title: 'My page', body: 'Welcome to my page!'};
const html = tmplFunc(data);
• A template literal is similar to a string literal, but has additional features. For exam-
ple, interpolation. It is delimited by backticks:
const num = 5;
assert.equal(`Count: ${num}!`, 'Count: 5!');
• Syntactically, a tagged template is a template literal that follows a function (or rather,
an expression that evaluates to a function). That leads to the function being called.
Its arguments are derived from the contents of the template literal.
Note that getArgs() receives both the text of the literal and the data interpolated
via ${}.
First, it supports string interpolation: If you put a dynamically computed value inside a
${}, it is converted to a string and inserted into the string returned by the literal.
assert.deepEqual(
tagFunc`Setting ${setting} is ${value}!`, // (A)
[['Setting ', ' is ', '!'], 'dark mode', true] // (B)
);
The function tagFunc before the first backtick is called a tag function. Its arguments are:
• Template strings (first argument): an Array with the text fragments surrounding the
interpolations ${}.
– In the example: ['Setting ', ' is ', '!']
• Substitutions (remaining arguments): the interpolated values.
– In the example: 'dark mode' and true
The static (fixed) parts of the literal (the template strings) are kept separate from the dy-
namic parts (the substitutions).
A tag function can return arbitrary values.
The raw interpretation enables raw string literals via String.raw (described later) and
similar applications.
Tagged templates are great for supporting small embedded languages (so-called domain-
specific languages). We’ll continue with a few examples.
repeat() is a custom function for looping. Its 2nd parameter produces unique keys for
the values returned by the 3rd parameter. Note the nested tagged template used by that
parameter.
Additionally, there are plugins for pre-compiling such queries in Babel, TypeScript, etc.
assert.equal(String.raw`\back`, '\\back');
This helps whenever data contains backslashes. For example, strings with regular expres-
sions:
All three regular expressions are equivalent. With a normal string literal, you have to
write the backslash twice, to escape it for that literal. With a raw string literal, you don’t
have to do that.
Raw string literals are also useful for specifying Windows filename paths:
19.5 (Advanced)
All remaining sections are advanced
164 19 Using template literals and tagged templates
For example:
function div(text) {
return `
<div>
${text}
</div>
`;
}
console.log('Output:');
console.log(
div('Hello!')
// Replace spaces with mid-dots:
.replace(/ /g, '·')
// Replace \n with #\n:
.replace(/\n/g, '#\n')
);
Due to the indentation, the template literal fits well into the source code. Alas, the output
is also indented. And we don’t want the return at the beginning and the return plus two
spaces at the end.
Output:
#
····<div>#
······Hello!#
····</div>#
··
There are two ways to fix this: via a tagged template or by trimming the result of the
template literal.
`.replace(/\n/g, '#\n');
}
console.log('Output:');
console.log(divDedented('Hello!'));
Output:
<div>#
Hello!#
</div>
function divDedented(text) {
return `
<div>
${text}
</div>
`.trim().replace(/\n/g, '#\n');
}
console.log('Output:');
console.log(divDedented('Hello!'));
The string method .trim() removes the superfluous whitespace at the beginning and at
the end, but the content itself must start in the leftmost column. The advantage of this
solution is that you don’t need a custom tag function. The downside is that it looks ugly.
Output:
<div>#
Hello!#
</div>
const addresses = [
{ first: '<Jane>', last: 'Bond' },
{ first: 'Lars', last: '<Croft>' },
];
The function tmpl() that produces the HTML table looks as follows.
• The first one (line 1) takes addrs, an Array with addresses, and returns a string
with a table.
• The second one (line 4) takes addr, an object containing an address, and returns a
string with a table row. Note the .trim() at the end, which removes unnecessary
whitespace.
The first templating function produces its result by wrapping a table element around an
Array that it joins into a string (line 10). That Array is produced by mapping the second
templating function to each element of addrs (line 3). It therefore contains strings with
table rows.
The helper function escapeHtml() is used to escape special HTML characters (line 6 and
line 7). Its implementation is shown in the next subsection.
Let us call tmpl() with the addresses and log the result:
console.log(tmpl(addresses));
<table>
<tr>
<td><Jane></td>
<td>Bond</td>
</tr><tr>
<td>Lars</td>
<td><Croft></td>
</tr>
</table>
19.7 Simple templating via template literals 167
Quiz
See quiz app.
168 19 Using template literals and tagged templates
Chapter 20
Symbols
Contents
20.1 Use cases for symbols . . . . . . . . . . . . . . . . . . . . . . . . . . 170
20.1.1 Symbols: values for constants . . . . . . . . . . . . . . . . . . 170
20.1.2 Symbols: unique property keys . . . . . . . . . . . . . . . . . 171
20.2 Publicly known symbols . . . . . . . . . . . . . . . . . . . . . . . . 172
20.3 Converting symbols . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
Symbols are primitive values that are created via the factory function Symbol():
The parameter is optional and provides a description, which is mainly useful for debug-
ging.
On one hand, symbols are like objects in that each value created by Symbol() is unique
and not compared by value:
On the other hand, they also behave like primitive values. They have to be categorized
via typeof:
const obj = {
[sym]: 123,
};
169
170 20 Symbols
On the plus side, logging that constant produces helpful output. On the minus side, there
is a risk of mistaking an unrelated value for a color, because two strings with the same
content are considered equal:
assert.notEqual(COLOR_BLUE, MOOD_BLUE);
function getComplement(color) {
switch (color) {
case COLOR_RED:
return COLOR_GREEN;
case COLOR_ORANGE:
return COLOR_BLUE;
case COLOR_YELLOW:
return COLOR_VIOLET;
case COLOR_GREEN:
return COLOR_RED;
case COLOR_BLUE:
return COLOR_ORANGE;
case COLOR_VIOLET:
return COLOR_YELLOW;
default:
20.1 Use cases for symbols 171
• The program operates at a base level. The keys at that level reflect the problem that
the program solves.
• Libraries and ECMAScript operate at a meta-level. The keys at that level are used
by services operating on base-level data and code. One such key is 'toString'.
const pt = {
x: 7,
y: 4,
toString() {
return `(${this.x}, ${this.y})`;
},
};
assert.equal(String(pt), '(7, 4)');
Properties .x and .y exist at the base level. They hold the coordinates of the point
represented by pt and are used to solve a problem – computing with points. Method
.toString() exists at a meta-level. It is used by JavaScript to convert this object to a
string.
Meta-level properties must never interfere with base level properties. That is, their keys
must never overlap. That is difficult when both language and libraries contribute to
the meta-level. For example, it is now impossible to give new meta-level methods sim-
ple names, such as toString, because they might clash with existing base level names.
Python’s solution to this problem is to prefix and suffix special names with two under-
scores: __init__, __iter__, __hash__, etc. However, even with this solution, libraries
can’t have their own meta-level properties, because those might be in conflict with future
language properties.
Symbols, used as property keys, help us here: Each symbol is unique and a symbol key
never clashes with any other string or symbol key.
As an example, let’s assume we are writing a library that treats objects differently if they
implement a special method. This is what defining a property key for such a method and
implementing it for an object would look like:
[specialMethod]() { // (A)
return this._id;
}
};
assert.equal(obj[specialMethod](), 'kf12oi');
The square brackets in line A enable us to specify that the method must have the key
specialMethod. More details are explained in §25.5.2 “Computed property keys”.
One key pitfall with symbols is how often exceptions are thrown when converting them
to something else. What is the thinking behind that? First, conversion to number never
makes sense and should be warned about. Second, converting a symbol to a string is
indeed useful for diagnostic output. But it also makes sense to warn about accidentally
turning a symbol into a string (which is a different kind of property key):
const obj = {};
const sym = Symbol();
assert.throws(
() => { obj['__'+sym+'__'] = true },
{ message: 'Cannot convert a Symbol value to a string' });
The downside is that the exceptions make working with symbols more complicated. You
have to explicitly convert symbols when assembling strings via the plus operator:
> const mySymbol = Symbol('mySymbol');
> 'Symbol I used: ' + mySymbol
TypeError: Cannot convert a Symbol value to a string
> 'Symbol I used: ' + String(mySymbol)
'Symbol I used: Symbol(mySymbol)'
Quiz
See quiz app.
174 20 Symbols
Part V
175
Chapter 21
Contents
21.1 Controlling loops: break and continue . . . . . . . . . . . . . . . . . 178
21.1.1 break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
21.1.2 break plus label: leaving any labeled statement . . . . . . . . 178
21.1.3 continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
21.2 if statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
21.2.1 The syntax of if statements . . . . . . . . . . . . . . . . . . . 180
21.3 switch statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
21.3.1 A first example of a switch statement . . . . . . . . . . . . . . 181
21.3.2 Don’t forget to return or break! . . . . . . . . . . . . . . . . . 181
21.3.3 Empty cases clauses . . . . . . . . . . . . . . . . . . . . . . . . 182
21.3.4 Checking for illegal values via a default clause . . . . . . . . 182
21.4 while loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
21.4.1 Examples of while loops . . . . . . . . . . . . . . . . . . . . . 183
21.5 do-while loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
21.6 for loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
21.6.1 Examples of for loops . . . . . . . . . . . . . . . . . . . . . . 184
21.7 for-of loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
21.7.1 const: for-of vs. for . . . . . . . . . . . . . . . . . . . . . . . 186
21.7.2 Iterating over iterables . . . . . . . . . . . . . . . . . . . . . . 186
21.7.3 Iterating over [index, element] pairs of Arrays . . . . . . . . . 186
21.8 for-await-of loops . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
21.9 for-in loops (avoid) . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
177
178 21 Control flow statements
Before we get to the actual control flow statements, let’s take a look at two operators for
controlling loops.
21.1.1 break
There are two versions of break: one with an operand and one without an operand. The
latter version works inside the following statements: while, do-while, for, for-of, for-
await-of, for-in and switch. It immediately leaves the current statement:
// Output:
// 'a'
// '---'
// 'b'
foo: { // label
if (condition) break foo; // labeled break
// ···
}
In the following example, we use break with a label to leave a loop differently when we
succeeded (line A). Then we skip what comes directly after the loop, which is where we
end up if we failed.
// Success:
result = str;
break search_block; // (A)
}
} // for
// Failure:
result = '(Untitled)';
} // search_block
21.1.3 continue
continue only works inside while, do-while, for, for-of, for-await-of and for-in.
It immediately leaves the current loop iteration and continues with the next one. For
example:
const lines = [
'Normal line',
'# Comment',
'Another normal line',
];
for (const line of lines) {
if (line.startsWith('#')) continue;
console.log(line);
}
// Output:
// 'Normal line'
// 'Another normal line'
21.2 if statements
These are two simple if statements: One with just a “then” branch and one with both a
“then” branch and an “else” branch:
if (cond) {
// then branch
}
180 21 Control flow statements
if (cond) {
// then branch
} else {
// else branch
}
if (cond1) {
// ···
} else if (cond2) {
// ···
}
if (cond1) {
// ···
} else if (cond2) {
// ···
} else {
// ···
}
if (cond) «then_statement»
else «else_statement»
So far, the then_statement has always been a block, but we can use any statement. That
statement must be terminated with a semicolon:
That means that else if is not its own construct, it’s simply an if statement whose
else_statement is another if statement.
switch («switch_expression») {
«switch_body»
}
case «case_expression»:
«statements»
21.3 switch statements 181
function englishToFrench(english) {
let french;
switch (english) {
case 'hello':
french = 'bonjour';
case 'goodbye':
french = 'au revoir';
}
182 21 Control flow statements
return french;
}
// The result should be 'bonjour'!
assert.equal(englishToFrench('hello'), 'au revoir');
That is, our implementation of dayOfTheWeek() only worked, because we used return.
We can fix englishToFrench() by using break:
function englishToFrench(english) {
let french;
switch (english) {
case 'hello':
french = 'bonjour';
break;
case 'goodbye':
french = 'au revoir';
break;
}
return french;
}
assert.equal(englishToFrench('hello'), 'bonjour'); // ok
function isWeekDay(name) {
switch (name) {
case 'Monday':
case 'Tuesday':
case 'Wednesday':
case 'Thursday':
case 'Friday':
return true;
case 'Saturday':
case 'Sunday':
return false;
}
}
assert.equal(isWeekDay('Wednesday'), true);
assert.equal(isWeekDay('Sunday'), false);
function isWeekDay(name) {
switch (name) {
21.4 while loops 183
case 'Monday':
case 'Tuesday':
case 'Wednesday':
case 'Thursday':
case 'Friday':
return true;
case 'Saturday':
case 'Sunday':
return false;
default:
throw new Error('Illegal value: '+name);
}
}
assert.throws(
() => isWeekDay('January'),
{message: 'Illegal value: January'});
Exercises: switch
• exercises/control-flow/number_to_month_test.mjs
• Bonus: exercises/control-flow/is_object_via_switch_test.mjs
while («condition») {
«statements»
}
// 'c'
while (true) {
if (Math.random() === 0) break;
}
let input;
do {
input = prompt('Enter text:');
console.log(input);
} while (input !== ':q');
prompt() is a global function that is available in web browsers. It prompts the user to
input text and returns it.
The first line is the head of the loop and controls how often the body (the remainder of the
loop) is executed. It has three parts and each of them is optional:
• initialization: sets up variables etc. for the loop. Variables declared here via
let or const only exist inside the loop.
• condition: This condition is checked before each loop iteration. If it is falsy, the
loop stops.
• post_iteration: This code is executed after each loop iteration.
«initialization»
while («condition») {
«statements»
«post_iteration»
}
// Output:
// 0
// 1
// 2
// Output:
// 'a'
// 'b'
// 'c'
If you omit all three parts of the head, you get an infinite loop:
for (;;) {
if (Math.random() === 0) break;
}
But you can also use a (mutable) variable that already exists:
console.log(elem);
}
In contrast, in for loops, you must declare variables via let or var, if their values change.
Exercise: for-of
exercises/control-flow/array_to_string_test.mjs
This is an example of using for-in properly, which involves boilerplate code (line A):
function getOwnPropertyNames(obj) {
const result = [];
for (const key in obj) {
if ({}.hasOwnProperty.call(obj, key)) { // (A)
result.push(key);
}
}
return result;
}
assert.deepEqual(
getOwnPropertyNames({ a: 1, b:2 }),
['a', 'b']);
assert.deepEqual(
getOwnPropertyNames(['a', 'b']),
['0', '1']); // strings!
We can implement the same functionality without for-in, which is almost always better:
function getOwnPropertyNames(obj) {
const result = [];
for (const key of Object.keys(obj)) {
result.push(key);
}
return result;
}
Quiz
See quiz app.
188 21 Control flow statements
Chapter 22
Exception handling
Contents
22.1 Motivation: throwing and catching exceptions . . . . . . . . . . . . 189
22.2 throw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
22.2.1 Options for creating error objects . . . . . . . . . . . . . . . . 191
22.3 The try statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
22.3.1 The try block . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
22.3.2 The catch clause . . . . . . . . . . . . . . . . . . . . . . . . . 191
22.3.3 The finally clause . . . . . . . . . . . . . . . . . . . . . . . . 192
22.4 Error classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
22.4.1 Properties of error objects . . . . . . . . . . . . . . . . . . . . 193
function readProfiles(filePaths) {
const profiles = [];
for (const filePath of filePaths) {
try {
const profile = readOneProfile(filePath);
profiles.push(profile);
189
190 22 Exception handling
Let’s examine what happens in line B: An error occurred, but the best place to handle
the problem is not the current location, it’s line A. There, we can skip the current file and
move on to the next one.
Therefore:
readProfiles(···)
for (const filePath of filePaths)
try
readOneProfile(···)
openFile(···)
if (!fs.existsSync(filePath))
throw
One by one, throw exits the nested constructs, until it encounters a try statement. Execu-
tion continues in the catch clause of that try statement.
22.2 throw
throw «value»;
Any value can be thrown, but it’s best to throw an instance of Error or its subclasses.
try {
«try_statements»
} catch (error) {
«catch_statements»
} finally {
«finally_statements»
}
• try-catch
• try-finally
• try-catch-finally
Since ECMAScript 2019, you can omit the catch parameter (error), if you are not inter-
ested in the value that was thrown.
The following code demonstrates that the value that is thrown in line A is indeed caught
in line B.
try {
func();
} catch (err) { // (B)
assert.equal(err, errorObject);
}
Let’s look at a common use case for finally: You have created a resource and want to
always destroy it when you are done with it – no matter what happens while working
with it. You’d implement that as follows:
The finally clause is always executed – even if an error is thrown (line A):
exercises/exception-handling/call_function_test.mjs
Quiz
See quiz app.
Chapter 23
Callable values
Contents
23.1 Kinds of functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
23.2 Ordinary functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
23.2.1 Parts of a function declaration . . . . . . . . . . . . . . . . . . 196
23.2.2 Roles played by ordinary functions . . . . . . . . . . . . . . . 197
23.2.3 Names of ordinary functions . . . . . . . . . . . . . . . . . . . 197
23.3 Specialized functions . . . . . . . . . . . . . . . . . . . . . . . . . . 198
23.3.1 Specialized functions are still functions . . . . . . . . . . . . . 198
23.3.2 Recommendation: prefer specialized functions . . . . . . . . . 199
23.3.3 Arrow functions . . . . . . . . . . . . . . . . . . . . . . . . . . 199
23.4 More kinds of functions and methods . . . . . . . . . . . . . . . . . 201
23.5 Returning values from functions and methods . . . . . . . . . . . . 202
23.6 Parameter handling . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
23.6.1 Terminology: parameters vs. arguments . . . . . . . . . . . . . 203
23.6.2 Terminology: callback . . . . . . . . . . . . . . . . . . . . . . 203
23.6.3 Too many or not enough arguments . . . . . . . . . . . . . . . 203
23.6.4 Parameter default values . . . . . . . . . . . . . . . . . . . . . 204
23.6.5 Rest parameters . . . . . . . . . . . . . . . . . . . . . . . . . . 204
23.6.6 Named parameters . . . . . . . . . . . . . . . . . . . . . . . . 205
23.6.7 Simulating named parameters . . . . . . . . . . . . . . . . . . 205
23.6.8 Spreading (...) into function calls . . . . . . . . . . . . . . . . 206
23.7 Dynamically evaluating code: eval(), new Function() (advanced) . 207
23.7.1 eval() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
23.7.2 new Function() . . . . . . . . . . . . . . . . . . . . . . . . . . 208
23.7.3 Recommendations . . . . . . . . . . . . . . . . . . . . . . . . 209
195
196 23 Callable values
As we have seen in §10.8 “Declarations: scope and activation”, function declarations are
activated early, while variable declarations (e.g. via const) are not.
The syntax of function declarations and function expressions is very similar. The context
determines which is which. For more information on this kind of syntactic ambiguity,
consult §6.5 “Ambiguous syntax”.
function add(x, y) {
return x + y;
}
This function declaration creates an ordinary function whose name is add. As an ordinary
function, add() can play three roles:
(As an aside, the names of classes normally start with capital letters.)
In contrast, the name of a function declaration is accessible inside the current scope:
Apart from nicer syntax, each kind of specialized function also supports new features,
making them better at their jobs than ordinary functions.
• Arrow functions are explained later in this chapter.
• Methods are explained in the chapter on single objects.
• Classes are explained in the chapter on classes.
Tbl. 23.1 lists the capabilities of ordinary and specialized functions.
• Arrow functions don’t have this as an implicit parameter. That is almost always
what you want if you use a real function, because it avoids an important this-
related pitfall (for details, consult §25.4.6 “Avoiding the pitfalls of this”).
function funcDecl(x, y) {
return x * y;
}
const arrowFunc = (x, y) => {
return x * y;
};
The (roughly) equivalent arrow function looks as follows. Arrow functions are expres-
sions.
Here, the body of the arrow function is a block. But it can also be an expression. The
following arrow function works exactly like the previous one.
If an arrow function has only a single parameter and that parameter is an identifier (not
a destructuring pattern) then you can omit the parentheses around the parameter:
const id = x => x;
Ordinary functions can be both methods and real functions. Alas, the two roles are in
conflict:
const person = {
name: 'Jill',
someMethod() {
const ordinaryFunc = function () {
assert.throws(
() => this.name, // (A)
/^TypeError: Cannot read property 'name' of undefined$/);
};
const arrowFunc = () => {
assert.equal(this.name, 'Jill'); // (B)
};
ordinaryFunc();
arrowFunc();
},
}
• Dynamic this: In line A, we try to access the this of .someMethod() from an or-
dinary function. There, it is shadowed by the function’s own this, which is unde-
fined (due the function call). Given that ordinary functions receive their this via
(dynamic) function or method calls, their this is called dynamic.
• Lexical this: In line B, we again try to access the this of .someMethod(). This
time, we succeed, because the arrow function does not have its own this. this is
resolved lexically, just like any other variable. That’s why the this of arrow func-
tions is called lexical.
If you want the expression body of an arrow function to be an object literal, you must put
the literal in parentheses:
If you don’t, JavaScript thinks, the arrow function has a block body (that doesn’t return
anything):
{a: 1} is interpreted as a block with the label a: and the expression statement 1. Without
an explicit return statement, the block body returns undefined.
This pitfall is caused by syntactic ambiguity: object literals and code blocks have the same
syntax. We use the parentheses to tell JavaScript that the body is an expression (an object
literal) and not a statement (a block).
For more information on shadowing this, consult §25.4.5 “this pitfall: accidentally shad-
owing this”.
So far, all (real) functions and methods, that we have seen, were:
• Single-result
• Synchronous
• Iteration treats objects as containers of data (so-called iterables) and provides a stan-
dardized way for retrieving what is inside them. If a function or a method returns
an iterable, it returns multiple values.
• Asynchronous programming deals with handling a long-running computation. You
are notified, when the computation is finished and can do something else in be-
tween. The standard pattern for asynchronously delivering single results is called
Promise.
These modes can be combined: For example, there are synchronous iterables and asyn-
chronous iterables.
Several new kinds of functions and methods help with some of the mode combinations:
• Async functions help implement functions that return Promises. There are also async
methods.
• Synchronous generator functions help implement functions that return synchronous
iterables. There are also synchronous generator methods.
• Asynchronous generator functions help implement functions that return asyn-
chronous iterables. There are also asynchronous generator methods.
Table 23.2: Syntax for creating functions and methods. The last column
specifies how many values are produced by an entity.
Result Values
Sync function Sync method
function f() {} { m() {} } value 1
f = function () {}
f = () => {}
Sync generator function Sync gen. method
function* f() {} { * m() {} } iterable 0+
f = function* () {}
Async function Async method
async function f() {} { async m() {} } Promise 1
f = async function () {}
f = async () => {}
Async generator function Async gen. method
async function* f() {} { async * m() {} } async iterable 0+
f = async function* () {}
Another example:
function boolToYesNo(bool) {
if (bool) {
return 'Yes';
} else {
return 'No';
}
}
assert.equal(boolToYesNo(true), 'Yes');
assert.equal(boolToYesNo(false), 'No');
If, at the end of a function, you haven’t returned anything explicitly, JavaScript returns
undefined for you:
23.6 Parameter handling 203
function noReturn() {
// No explicit return
}
assert.equal(noReturn(), undefined);
• Parameters are part of a function definition. They are also called formal parameters
and formal arguments.
• Arguments are part of a function call. They are also called actual parameters and
actual arguments.
// Output:
// 'a'
// 'b'
For example:
function foo(x, y) {
return [x, y];
}
assert.deepEqual(
f(undefined, undefined),
[undefined, 0]);
You can use a rest parameter to enforce a certain number of arguments. Take, for example,
the following function.
function createPoint(x, y) {
return {x, y};
// same as {x: x, y: y}
}
This function uses destructuring to access the properties of its single parameter. The pat-
tern it uses is an abbreviation for the following pattern:
{start: start=0, end: end=-1, step: step=1}
But it does not work if you call the function without any parameters:
> selectEntries()
TypeError: Cannot destructure property `start` of 'undefined' or 'null'.
You can fix this by providing a default value for the whole pattern. This default value
works the same as default values for simpler parameter definitions: If the parameter is
missing, the default is used.
function selectEntries({start=0, end=-1, step=1} = {}) {
return {start, end, step};
}
assert.deepEqual(
selectEntries(),
{ start: 0, end: -1, step: 1 });
// Output:
// 'a'
// 'b'
Spreading and rest parameters use the same syntax (...), but they serve opposite pur-
poses:
• Rest parameters are used when defining functions or methods. They collect argu-
ments into Arrays.
23.7 Dynamically evaluating code: eval(), new Function() (advanced) 207
• Spread arguments are used when calling functions or methods. They turn iterable
objects into arguments.
Math.max() returns the largest one of its zero or more arguments. Alas, it can’t be used
for Arrays, but spreading gives us a way out:
Similarly, the Array method .push() destructively adds its zero or more parameters to
the end of its Array. JavaScript has no method for destructively appending an Array to
another one. Once again, we are saved by spreading:
arr1.push(...arr2);
assert.deepEqual(arr1, ['a', 'b', 'c', 'd']);
23.7.1 eval()
Given a string str with JavaScript code, eval(str) evaluates that code and returns the
result:
• Directly, via a function call. Then the code in its argument is evaluated inside the
current scope.
• Indirectly, not via a function call. Then it evaluates its code in global scope.
“Not via a function call” means “anything that looks different than eval(···)”:
• eval.call(undefined, '···')
• (0, eval)('···') (uses the comma operator)
• window.eval('···')
• const e = eval; e('···')
• Etc.
window.myVariable = 'global';
function func() {
const myVariable = 'local';
// Direct eval
assert.equal(eval('myVariable'), 'local');
// Indirect eval
assert.equal(eval.call(undefined, 'myVariable'), 'global');
}
Evaluating code in global context is safer, because then the code has access to fewer in-
ternals.
The previous statement is equivalent to the next statement. Note that «param_1» (etc.)
are not inside string literals, anymore.
In the next example, we create the same function twice. First via new Function(), then
via a function expression:
23.7.3 Recommendations
Avoid dynamic evaluation of code as much as you can:
• It’s a security risk, because it may enable an attacker to execute arbitrary code with
the privileges of your code.
• It may be switched off. For example, in browsers, via a Content Security Policy.
Very often, JavaScript is dynamic enough so that you don’t need eval() or similar. In the
following example, what we are doing with eval() (line A) can be achieved just as well
without it (line B).
const obj = {a: 1, b: 2};
const propKey = 'b';
Quiz
See quiz app.
210 23 Callable values
Part VI
Modularity
211
Chapter 24
Modules
Contents
24.1 JavaScript source code formats . . . . . . . . . . . . . . . . . . . . . 214
24.1.1 Code before built-in modules was written in ECMAScript 5 . . 214
24.2 Before we had modules, we had scripts . . . . . . . . . . . . . . . . 214
24.3 Module systems created prior to ES6 . . . . . . . . . . . . . . . . . . 216
24.3.1 Server side: CommonJS modules . . . . . . . . . . . . . . . . 216
24.3.2 Client side: AMD (Asynchronous Module Definition) modules 216
24.3.3 Characteristics of JavaScript modules . . . . . . . . . . . . . . 217
24.4 ECMAScript modules . . . . . . . . . . . . . . . . . . . . . . . . . . 218
24.4.1 ES modules: syntax, semantics, loader API . . . . . . . . . . . 218
24.5 Exporting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
24.5.1 Named exports . . . . . . . . . . . . . . . . . . . . . . . . . . 218
24.5.2 Default exports . . . . . . . . . . . . . . . . . . . . . . . . . . 219
24.6 Importing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
24.6.1 Imports are read-only views on exports . . . . . . . . . . . . . 220
24.6.2 Syntactic pitfall: importing is not destructuring . . . . . . . . . 221
24.6.3 ESM’s transparent support for cyclic imports (advanced) . . . 222
24.7 npm packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
24.7.1 Packages are installed inside a directory node_modules/ . . . . 223
24.7.2 Why can npm be used to install frontend libraries? . . . . . . . 224
24.8 Naming modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
24.9 Module specifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
24.9.1 Categories of module specifiers . . . . . . . . . . . . . . . . . 225
24.9.2 ES module specifiers in browsers . . . . . . . . . . . . . . . . 225
24.9.3 ES module specifiers on Node.js . . . . . . . . . . . . . . . . . 226
24.10Loading modules dynamically via import() . . . . . . . . . . . . . . 227
24.10.1 Example: loading a module dynamically . . . . . . . . . . . . 227
24.10.2 Use cases for import() . . . . . . . . . . . . . . . . . . . . . . 228
24.11Preview: import.meta.url . . . . . . . . . . . . . . . . . . . . . . . . 229
213
214 24 Modules
<script src="other-module1.js"></script>
<script src="other-module2.js"></script>
<script src="my-module.js"></script>
// Body
function internalFunc() {
// ···
}
function exportedFunc() {
importedFunc1();
importedFunc2();
internalFunc();
}
myModule is a global variable that is assigned the result of immediately invoking a func-
tion expression. The function expression starts in the first line. It is invoked in the last
line.
This way of wrapping a code fragment is called immediately invoked function expression
(IIFE, coined by Ben Alman). What do we gain from an IIFE? var is not block-scoped (like
const and let), it is function-scoped: The only way to create new scopes for var-declared
variables is via functions or methods (with const and let, you can use either functions,
methods or blocks {}). Therefore, the IIFE in the example hides all of the following vari-
ables from global scope and minimizes name clashes: importedFunc1, importedFunc2,
internalFunc, exportedFunc.
Note that we are using an IIFE in a particular manner: At the end, we pick what we
want to export and return it via an object literal. That is called the revealing module pattern
(coined by Christian Heilmann).
• Libraries in script files export and import functionality via global variables, which
risks name clashes.
• Dependencies are not stated explicitly and there is no built-in way for a script to
load the scripts it depends on. Therefore, the web page has to load not just the
scripts that are needed by the page, but also the dependencies of those scripts, the
dependencies’ dependencies, etc. And it has to do so in the right order!
216 24 Modules
From now on, CommonJS module means the Node.js version of this standard (which has a
few additional features). This is an example of a CommonJS module:
// Imports
var importedFunc1 = require('./other-module1.js').importedFunc1;
var importedFunc2 = require('./other-module2.js').importedFunc2;
// Body
function internalFunc() {
// ···
}
function exportedFunc() {
importedFunc1();
importedFunc2();
internalFunc();
}
// Exports
module.exports = {
exportedFunc: exportedFunc,
};
define(['./other-module1.js', './other-module2.js'],
function (otherModule1, otherModule2) {
var importedFunc1 = otherModule1.importedFunc1;
var importedFunc2 = otherModule2.importedFunc2;
function internalFunc() {
// ···
}
function exportedFunc() {
importedFunc1();
importedFunc2();
internalFunc();
}
return {
exportedFunc: exportedFunc,
};
});
On the plus side, AMD modules can be executed directly. In contrast, CommonJS mod-
ules must either be compiled before deployment or custom source code must be gener-
ated and evaluated dynamically (think eval()). That isn’t always permitted on the web.
• With CommonJS, ES modules share the compact syntax and support for cyclic de-
pendencies.
• With AMD, ES modules share being designed for asynchronous loading.
function internalFunc() {
···
}
1. Syntax (how code is written): What is a module? How are imports and exports
declared? Etc.
2. Semantics (how code is executed): How are variable bindings exported? How are
imports connected with exports? Etc.
3. A programmatic loader API for configuring module loading.
24.5 Exporting
24.5.1 Named exports
Each module can have zero or more named exports.
24.5 Exporting 219
lib/my-math.mjs
main1.mjs
main2.mjs
Module main2.mjs has a so-called namespace import – all named exports of my-math.mjs
can be accessed as properties of the object myMath:
assert.deepEqual(
Object.keys(myMath), ['LIGHTSPEED', 'square']);
my-func.mjs
main.mjs
Note the syntactic difference: The curly braces around named imports indicate that we
are reaching into the module, while a default import is the module.
The most common use case for a default export is a module that contains a single function
or a single class.
Second, you can directly default-export values. In that style, export default is itself
much like a declaration.
export default 'abc';
export default foo();
export default /^xyz$/;
export default 5 * 7;
export default { no: false, yes: true };
Why are there two default export styles? The reason is that export default can’t be used
to label const: const may define multiple values, but export default needs exactly one
value. Consider the following hypothetical code:
// Not legal JavaScript!
export default const foo = 1, bar = 2, baz = 3;
With this code, you don’t know which one of the three values is the default export.
24.6 Importing
24.6.1 Imports are read-only views on exports
So far, we have used imports and exports intuitively and everything seems to have
worked as expected. But now it is time to take a closer look at how imports and exports
24.6 Importing 221
counter.mjs
main.mjs
main.mjs name-imports both exports. When we use incCounter(), we discover that the
connection to counter is live – we can always access the live state of that variable:
Note that, while the connection is live and we can read counter, we cannot change this
variable (e.g. via counter++).
• It is easier to split modules, because previously shared variables can become ex-
ports.
• This behavior is crucial for supporting transparent cyclic imports. Read on for
more information.
• You can destructure again inside a destructuring pattern, but the {} in an import
statement can’t be nested.
N O
P Q R S
• Instantiation: Every module is visited and its imports are connected to its exports.
Before a parent can be instantiated, all of its children must be instantiated.
• Evaluation: The bodies of the modules are executed. Once again, children are eval-
uated before parents.
This approach handles cyclic imports correctly, due to two features of ES modules:
• Due to the static structure of ES modules, the exports are already known after pars-
ing. That makes it possible to instantiate P before its child M: P can already look
up M’s exports.
• When P is evaluated, M hasn’t been evaluated, yet. However, entities in P can al-
ready mention imports from M. They just can’t use them, yet, because the imported
values are filled in later. For example, a function in P can access an import from
M. The only limitation is that we must wait until after the evaluation of M, before
calling that function.
Imports being filled in later is enabled by them being “live immutable views” on
exports.
{
"name": "foo",
"version": "1.0.0",
24.7 npm packages 223
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
• name specifies the name of this package. Once it is uploaded to the npm registry, it
can be installed via npm install foo.
• version is used for version management and follows semantic versioning, with
three numbers:
– Major version: is incremented when incompatible API changes are made.
– Minor version: is incremented when functionality is added in a backward
compatible manner.
– Patch version: is incremented when backward compatible changes are made.
• description, keywords, author make it easier to find packages.
• license clarifies how you can use this package.
• main: specifies the module that “is” the package (explained later in this chapter).
• scripts: are commands that you can execute via npm run. For example, the script
test can be executed via npm run test.
• /tmp/a/b/node_modules
• /tmp/a/node_modules
• /tmp/node_modules
When installing a package foo, npm uses the closest node_modules. If, for example, we
are inside /tmp/a/b/ and there is a node_modules in that directory, then npm puts the
package inside the directory
/tmp/a/b/node_modules/foo/
When importing a module, we can use a special module specifier to tell Node.js that we
want to import it from an installed package. How exactly that works, is explained later.
For now, consider the following example:
224 24 Modules
// /home/jane/proj/main.mjs
import * as theModule from 'the-package/the-module.mjs';
To find the-module.mjs (Node.js prefers the filename extension .mjs for ES modules),
Node.js walks up the node_module chain and searches the following locations:
• /home/jane/proj/node_modules/the-package/the-module.mjs
• /home/jane/node_modules/the-package/the-module.mjs
• /home/node_modules/the-package/the-module.mjs
But that style does not work for default imports: I like underscore-casing for namespace
objects, but it is not a good choice for functions etc.
'./some/other/module.mjs'
'../../lib/counter.mjs'
'/home/jane/file-tools.mjs'
'https://example.com/some-module.mjs'
'file:///home/john/tmp/main.mjs'
• Bare path: does not start with a dot, a slash or a protocol, and consists of a single
filename without an extension. Examples:
'lodash'
'the-package'
• Deep import path: starts with a bare path and has at least one slash. Example:
'the-package/dist/the-module.mjs'
• Relative paths, absolute paths and URLs work as expected. They all must point to
real files. (In contrast to CommonJS, which lets you omit filename extensions and
more.)
• The file name extensions of modules don’t matter, as long as they are served with
the content type text/javascript.
• How bare paths will end up being handled is not yet clear. You will probably
eventually be able to map them to other specifiers via lookup tables.
Note that bundling tools such as webpack, which combine modules into fewer files, are
often less strict with specifiers than browsers. That’s because they operate at build/-
compile time (not at runtime) and can search for files by traversing the file system.
226 24 Modules
• Relative paths are resolved as they are in web browsers – relative to the path of the
current module.
• Absolute paths are currently not supported. As a work-around, you can use URLs
that start with file:///. You can create such URLs via url.pathToFileURL().
• A bare path is interpreted as a package name and resolved relative to the closest
node_modules directory. What module should be loaded, is determined by looking
at property "main" of the package’s package.json (similarly to CommonJS).
• Deep import paths are also resolved relatively to the closest node_modules direc-
tory. They contain file names, so it is always clear which module is meant.
All specifiers, except bare paths, must refer to actual files. That is, ESM does not support
the following CommonJS features:
All built-in Node.js modules are available via bare paths and have named ESM exports.
For example:
assert.equal(
path.join('a/b/c', '../d'), 'a/b/d');
The filename extension .js stands for either ESM or CommonJS. Which one it is, is config-
ured via the “closest” package.json (in the current directory, the parent directory, etc.).
Using package.json in this manner is independent of packages.
24.10 Loading modules dynamically via import() 227
Not all source code that is executed by Node.js, comes from files. You can also send it
code via stdin, --eval and --print. The command line option --input-type lets you
specify how such code is interpreted:
• As CommonJS (the default): --input-type=commonjs
• As ESM: --input-type=module
function loadConstant() {
return import(moduleSpecifier)
.then(myMath => {
const result = myMath.LIGHTSPEED;
assert.equal(result, 299792458);
return result;
});
}
Next, we’ll implement the exact same functionality in main2.mjs, but via a so-called async
function, which provides nicer syntax for Promises.
Some functionality of web apps doesn’t have to be present when they start, it can be
loaded on demand. Then import() helps, because you can put such functionality into
modules. For example:
/* Error handling */
})
});
Sometimes you may want to load a module depending on whether a condition is true.
For example, to load a polyfill on legacy platforms. That looks as follows.
if (isLegacyPlatform()) {
import(···)
.then(···);
}
import(`messages_${getLocale()}.js`)
.then(···);
Its most important property is import.meta.url, which contains a string with the URL
of the current module file. For example:
'https://example.com/code/main.mjs'
Parameter input contains the URL to be parsed. It can be relative if the second parameter,
base, is provided.
On other words – this constructor lets us resolve a relative path against a base URL:
This is how we get a URL instance that points to a file data.txt that sits next to the current
module:
230 24 Modules
'file:///Users/rauschma/my-module.mjs'
Many Node.js file system operations accept either strings with paths or instances of URL.
That enables us to read a sibling file data.txt of the current module:
fs.promises contains a Promise-based version of the fs API, that can be used with async
functions.
The Node.js module url has two functions for converting between file: URLs and
paths:
If you need a path that can be used in the local file system, then property .pathname of
URL instances does not always work:
assert.equal(
new URL('file:///tmp/with%20space.txt').pathname,
'/tmp/with%20space.txt');
Similarly, pathToFileURL() does more than just prepend 'file://' to an absolute path.
24.12 Quick reference: exporting and importing 231
// Named exports
export {foo, b as bar};
export function f() {}
export const >
// Default exports
export default function f() {} // declaration with optional name
// Replacement for `const` (there must be exactly one value)
export default 123;
24.12.2 Importing
// Empty import (for modules with side effects)
import './some-module.mjs';
// Default import
import someModule from './some-module.mjs';
// Namespace import
import * as someModule from './some-module.mjs';
// Named imports
import {foo, bar as b} from './some-module.mjs';
// Combinations:
import someModule, * as someModule from './some-module.mjs';
import someModule, {foo, bar as b} from './some-module.mjs';
Quiz
See quiz app.
232 24 Modules
Chapter 25
Single objects
Contents
25.1 What is an object? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
25.1.1 Roles of objects: record vs. dictionary . . . . . . . . . . . . . . 235
25.2 Objects as records . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
25.2.1 Object literals: properties . . . . . . . . . . . . . . . . . . . . . 235
25.2.2 Object literals: property value shorthands . . . . . . . . . . . . 236
25.2.3 Getting properties . . . . . . . . . . . . . . . . . . . . . . . . . 236
25.2.4 Setting properties . . . . . . . . . . . . . . . . . . . . . . . . . 236
25.2.5 Object literals: methods . . . . . . . . . . . . . . . . . . . . . . 237
25.2.6 Object literals: accessors . . . . . . . . . . . . . . . . . . . . . 237
25.3 Spreading into object literals (...) . . . . . . . . . . . . . . . . . . . 238
25.3.1 Use case for spreading: copying objects . . . . . . . . . . . . . 238
25.3.2 Use case for spreading: default values for missing properties . 239
25.3.3 Use case for spreading: non-destructively changing properties 239
25.4 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
25.4.1 Methods are properties whose values are functions . . . . . . 240
25.4.2 .call(): specifying this via a parameter . . . . . . . . . . . . 240
25.4.3 .bind(): pre-filling this and parameters of functions . . . . . 241
25.4.4 this pitfall: extracting methods . . . . . . . . . . . . . . . . . 243
25.4.5 this pitfall: accidentally shadowing this . . . . . . . . . . . . 244
25.4.6 Avoiding the pitfalls of this . . . . . . . . . . . . . . . . . . . 245
25.4.7 The value of this in various contexts . . . . . . . . . . . . . . 246
25.5 Objects as dictionaries (advanced) . . . . . . . . . . . . . . . . . . . 246
25.5.1 Arbitrary fixed strings as property keys . . . . . . . . . . . . . 246
25.5.2 Computed property keys . . . . . . . . . . . . . . . . . . . . . 247
25.5.3 The in operator: is there a property with a given key? . . . . . 248
25.5.4 Deleting properties . . . . . . . . . . . . . . . . . . . . . . . . 249
25.5.5 Listing property keys . . . . . . . . . . . . . . . . . . . . . . . 249
25.5.6 Listing property values via Object.values() . . . . . . . . . . 250
233
234 25 Single objects
1. Single objects: How do objects, JavaScript’s basic OOP building blocks, work in
isolation?
2. Prototype chains: Each object has a chain of zero or more prototype objects. Proto-
types are JavaScript’s core inheritance mechanism.
3. Classes: JavaScript’s classes are factories for objects. The relationship between a
class and its instances is based on prototypal inheritance.
4. Subclassing: The relationship between a subclass and its superclass is also based on
prototypal inheritance.
SuperClass
superData
superMthd
mthd ƒ
MyClass SubClass
mthd ƒ __proto__ data subData
data 4 data 4 mthd subMthd
• First, we’ll explore objects-as-records. Even though property keys are strings or
symbols under the hood, they will appear as fixed identifiers to us, in this part of
the chapter.
• Later, we’ll explore objects-as-dictionaries. Note that Maps are usually better dic-
tionaries than objects. However, some of the operations that we’ll encounter, can
also be useful for objects-as-records.
const jane = {
first: 'Jane',
last: 'Doe', // optional trailing comma
};
In the example, we created an object via an object literal, which starts and ends with curly
braces {}. Inside it, we defined two properties (key-value entries):
• The first property has the key first and the value 'Jane'.
• The second property has the key last and the value 'Doe'.
We will later see other ways of specifying property keys, but with this way of specifying
them, they must follow the rules of JavaScript variable names. For example, you can
use first_name as a property key, but not first-name). However, reserved words are
allowed:
const obj = {
if: true,
const: true,
};
In order to check the effects of various operations on objects, we’ll occasionally use Ob-
ject.keys() in this part of the chapter. It lists property keys:
236 25 Single objects
function createPoint(x, y) {
return {x, y};
}
assert.deepEqual(
createPoint(9, 2),
{ x: 9, y: 2 }
);
const jane = {
first: 'Jane',
last: 'Doe',
};
assert.equal(jane.unknownProperty, undefined);
const obj = {
prop: 1,
};
assert.equal(obj.prop, 1);
obj.prop = 2; // (A)
assert.equal(obj.prop, 2);
obj.unknownProperty = 'abc';
25.2 Objects as records 237
assert.deepEqual(
Object.keys(obj), ['unknownProperty']);
const jane = {
first: 'Jane', // data property
says(text) { // method
return `${this.first} says “${text}”`; // (A)
}, // comma as separator (optional at end)
};
assert.equal(jane.says('hello'), 'Jane says “hello”');
During the method call jane.says('hello'), jane is called the receiver of the method
call and assigned to the special variable this. That enables method .says() to access the
sibling property .first in line A.
25.2.6.1 Getters
const jane = {
first: 'Jane',
last: 'Doe',
get full() {
return `${this.first} ${this.last}`;
},
};
25.2.6.2 Setters
const jane = {
first: 'Jane',
last: 'Doe',
set full(fullName) {
const parts = fullName.split(' ');
238 25 Single objects
this.first = parts[0];
this.last = parts[1];
},
};
Inside an object literal, a spread property adds the properties of another object to the current
one:
Caveat – copying is shallow: copy is a fresh object with duplicates of all properties (key-
value entries) of original. But if property values are objects, then those are not copied
themselves; they are shared between original and copy. Let’s look at an example.
The first level of copy is really a copy: If you change any properties at that level, it does
not affect the original:
copy.a = 2;
assert.deepEqual(
25.3 Spreading into object literals (...) 239
However, deeper levels are not copied. For example, the value of .b is shared between
original and copy. Changing .b in the copy, also changes it in the original.
copy.b.foo = false;
assert.deepEqual(
original, { a: 1, b: {foo: false} });
25.3.2 Use case for spreading: default values for missing properties
If one of the inputs of your code is an object with data, you can make properties optional
by specifying default values that are used if those properties are missing. One technique
for doing so, is via an object whose properties contain the default values. In the following
example, that object is DEFAULTS:
The result, the object allData, is created by copying DEFAULTS and overriding its proper-
ties with those of providedData.
But you don’t need an object to specify the default values, you can also specify them
inside the object literal, individually:
With spreading, we can change .foo non-destructively – we make a copy of obj where
.foo has a different value:
240 25 Single objects
25.4 Methods
25.4.1 Methods are properties whose values are functions
Let’s revisit the example that was used to introduce methods:
const jane = {
first: 'Jane',
says(text) {
return `${this.first} says “${text}”`;
},
};
Why is that? Remember that, in the chapter on callable values, we learned that ordinary
functions play several roles. Method is one of those roles. Therefore, under the hood, jane
roughly looks as follows.
const jane = {
first: 'Jane',
says: function (text) {
return `${this.first} says “${text}”`;
},
};
If you make a method call, this is an implicit parameter that is filled in via the receiver
of the call:
const obj = {
method(x) {
assert.equal(this, obj); // implicit parameter
25.4 Methods 241
assert.equal(x, 'a');
},
};
obj.method.call(obj, 'a');
As an aside, that means that there are actually two different dot operators:
They are different in that (2) is not just (1), followed by the function call operator ().
Instead, (2) additionally specifies a value for this.
If you function-call an ordinary function, its implicit parameter this is also provided – it
is implicitly set to undefined:
function func(x) {
assert.equal(this, undefined); // implicit parameter
assert.equal(x, 'a');
}
func('a');
func.call(undefined, 'a');
this being set to undefined during a function call, indicates that it is a feature that is only
needed during a method call.
Next, we’ll examine the pitfalls of using this. Before we can do that, we need one more
tool: method .bind() of functions.
.bind() returns a new function boundFunc(). Calling that function invokes someFunc()
with this set to thisValue and these parameters: arg1, arg2, followed by the parameters
of boundFunc().
boundFunc('a', 'b')
someFunc.call(thisValue, arg1, arg2, 'a', 'b')
242 25 Single objects
Considering the previous section, .bind() can be implemented as a real function as fol-
lows:
Using .bind() for real functions is somewhat unintuitive, because you have to provide
a value for this. Given that it is undefined during function calls, it is usually set to
undefined or null.
In the following example, we create add8(), a function that has one parameter, by binding
the first parameter of add() to 8.
function add(x, y) {
return x + y;
}
In the following code, we turn method .says() into the stand-alone function func():
const jane = {
first: 'Jane',
says(text) {
return `${this.first} says “${text}”`; // (A)
},
};
Setting this to jane via .bind() is crucial here. Otherwise, func() wouldn’t work prop-
erly, because this is used in line A.
25.4 Methods 243
The .bind() ensures that this is always jane when we call func().
You can also use arrow functions to extract methods:
const func3 = text => jane.says(text);
assert.equal(func3('hello'), 'Jane says “hello”');
The following is a simplified version of code that you may see in actual web development:
class ClickHandler {
constructor(id, elem) {
this.id = id;
elem.addEventListener('click', this.handleClick); // (A)
}
handleClick(event) {
244 25 Single objects
In line A, we don’t extract the method .handleClick() properly. Instead, we should do:
elem.addEventListener('click', this.handleClick.bind(this));
Consider the following problem: When you are inside an ordinary function, you can’t
access the this of the surrounding scope, because the ordinary function has its own this.
In other words: a variable in an inner scope hides a variable in an outer scope. That is
called shadowing. The following code is an example:
const prefixer = {
prefix: '==> ',
prefixStringArray(stringArray) {
return stringArray.map(
function (x) {
return this.prefix + x; // (A)
});
},
};
assert.throws(
() => prefixer.prefixStringArray(['a', 'b']),
/^TypeError: Cannot read property 'prefix' of undefined$/);
In line A, we want to access the this of .prefixStringArray(). But we can’t, since the
surrounding ordinary function has its own this, that shadows (blocks access to) the this
of the method. The value of the former this is undefined – due to the callback being
function-called. That explains the error message.
The simplest way to fix this problem is via an arrow function, which doesn’t have its own
this and therefore doesn’t shadow anything:
const prefixer = {
prefix: '==> ',
prefixStringArray(stringArray) {
return stringArray.map(
(x) => {
25.4 Methods 245
return this.prefix + x;
});
},
};
assert.deepEqual(
prefixer.prefixStringArray(['a', 'b']),
['==> a', '==> b']);
We can also store this in a different variable (line A), so that it doesn’t get shadowed:
prefixStringArray(stringArray) {
const that = this; // (A)
return stringArray.map(
function (x) {
return that.prefix + x;
});
},
Another option is to specify a fixed this for the callback, via .bind() (line A):
prefixStringArray(stringArray) {
return stringArray.map(
function (x) {
return this.prefix + x;
}.bind(this)); // (A)
},
Lastly, .map() lets us specify a value for this (line A) that it uses when invoking the
callback:
prefixStringArray(stringArray) {
return stringArray.map(
function (x) {
return this.prefix + x;
},
this); // (A)
},
• this becomes easier to understand, because it will only appear inside methods
(never inside ordinary functions). That makes it clear that this is an OOP feature.
However, even though I don’t use (ordinary) function expressions, anymore, I do like func-
tion declarations syntactically. You can use them safely if you don’t refer to this inside
them. The static checking tool ESLint can warn you during development when you do
this wrong, via a built-in rule.
Alas, there is no simple way around the first pitfall: Whenever you extract a method, you
have to be careful and do it properly. For example, by binding this.
Inside a callable entity, the value of this depends on how the callable entity is invoked
and what kind of callable entity it is:
• Function call:
– Ordinary functions: this === undefined
– Arrow functions: this is same as in surrounding scope (lexical this)
• Method call: this is receiver of call
• new: this refers to newly created instance
However, I like to pretend that you can’t access this in top-level scopes, because top-
level this is confusing and not that useful.
We first look at features of objects that are related to dictionaries, but also useful for
objects-as-records. This section concludes with tips for actually using objects as dictio-
naries (spoiler: use Maps if you can).
const obj = {
mustBeAnIdentifier: 123,
};
25.5 Objects as dictionaries (advanced) 247
// Get property
assert.equal(obj.mustBeAnIdentifier, 123);
// Set property
obj.mustBeAnIdentifier = 'abc';
assert.equal(obj.mustBeAnIdentifier, 'abc');
As a next step, we’ll go beyond this limitation for property keys: In this section, we’ll use
arbitrary fixed strings as keys. In the next subsection, we’ll dynamically compute keys.
First – when creating property keys via object literals, we can quote property keys (with
single or double quotes):
const obj = {
'Can be any string!': 123,
};
Second – when getting or setting properties, we can use square brackets with strings
inside them:
// Get property
assert.equal(obj['Can be any string!'], 123);
// Set property
obj['Can be any string!'] = 'abc';
assert.equal(obj['Can be any string!'], 'abc');
const obj = {
'A nice method'() {
return 'Yes!';
},
};
The syntax of dynamically computed property keys in object literals is inspired by dy-
namically accessing properties. That is, we can use square brackets to wrap expressions:
const obj = {
['Hello world!']: true,
['f'+'o'+'o']: 123,
[Symbol.toStringTag]: 'Goodbye', // (A)
248 25 Single objects
};
The main use case for computed keys is having symbols as property keys (line A).
Note that the square brackets operator for getting and setting properties works with ar-
bitrary expressions:
assert.equal(obj['f'+'o'+'o'], 123);
assert.equal(obj['==> foo'.slice(-3)], 123);
assert.equal(obj[methodKey](), 'Yes!');
For the remainder of this chapter, we’ll mostly use fixed property keys again (because
they are syntactically more convenient). But all features are also available for arbitrary
strings and symbols.
'exists');
assert.equal(
obj.unknownKey ? 'exists' : 'does not exist',
'does not exist');
The previous checks work, because obj.foo is truthy and because reading a missing prop-
erty returns undefined (which is falsy).
There is, however, one important caveat: Truthiness checks fail if the property exists, but
has a falsy value (undefined, null, false, 0, "", etc.):
assert.equal(
obj.bar ? 'exists' : 'does not exist',
'does not exist'); // should be: 'exists'
delete obj.foo;
assert.deepEqual(Object.keys(obj), []);
Table 25.1: Standard library methods for listing own (non-inherited) prop-
erty keys. All of them return Arrays with strings and/or symbols.
Each of the methods in tbl. 25.1 returns an Array with the own property keys of the
parameter. In the names of the methods, you can see that the following distinction is
made:
By default, most properties are enumerable. The next example shows how to change that.
It also demonstrates the various ways of listing property keys.
const enumerableSymbolKey = Symbol('enumerableSymbolKey');
const nonEnumSymbolKey = Symbol('nonEnumSymbolKey');
assert.deepEqual(
Object.keys(obj),
[ 'enumerableStringKey' ]);
assert.deepEqual(
Object.getOwnPropertyNames(obj),
[ 'enumerableStringKey', 'nonEnumStringKey' ]);
assert.deepEqual(
Object.getOwnPropertySymbols(obj),
[ enumerableSymbolKey, nonEnumSymbolKey ]);
assert.deepEqual(
Reflect.ownKeys(obj),
[
'enumerableStringKey', 'nonEnumStringKey',
enumerableSymbolKey, nonEnumSymbolKey,
]);
Exercise: Object.entries()
exercises/single-objects/find_key_test.mjs
To demonstrate both, we’ll use them to implement two tool functions from the library
Underscore in the next subsubsections.
pick returns a copy of object that only has those properties, whose keys are mentioned
as arguments:
const address = {
street: 'Evergreen Terrace',
number: '742',
city: 'Springfield',
state: 'NT',
zip: '49007',
};
assert.deepEqual(
pick(address, 'street', 'number'),
{
street: 'Evergreen Terrace',
number: '742',
}
);
invert returns a copy of object where the keys and values of all properties are swapped:
assert.deepEqual(
invert({a: 1, b: 2, c: 3}),
{1: 'a', 2: 'b', 3: 'c'}
);
function invert(object) {
const mappedEntries = Object.entries(object)
.map(([key, value]) => [value, key]);
return Object.fromEntries(mappedEntries);
}
25.5 Objects as dictionaries (advanced) 253
function fromEntries(iterable) {
const result = {};
for (const [key, value] of iterable) {
let coercedKey;
if (typeof key === 'string' || typeof key === 'symbol') {
coercedKey = key;
} else {
coercedKey = String(key);
}
result[coercedKey] = value;
}
return result;
}
The first pitfall is that the in operator also finds inherited properties:
We want dict to be treated as empty, but the in operator detects the properties it inherits
from its prototype, Object.prototype.
The second pitfall is that you can’t use the property key __proto__, because it has special
powers (it sets the prototype of the object):
dict['__proto__'] = 123;
// No property was added to dict:
assert.deepEqual(Object.keys(dict), []);
• Whenever you can, use Maps. They are the best solution for dictionaries.
• If you can’t: use a library for objects-as-dictionaries that does everything safely.
• If you can’t: use an object without a prototype.
254 25 Single objects
dict['__proto__'] = 123;
assert.deepEqual(Object.keys(dict), ['__proto__']);
We avoided both pitfalls: First, a property without a prototype does not inherit any
properties (line A). Second, in modern JavaScript, __proto__ is implemented via Ob-
ject.prototype. That means that it is switched off if Object.prototype is not in the
prototype chain.
• .toString()
• .valueOf()
25.6.1 .toString()
25.6.2 .valueOf()
25.7.1 Object.assign()
This expression assigns all properties of source_1 to target, then all properties of
source_2, etc. At the end, it returns target. For example:
assert.deepEqual(
result, { foo: 1, bar: 4, baz: 3 });
// target was modified and returned:
assert.equal(result, target);
The use cases for Object.assign() are similar to those for spread properties. In a way, it
spreads destructively.
There is one caveat: Object.freeze(obj) freezes shallowly. That is, only the properties
of obj are frozen, but not objects stored in properties.
When you are using one of the operations for handling property attributes, attributes
are specified via property descriptors: objects where each property represents one attribute.
For example, this is how you read the attributes of a property obj.foo:
256 25 Single objects
assert.deepEqual(Object.keys(obj), ['foo']);
For more information on property attributes and property descriptors, consult “Speaking
JavaScript”.
Quiz
See quiz app.
Chapter 26
Contents
26.1 Prototype chains . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
26.1.1 JavaScript’s operations: all properties vs. own properties . . . 259
26.1.2 Pitfall: only the first member of a prototype chain is mutated . 259
26.1.3 Tips for working with prototypes (advanced) . . . . . . . . . . 260
26.1.4 Sharing data via prototypes . . . . . . . . . . . . . . . . . . . 261
26.2 Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
26.2.1 A class for persons . . . . . . . . . . . . . . . . . . . . . . . . 263
26.2.2 Classes under the hood . . . . . . . . . . . . . . . . . . . . . . 264
26.2.3 Class definitions: prototype properties . . . . . . . . . . . . . 265
26.2.4 Class definitions: static properties . . . . . . . . . . . . . . . . 266
26.2.5 The instanceof operator . . . . . . . . . . . . . . . . . . . . . 266
26.2.6 Why I recommend classes . . . . . . . . . . . . . . . . . . . . 266
26.3 Private data for classes . . . . . . . . . . . . . . . . . . . . . . . . . . 267
26.3.1 Private data: naming convention . . . . . . . . . . . . . . . . 267
26.3.2 Private data: WeakMaps . . . . . . . . . . . . . . . . . . . . . 268
26.3.3 More techniques for private data . . . . . . . . . . . . . . . . . 269
26.4 Subclassing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
26.4.1 Subclasses under the hood (advanced) . . . . . . . . . . . . . 270
26.4.2 instanceof in more detail (advanced) . . . . . . . . . . . . . . 271
26.4.3 Prototype chains of built-in objects (advanced) . . . . . . . . . 271
26.4.4 Dispatched vs. direct method calls (advanced) . . . . . . . . . 274
26.4.5 Mixin classes (advanced) . . . . . . . . . . . . . . . . . . . . . 275
26.5 FAQ: objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
26.5.1 Why do objects preserve the insertion order of properties? . . . 277
257
258 26 Prototype chains and classes
1. Single objects: How do objects, JavaScript’s basic OOP building blocks, work in
isolation?
2. Prototype chains: Each object has a chain of zero or more prototype objects. Proto-
types are JavaScript’s core inheritance mechanism.
3. Classes: JavaScript’s classes are factories for objects. The relationship between a
class and its instances is based on prototypal inheritance.
4. Subclassing: The relationship between a subclass and its superclass is also based on
prototypal inheritance.
SuperClass
superData
superMthd
mthd ƒ
MyClass SubClass
mthd ƒ __proto__ data subData
data 4 data 4 mthd subMthd
Given that a prototype object can have a prototype itself, we get a chain of objects – the
so-called prototype chain. That means that inheritance gives us the impression that we are
dealing with single objects, but we are actually dealing with chains of objects.
Fig. 26.2 shows what the prototype chain of obj looks like.
Non-inherited properties are called own properties. obj has one own property, .objProp.
26.1 Prototype chains 259
...
proto
__proto__
protoProp 'a'
obj
__proto__
objProp 'b'
Figure 26.2: obj starts a chain of objects that continues with proto and other objects.
> Object.keys(obj)
[ 'foo' ]
Read on for another operation that also only considers own properties: setting properties.
const proto = {
protoProp: 'a',
};
const obj = {
__proto__: proto,
objProp: 'b',
};
In the next code snippet, we set the inherited property obj.protoProp (line A). That
260 26 Prototype chains and classes
“changes” it by creating an own property: When reading obj.protoProp, the own prop-
erty is found first and its value overrides the value of the inherited property.
...
proto
__proto__
protoProp 'a'
obj
__proto__
objProp 'b'
protoProp 'x'
Figure 26.3: The own property .protoProp of obj overrides the property inherited from
proto.
I recommend to avoid the pseudo-property __proto__: As we will see later, not all objects
have it.
However, __proto__ in object literals is different. There, it is a built-in feature and always
available.
• The best way to set a prototype is when creating an object – via __proto__ in an
object literal or via:
Object.create(proto: Object) : Object
If you have to, you can use Object.setPrototypeOf() to change the prototype of
an existing object. But that may affect performance negatively.
This is how these features are used:
const proto1 = {};
const proto2 = {};
Object.setPrototypeOf(obj, proto2);
assert.equal(Object.getPrototypeOf(obj), proto2);
So far, “p is a prototype of o” always meant “p is a direct prototype of o”. But it can also be
used more loosely and mean that p is in the prototype chain of o. That looser relationship
can be checked via:
p.isPrototypeOf(o)
For example:
const a = {};
const b = {__proto__: a};
const c = {__proto__: b};
assert.equal(a.isPrototypeOf(b), true);
assert.equal(a.isPrototypeOf(c), true);
assert.equal(a.isPrototypeOf(a), false);
assert.equal(c.isPrototypeOf(a), false);
describe() {
return 'Person named '+this.name;
},
};
We have two objects that are very similar. Both have two properties whose names are
.name and .describe. Additionally, method .describe() is the same. How can we avoid
that method being duplicated?
We can move it to an object PersonProto and make that object a prototype of both jane
and tarzan:
const PersonProto = {
describe() {
return 'Person named ' + this.name;
},
};
const jane = {
__proto__: PersonProto,
name: 'Jane',
};
const tarzan = {
__proto__: PersonProto,
name: 'Tarzan',
};
The name of the prototype reflects that both jane and tarzan are persons.
PersonProto
describe function() {···}
jane tarzan
__proto__ __proto__
name 'Jane' name 'Tarzan'
Figure 26.4: Objects jane and tarzan share method .describe(), via their common pro-
totype PersonProto.
The diagram in fig. 26.4 illustrates how the three objects are connected: The objects at the
bottom now contain the properties that are specific to jane and tarzan. The object at the
top contains the properties that are shared between them.
When you make the method call jane.describe(), this points to the receiver of that
method call, jane (in the bottom left corner of the diagram). That’s why the method still
works. tarzan.describe() works similarly.
26.2 Classes 263
26.2 Classes
We are now ready to take on classes, which are basically a compact syntax for setting up
prototype chains. Under the hood, JavaScript’s classes are unconventional. But that is
something you rarely see when working with them. They should normally feel familiar
to people who have used other object-oriented programming languages.
class Person {
constructor(name) {
this.name = name;
}
describe() {
return 'Person named '+this.name;
}
}
The name of a named class expression works similarly to the name of a named function
expression.
264 26 Prototype chains and classes
This was a first look at classes. We’ll explore more features soon, but first we need to
learn the internals of classes.
Person Person.prototype
prototype constructor
describe function() {...}
jane
__proto__
name 'Jane'
Figure 26.5: The class Person has the property .prototype that points to an object that is
the prototype of all instances of Person. jane is one such instance.
The main purpose of class Person is to set up the prototype chain on the right (jane,
followed by Person.prototype). It is interesting to note that both constructs inside class
Person (.constructor and .describe()) created properties for Person.prototype, not
for Person.
The reason for this slightly odd approach is backward compatibility: Prior to classes,
constructor functions (ordinary functions, invoked via the new operator) were often used
as factories for objects. Classes are mostly better syntax for constructor functions and
therefore remain compatible with old code. That explains why classes are functions:
In this book, I use the terms constructor (function) and class interchangeably.
It is easy to confuse .__proto__ and .prototype. Hopefully, the diagram in fig. 26.5
makes it clear, how they differ:
There is one detail in fig. 26.5 that we haven’t looked at, yet: Person.prototype.constructor
points back to Person:
This setup also exists due to backward compatibility. But it has two additional benefits.
First, each instance of a class inherits property .constructor. Therefore, given an in-
stance, you can make “similar” objects via it:
Second, you can get the name of the class that created a given instance:
assert.equal(tarzan.constructor.name, 'Person');
class Foo {
constructor(prop) {
this.prop = prop;
}
protoMethod() {
return 'protoMethod';
}
get protoGetter() {
return 'protoGetter';
}
}
• .constructor() is called after creating a new instance of Foo, to set up that in-
stance.
• .protoMethod() is a normal method. It is stored in Foo.prototype.
• .protoGetter is a getter that is stored in Foo.prototype.
123
> foo.protoMethod()
'protoMethod'
> foo.protoGetter
'protoGetter'
class Bar {
static staticMethod() {
return 'staticMethod';
}
static get staticGetter() {
return 'staticGetter';
}
}
The static method and the static getter are used as follows.
> Bar.staticMethod()
'staticMethod'
> Bar.staticGetter
'staticGetter'
We’ll explore the instanceof operator in more detail later, after we have looked at sub-
classing.
• Classes are a common standard for object creation and inheritance that is now
widely supported across frameworks (React, Angular, Ember, etc.). This is an im-
26.3 Private data for classes 267
provement to how things were before, when almost every framework had its own
inheritance library.
• They help tools such as IDEs and type checkers with their work and enable new
features there.
• If you come from another language to JavaScript and are used to classes, then you
can get started more quickly.
• JavaScript engines optimize them. That is, code that uses classes is almost always
faster than code that uses a custom inheritance library.
• You can subclass built-in constructor functions such as Error.
That doesn’t mean that classes are perfect:
• There is a risk of overdoing inheritance.
• There is a risk of putting too much functionality in classes (when some of it is often
better put in functions).
• How they work superficially and under the hood is quite different. In other words,
there is a disconnect between syntax and semantics. Two examples are:
– A method definition inside a class C creates a method in the object
C.prototype.
– Classes are functions.
The motivation for the disconnect is backward compatibility. Thankfully, the dis-
connect causes few problems in practice; you are usually OK if you go along what
classes pretend to be.
this._action = action;
}
dec() {
this._counter--;
if (this._counter === 0) {
this._action();
}
}
}
With this technique, you don’t get any protection and private names can clash. On the
plus side, it is easy to use.
class Countdown {
constructor(counter, action) {
_counter.set(this, counter);
_action.set(this, action);
}
dec() {
let counter = _counter.get(this);
counter--;
_counter.set(this, counter);
if (counter === 0) {
_action.get(this)();
}
}
}
This technique offers you considerable protection from outside access and there can’t be
any name clashes. But it is also more complicated to use.
26.4 Subclassing 269
26.4 Subclassing
Classes can also subclass (“extend”) existing classes. As an example, the following class
Employee subclasses Person:
class Person {
constructor(name) {
this.name = name;
}
describe() {
return `Person named ${this.name}`;
}
static logNames(persons) {
for (const person of persons) {
console.log(person.name);
}
}
}
Two comments:
• Inside a .constructor() method, you must call the super-constructor via super(),
before you can access this. That’s because this doesn’t exist before the super-
constructor was called (this phenomenon is specific to classes).
• Static methods are also inherited. For example, Employee inherits the static method
270 26 Prototype chains and classes
.logNames():
Exercise: Subclassing
exercises/proto-chains-classes/color_point_class_test.mjs
Function.prototype Object.prototype
__proto__ __proto__
prototype
Person Person.prototype
__proto__ __proto__
prototype
Employee Employee.prototype
__proto__
jane
Figure 26.6: These are the objects that make up class Person and its subclass, Employee.
The left column is about classes. The right column is about the Employee instance jane
and its prototype chain.
The classes Person and Employee from the previous section are made up of several objects
(fig. 26.6). One key insight for understanding how these objects are related, is that there
are two prototype chains:
The instance prototype chain starts with jane and continues with Employee.prototype
and Person.prototype. In principle, the prototype chain ends at this point, but we get
one more object: Object.prototype. This prototype provides services to virtually all
objects, which is why it is included here, too:
In the class prototype chain, Employee comes first, Person next. Afterwards, the chain
continues with Function.prototype, which is only there, because Person is a function
and functions need the services of Function.prototype.
We have not yet seen how instanceof really works. Given the expression:
x instanceof C
C.prototype.isPrototypeOf(x)
If we go back to fig. 26.6, we can confirm that the prototype chain does lead us to the
following correct answers:
const p = Object.getPrototypeOf.bind(Object);
Fig. 26.7 shows a diagram for this prototype chain. We can see that {} really is an instance
of Object – Object.prototype is in its prototype chain.
272 26 Prototype chains and classes
null
__proto__
Object.prototype
__proto__
{}
Figure 26.7: The prototype chain of an object created via an object literal starts with that
object, continues with Object.prototype and ends with null.
null
__proto__
Object.prototype
__proto__
Array.prototype
__proto__
[]
Figure 26.8: The prototype chain of an Array has these members: the Array instance,
Array.prototype, Object.prototype, null.
This prototype chain (visualized in fig. 26.8) tells us that an Array object is an instance of
Array, which is a subclass of Object.
26.4 Subclassing 273
Lastly, the prototype chain of an ordinary function tells us that all functions are objects:
> p(function () {}) === Function.prototype
true
> p(p(function () {})) === Object.prototype
true
Object.prototype ends most prototype chains. Its prototype is null, which means it
isn’t an instance of Object, either:
> Object.prototype instanceof Object
false
The pseudo-property .__proto__ is implemented by class Object, via a getter and a setter.
It could be implemented like this:
class Object {
get __proto__() {
return Object.getPrototypeOf(this);
}
set __proto__(other) {
Object.setPrototypeOf(this, other);
}
// ···
}
That means that you can switch .__proto__ off, by creating an object that doesn’t have
Object.prototype in its prototype chain (see previous section):
> '__proto__' in {}
true
> '__proto__' in { __proto__: null }
false
274 26 Prototype chains and classes
class Person {
constructor(name) {
this.name = name;
}
describe() {
return 'Person named '+this.name;
}
}
const jane = new Person('Jane');
...
Person.prototype
__proto__
describe function() {···}
jane
__proto__
name 'Jane'
Figure 26.9: The prototype chain of jane starts with jane and continues with Per-
son.prototype.
Normal method calls are dispatched – the method call jane.describe() happens in two
steps:
• Dispatch: In the prototype chain of jane, find the first property whose key is 'de-
scribe' and retrieve its value.
func.call(jane);
This way of dynamically looking for a method and invoking it, is called dynamic dispatch.
You can make the same method call directly, without dispatching:
Person.prototype.describe.call(jane)
This time, we directly point to the method, via Person.prototype.describe and don’t
search for it in the prototype chain. We also specify this differently, via .call().
26.4 Subclassing 275
Note that this always points to the beginning of a prototype chain. That enables .de-
scribe() to access .name.
Direct method calls become useful when you are working with methods of Ob-
ject.prototype. For example, Object.prototype.hasOwnProperty(k) checks if this
has a non-inherited property whose key is k:
However, in the prototype chain of an object, there may be another property with the key
'hasOwnProperty', that overrides the method in Object.prototype. Then a dispatched
method call doesn’t work:
This pattern may seem inefficient, but most engines optimize this pattern, so that perfor-
mance should not be an issue.
The idea is as follows: Let’s say we want a class C to inherit from two superclasses S1 and
S2. That would be multiple inheritance, which JavaScript doesn’t support.
Each of these two functions returns a class that extends a given superclass Sup. We create
class C as follows:
We now have a class C that extends a class S2 that extends a class S1 that extends Object
(which most classes do, implicitly).
We implement a mixin Branded that has helper methods for setting and getting the brand
of an object:
The following code confirms that the mixin worked: Car has method .setBrand() of
Branded.
• The same class can extend a single superclass and zero or more mixins.
• The same mixin can be used by multiple classes.
26.5 FAQ: objects 277
Quiz
See quiz app.
278 26 Prototype chains and classes
Chapter 27
You are reading a preview version of this book. You can either read all essential chapters
online or you can buy the full version.
You can take a look at the full table of contents, which is also linked to from the book’s
homepage
279