CRC Quick Javascript
CRC Quick Javascript
CRC Quick Javascript
Are you a novice who wants to learn to program? This book is not for you.
In fact, this language is not for you. Get a good Python book.
If you program in C++ or Java, there are parts of the book you can skip over
because the JavaScript statements are exactly the same. These parts are
clearly marked.
by CRC Press
6000 Broken Sound Parkway NW, Suite 300, Boca Raton, FL 33487-2742
Reasonable efforts have been made to publish reliable data and information, but the author and
publisher cannot assume responsibility for the validity of all materials or the consequences of their
use. The authors and publishers have attempted to trace the copyright holders of all material
reproduced in this publication and apologize to copyright holders if permission to publish in this
form has not been obtained. If any copyright material has not been acknowledged please write and let
us know so we may rectify in any future reprint.
Except as permitted under U.S. Copyright Law, no part of this book may be reprinted, reproduced,
transmitted, or utilized in any form by any electronic, mechanical, or other means, now known or
hereafter invented, including photocopying, microfilming, and recording, or in any information
storage or retrieval system, without written permission from the publishers.
For permission to photocopy or use material electronically from this work, access
www.copyright.com or contact the Copyright Clearance Center, Inc. (CCC), 222 Rosewood Drive,
Danvers, MA 01923, 978.750-8400. For works that are not available on CCC please contact
mpkbookspermissions@tandf.co.uk
Trademark notice: Product or corporate names may be trademarks or registered trademarks and are
used only for identification and explanation without intent to infringe.
Description: First edition. | Boca Raton: CRC Press, [2023] | Series: Quick programing | Includes
bibliographical references and index.
Identifiers: LCCN 2022046807 | ISBN 9781032417578 (hbk) | ISBN 9781032417561 (pbk) | ISBN
9781003359609 (ebk)
DOI: 10.1201/9781003359609
Typeset in Minion
I finally escaped from industry and joined the Villanova faculty full time for
a few years, and then moved to the University of Pennsylvania, where I
directed a master’s program (MCIT, Masters in Computer and Information
Technology) for students coming into computer science from another
discipline.
I retired in 2017, but I can’t stop teaching, so I’m writing a series of “quick
start” books on programming and programming languages. I’ve also written
two science fiction novels, Ice Jockey and All True Value, and I expect to
write more. Check them out!
If you found this book useful, it would be wonderful if you would post a
review. Reviews, even critical ones, help to sell books.
To invoke strict mode for an entire program, put "use strict"; (complete with
quotes) at the first line of a program. To invoke strict mode for a single
function, put that string as the first line inside the function.
When running in a browser, JavaScript cannot do arbitrary file I/O, nor can
it communicate with web servers other than the one from which it came.
This is necessary for security; you do not want web pages to access your
files without your knowledge. Other environments, such as IDEs, may
allow these operations.
Chapter 1
Introduction
DOI: 10.1201/9781003359609-1
<!DOCTYPE html>
<html>
<head>
<title>Using JavaScript</title>
</head>
<body>
<script>
alert("Hello, World!");
</script>
</body>
</html>
This is not the best way to write programs. Browsers are designed to ignore
errors; after all, the user doesn’t want to see them. You, on the other hand,
want to see them and correct them. For this you need to open your
browser’s console. A console will display error messages (among other
things).
To open the console:
result = confirm(question);
– This displays the question in a modal dialog box with two
buttons: OK, to return true, and Cancel, to return false.
alert(arg)
– This is the simplest way to see a result. Calling alert with one
argument of any type will pop up a dialog box containing the
given value. There are two disadvantages: The dialog box is
modal, which means that it must be dismissed before computation
can continue; and you only get to see one message at a time.
console.log(args)
document.write(args)
document.writeln(args)
– This acts just like write, except that the method writes a newline
after the last argument. Since HTML ignores newlines, this is
fairly useless. To get a newline that isn’t ignored, write the string
"<br>".
The advantage of the alert and write methods is that they do not require
you to have a console open in order to view results.
Chapter 2
JavaScript
The Bare Minimum
DOI: 10.1201/9781003359609-2
This chapter and Chapter 3 describe JavaScript simply as a language, without reference to Web programming. It is
in two major parts:
The Bare Minimum—This section is intended to get you started programming in JavaScript as quickly as
possible. The best way to do that is to try things out as you go.
In More Detail—This goes over the same material again, filling in a lot of the gaps. To some extent, it can also be
used as a reference.
2.1 Comments
// introduces a comment that extends to the end of the line.
Inside a comment, // and /* have no special meaning (so you cannot “nest” comments). Inside a quoted string or
regular expression, // and /* do not start a comment.
A number may be written with or without a decimal point, but all numbers are stored as double precision
floating point.
A bigint is an integer with an arbitrarily large number of digits.
The two boolean values are written as true and false.
A string may be enclosed in either single quotes, double quotes, or backticks (`). There is no “character” type.
A symbol is a value that is guaranteed to be unique; no other value is equal to it. A symbol is created by
calling Symbol() or Symbol(name), where name is a (not necessarily unique) value that may be helpful in
debugging.
The undefined type has a single value, undefined. It is the value of a variable that has been declared but not
yet given a value.
The null type has a single value, null, meaning that the value does not exist.
An object is any more complicated data type. Functions, arrays, sets, maps, regular expressions, errors, and
dates are all special types of object, as are user-defined objects.
The type of a value can be determined by typeof, which can be used as either an operator, typeof x, or as a
function, typeof(x). It will return, as a string, one of the type names given above (for example, "number").
The value of typeof(null) is "object". This is generally regarded as an error in the design of the language.
For any type of object except a function, typeof will return "object". Applied to a function, typeof will
return "function".
If the name of a property is stored in a variable or can be computed, it can be accessed by the syntax
object[expression], for example, friend["given" + "Name"].
The test property in object, where property is given as a string, will return true if and only if object has that
property. A request for a property that does not exist will get the value undefined.
2.2.4 Arrays
An array is an ordered collection of values. An array literal can be defined by enclosing comma-separated values
in square brackets:
let ary = ["cat", "dog", "mouse"];
Commas can be used either between values or after values, so the above assignment is exactly equivalent to:
let ary = ["cat", "dog", "mouse", ];
Array indexing is zero-based; the first element of the above array is ary[0] and the last is ary[ary.length – 1].
This form is not generally recommended. One odd feature of it is that if the argument is a single numeric value,
Array(n), the result is a sparse array of n locations, not an array containing the single value n.
In a sparse array, no storage is allocated for the elements of the array until they have been assigned values;
unassigned locations will appear to have the value undefined. A sparse array can be arbitrarily large (only the
actual values in it contribute to its size), but it is slower to work with.
The length property of an array is always one more than the largest index. Since an array may be sparse, length is
not necessarily a count of the number of values in the array.
Arrays can be created from iterable types (types that can be stepped through).
2.2.5 Sets
A set is a collection of values, such that (1) no value appears more than once, and (2) the order in which the values
occur in the set is irrelevant.
Operations are:
new Set(iter) — Creates and returns a new set containing the values in the iterable object iter (often an
array). To create an empty set, omit iter.
If iter is a map, the result is a set of [key, value] arrays.
set.has(value) — Tests if value is a member of set.
set.add(value) — Adds value to set if it is not already present, and returns set.
set.delete(value) — If value is in set, it is deleted. delete does not return set; it returns true if value was
in set, and false otherwise.
set.clear() — Removes all values from set and returns undefined.
set.size — Returns the number of elements in set.
2.2.6 Maps
Maps are lookup tables. Each entry in a map consists of a key-value pair. Keys must be unique, but the same value
may occur multiple times.
Operations are:
Caution: The notations map.value and map[value] refer to map properties, not to map entries. Use set and get
to access the entries.
2.2.7 Dates
JavaScript has a Date object, which can be created in any of the following ways:
new Date() — Returns the current date and time in the browser’s time zone.
new Date(ms) — Returns the date that corresponds to the number of milliseconds since “epoch” (January 1,
1970).
new Date(string) — Converts string into a Date, if possible, and returns it. The International Standard
format is yyyy-mm-dd, but many other formats are recognized.
Caution: Without new in front of it, Date() returns a string, not a date, and is equivalent to String(new
Date()).
Internally, Dates are kept as milliseconds since epoch. This makes arithmetic on dates very easy because the
arguments to the various set methods are not limited to the same ranges as the get methods return. For example,
sets date to be a Date exactly 100 days in the future, with the correct month and year.
Regular expressions are largely standardized, so all but the most idiosyncratic patterns can be used in JavaScript.
In this section, we assume a knowledge of regular expressions and only describe how to use them in JavaScript.
For the reader unfamiliar with regular expressions, there is a brief discussion in Appendix D.
A regular expression is written inside forward slashes, and may be followed by flags. For example, /[a-z]+/gi is
the regular expression [a-z]+ with the global (g) and case-insensitive (i) flags. ([a-z]+ will match any sequence
of lowercase letters.)
Parentheses are used to group parts of the regular expression. Groups are numbered left to right, starting with 1; an
easy way to find the number of a group is to count only the left (opening) parentheses. Group 0 is the entire
matched part. For example, in the pattern
c[0] = "1-800-555-1212"
c[1] = "555-1212"
c[2] = "555"
c[3] = "1212"
string.replace(regexp, replacement) — Searches string for regexp and, if found, replaces it with
replacement. If the global modifier g is present, the method replaces all occurrences of regexp with
replacement.
If regexp contains parenthesized groups, then replacement may use $1 to represent the first group
matched, $2 to represent the second group matched, and so on.
regexp.exec(string) — Searches string for regexp and remembers its position in string.
This method is designed to be used in a loop. If the g flag is set, each call of exec will return the next
matching substring, or null when no more remain. After a null is returned, the regex is reset and can be
used again.
If the g flag is not set, exec behaves like match without the g flag set.
regexp.test(string) — Returns true if regexp matches somewhere in string, and null otherwise. The g flag
is handled the same way that it is for exec.
2.3 Identifiers
Identifiers consist of letters, digits, underscores, and/or dollar signs; the first character may not be a digit.
By convention, variable and function names begin with a lowercase letter, while class names begin with a capital.
The usual convention is to use “camel case” for multiword names, such as newUser. Constants are often written in
all capital letters.
Constants are declared with the word const and must be given a value at the time of declaration. Often the names
of constants are written with all capital letters. For example,
const zero = 0;
const RED = "#FF0000";
Using a const name = object; declaration prevents any later assignment to name, so name will always refer to the
same object, but it does not prevent changes to that object.
const card = {suit: "clubs", pips: 2};
card = {suit: "clubs", pips: 10}; // illegal
card.pips = 10; // legal
Variables declared with let or const at the top level of the program, not within any block, are global to the
program; they can be accessed anywhere.
Variables and constants declared within a block have block scope. A block is a group of statements and/or
expressions enclosed in braces, {…}. The scope of an identifier is that portion of the program in which the variable
is visible and can be used.
In other words, variables and constants declared in a block are visible and can be used only in the innermost block
in which they are declared. Outside that block, these variables and constants do not exist; their names can be
recycled for other uses.
It is an error to attempt to get the value of a variable that has been declared but not yet given a value.
2.5 var
The modern way to declare a variable is with let. The older way, using var in place of let, or simply assigning a
value to a variable, should be avoided. Variables declared the old way follow unusual, and generally undesirable,
scope rules.
It is not an error to use var to declare the same variable more than once in the same scope.
The parameters to a function, and any variables declared with var inside that function, are local to the
function. They may be used anywhere inside that function, even before the declaration. This is different from
block scope.
If a value is assigned to a new variable without declaring the variable with let, var or const, that variable
has global scope: It can be used anywhere in the program.
This can only be done in non-strict mode (sometimes called “sloppy mode”).
If a variable has been declared with var but not assigned a value, it has the special value undefined.
2.6 Operators
Here are the most common operators on numbers:
+
add
-
subtract or unary minus
*
multiply
/
divide, giving a floating-point result
%
remainder (modulus)
**
exponentiation
&&
and
||
or
!
not
<
less than
<=
less than or equal to
==
equal to
===
strictly equal to (equal and same type)
!=
not equal to
!==
strictly unequal (different types or values)
>=
greater than or equal to
>
greater than
Caution: When comparing values of different types, JavaScript attempts to convert them to numbers. Despite
the fact that undefined is converted to NaN and null is converted to 0, nevertheless undefined==null is true.
Don’t compare values if one or both of them could be null or undefined; check first using ===, which is
always safe.
Caution: As in almost all languages, it is unwise to compare two floating point numbers for equality. For
example, 11*(100/11) is not equal to 100, as these differ in the 14th decimal place. One potential workaround
is to use the method .toFixed(n), which will return a string with n digits after the decimal point, then do
string comparisons.
String concatenation:
+ concatenate (join together) two strings, or a string and any other value.
The arithmetic comparison operators can also be used on strings, giving lexicographic ordering. All capital letters
precede all lowercase letters, thus "Cat" < "cat".
Assignment:
= assignment operator—can be used as an expression; the value of the expression is the value that is
assigned.
JavaScript also has the + and - unary operators. The + operator, when applied to a non-numeric value (such as a
string), attempts to convert that value into a number, returning NaN (“not a number”) if it cannot.
?: The ternary operator: In the expression test ? valueIfTrue: valueIfFalse, the test determines which of the
two succeeding values is used.
If you are familiar with Java, most of the Java operators can also be used in JavaScript, including all of the bit-
manipulation operators. The bit-manipulation operators convert their operands to integers, do the operation on
integers, and convert the results back to (floating-point) numbers.
Order of precedence is the same as in most languages: Unary plus and minus are done first, then exponentiation,
then multiplication and division, then addition and subtraction, then assignment.
The “strict” tests === and !== will regard values as unequal if they are of different types. Unless you want the
automatic conversion, these operators should be preferred.
For objects (arrays, sets, etc.), the “equality” operators are actually tests of identity. That is, an object is “equal”
only to itself, not to any other object. The comparison [1] == [1] is false because two arrays are created, then
compared.
Assignment of objects does not create new objects. After the sequence a = [1]; b = a;, the test a == b will
return true. This is because a and b are now references to the same object. Changes made to the object from either
a or b will be visible to the other variable.
JavaScript has no built-in equality testing for objects; to do this, the objects need to be broken down into primitive
components. This requires significant amounts of code. The sites underscorejs.com and lodash.com both provide
large, well-tested libraries for this and other purposes.
2.8 Conversions
JavaScript performs most conversions (also called coercions) automatically. Here are some that might require
some care.
Any arithmetic performed on primitives will cause them to be converted to numbers. Strings of digits will be
converted in the obvious way. Boolean true will be converted to 1, null and false to 0, and undefined to NaN.
Exception: The binary + operator is both addition and string concatenation, but concatenation has priority; 2
+ "2" is "22". The unary operator + will attempt to convert its operand to a number, so 2 + +"2" is 4.
Any value in a boolean context (such as the condition of an if statement) will be treated as false if it is 0,
undefined, null, NaN, or the empty string. All other values, including "0", are considered to be true.
When an array is converted to a string, the result is a string of comma-separated values; the brackets are omitted.
Thus, a multidimensional array can readily be mistaken for a one-dimensional array.
2.9 Statements
Many statements in JavaScript are almost identical to those in Java. For the convenience of Java programmers,
these statements are described separately from statements unique to JavaScript.
2.9.1 Semicolons
Every JavaScript statement should end with a semicolon. If you are used to programming in Java or one of the C
languages, continue using semicolons the way you are used to.
Although each statement should be terminated by a semicolon, JavaScript uses a tool called ASI (Automatic
Semicolon Insertion), which will, up to a point, put semicolons at the ends of lines where appropriate. If you miss
a few semicolons it’s probably okay, but it’s unwise to depend on ASI to do this for you.
When a group of statements is enclosed in braces, {}, this forms a compound statement (or block). A semicolon is
not needed after the closing brace.
If a statement needs to extend over two lines, separate it in a place where the first line cannot be understood as a
complete statement. Good places to break a statement are after an operator or inside a parenthesized expression.
If a line begins with an opening parenthesis, opening bracket, or arithmetic operator, it will be taken as a
continuation of the previous line. If this is not your intent, end the previous line with a semicolon.
Semicolons are required to separate two or more statements on the same line.
2.9.2 Declarations
Variables in JavaScript may hold any type of value, so their type is not a part of their declaration.
let x;
let z = 26;
let a = 5, b = 10, c;
statements
Functions in HTML should normally be defined in the <head> element to ensure that they will be defined before
they are needed. Functions may be recursive. Functions may be nested within other functions.
Example definition:
function average(x, y) {
let sum = x + y; // x, y, and sum are local
return sum / 2;
}
Example call:
let avg = average(5, 10); // 7.5
Any variables declared within the function body are local to the function. The parameters are also local to the
function.
Functions may access variables in the environment in which the function occurs (unless there is a local variable
with the same name). All functions may access global variables. Functions declared within another function may
access the variables available at the point of declaration.
Function definitions are hoisted. This simply means that all function definitions are processed before any other
code is executed so that they need not lexically occur before calls to them can be made.
The value of a variable may be changed. For example, the assignment statement
x = "abc";
2.9.4.2 Expressions
Any expression can be used as a statement; the value of the expression, if any, is discarded.
Expressions are allowed to act as statements because they can have side effects. For example, if a is an array,
a.sort() sorts the array.
Expressions that return a result but do not have side effects can be used as statements, but they don’t do anything.
For example, using 2+2 as a statement is legal but useless.
This can lead to errors. If str is the string "Hello", the expression str.toUpperCase() returns the string "HELLO",
and this can be assigned to a variable, but using str.toUpperCase() as a statement does nothing.
A compound statement, or block, is some number (possibly zero) of declarations and statements, enclosed in
braces, {}. A compound statement is itself considered to be a statement.
Control statements, such as if statements and loops, control the execution of a single statement. If you want to
control more than just one statement, you must enclose those statements in braces to make them into a (single)
compound statement.
The body of a function or method must always be a compound statement. (Exception: “arrow” functions, to be
discussed later.)
Good style dictates that statements within a block be indented relative to the start of the block. The usual
indentation for JavaScript is two spaces.
2.9.4.4 If Statements
An if statement tests a condition. If the condition is true, the following statement (typically, a compound
statement) is executed. If the condition is not true, the if statement does nothing. The syntax is:
if (condition) {
statements
}
For example, the following if statement resets x to zero if it has become negative.
if (x < 0) {
x = 0;
}
An if statement may also have an else clause. If the condition is true, the statement following the condition is
executed. If the condition is not true, the statement following the word else is executed. Both statements are
typically compound statements. The syntax is:
if (condition) {
some statements
}
else {
some other statements
}
For example,
if (x % 2 == 0) {
x = x / 2;
}
else {
x = 3 * x + 1;
}
If either part contains only a single (non-compound) statement, the braces may be omitted. In this case, it is good
style to put the single statement on the same line as the if or the else.
if (x % 2 == 0) x = x / 2;
else x = 3 * x + 1;
A while loop is a loop with the test at the top. The syntax is:
while (condition) {
statements
}
First, the condition is tested; if it is false, nothing more is done, and the loop exits without ever executing the
statements. If the condition is true, the statements are executed, then the entire loop (starting with the test) is
executed again.
For example, the approximate common log (that is, log base ten) of a number x can be computed by:
let log = 0;
while (x > 1) {
x = x / 10;
log = log + 1;
}
The braces indicate a block of statements. If there is only one statement, the braces may be omitted; however, it is
good style to always include the braces.
Normally, the statements controlled by the loop must affect the condition being tested. In the above example, x is
compared to 1, and the controlled statements change the value of x. If the controlled statements never make the
condition false, then the loop never exits, and the program “hangs” (stops responding). This is a kind of error
commonly, if inaccurately, called an infinite loop.
Two additional statement types, break and continue, can also control the behavior of while loops. These
statements can be used with statement labels.
2.9.4.6 Do-While Loops
A do-while loop is a loop with the test at the bottom, rather than the more usual test at the top. The syntax is:
do {
statements
} while (condition);
First, the statements are executed, then the condition is tested; if it is true, then the entire loop is executed again.
The loop exits when the condition gives a false result.
This kind of loop is most often used when the test doesn’t make any sense until the loop body has been executed at
least once. For most purposes, the while loop is preferable.
For example, suppose you want to choose a random number between 0 and 1000 that is divisible by 7. You cannot
test the number until after you have chosen it, so do-while is appropriate.
let x;
do {
x = Math.round(1000 * Math.random());
} while (x % 7 != 0);
As with a while loop, an infinite loop will result if the exit condition is never satisfied.
The do-while loop is a little harder to think about than a while loop. Since we want a number that is divisible by
7, the loop has to test that the number is not divisible by 7.
Unlike other kinds of control statements, the braces in a do-while are required, even if only a single statement is in
the loop.
Variables declared within a block are local to that block. If the variable x is declared within the braces of the do-
while loop, it cannot be used in the condition, which lies outside of the block.
Two additional statement types, break and continue, can also control the behavior of do-while loops. These
statements can be used with statement labels.
2.9.4.7 Traditional For Loops
A for loop is a loop with the test at the top. The syntax is:
for (initialization; condition; update) {
statements
}
The initialization is performed first, and only once. After that, the condition is tested and, if true, the statements
are executed and the update is performed; then control returns to the condition. In other words, the for loop
behaves almost exactly like the following while loop:
initialization;
while (condition) {
statements;
update;
}
The word let, if used, must precede the first variable in the initialization.
The braces indicate a block of statements. If there is only one statement, the braces may be omitted; however, it is
good style to always include the braces.
As an example, an array can be declared and its contents written out by:
let ary = [3, 1, 4, 1, 6];
for (let i = 0; i < ary.length; i += 1) {
console.log(ary[i]);
}
Two additional statement types, break and continue, can also control the behavior of for loops. These statements,
which will be described shortly, can be used with statement labels.
Consider:
for (let variable = value; condition; update) { statements }
The scope of variable is the entire for statement; it is not accessible outside this statement. If the keyword let is
omitted, variable must have been declared previously; if let is replaced with var, variable is global.
When the loop body is a single statement, the braces are recommended but not required. In this case, it is good
style to write the entire loop on a single line.
for (let variable = value; condition; update) statement;
The “block scope” of variable is the entire for statement, just as if the braces were present.
Just as the if statement provides a choice between two blocks of code, based on a boolean value, the switch
statement provides a choice between several blocks of code, based on a value of any type.
Operation is as follows. The switch expression is evaluated, and then compared against each case expression in
order. When a case expression is found that is strictly equal to the switch expression, execution begins with the
following statements and continues until either a break or a return is encountered, or until the end of the entire
switch statement.
Note: The === operator is used for the equality test. This will return a true result only if the two values are the
same type as well as the same value.
The break statement is not required at the end of each case; if it is omitted, control will flow into the next group of
statements. This is seldom what you want to happen. On the rare occasion that this is the desired behavior, it is best
to include a comment that the omission is intentional, otherwise you or someone else may “correct” this apparent
problem at some later date.
One case expression: may be followed immediately by another case expression: with no intervening statements
(or break). If either expression matches the switch expression, the following statements will be executed.
The default case is optional and usually should come last. If no matching expression is found, the statements in
the default case are executed. If no matching expression is found and there is no default case, the switch
statement exits without doing anything.
It is good style to always include a default case, even if you believe that all possibilities have been covered.
The statements may be any sequence of zero or more statements. It is not necessary to use braces to group the
statements (including the following break statement) into a compound statement, although this is sometimes done.
This will result in the output 123 is a number, since the strict equality test makes no distinction between integers
and floating point numbers; they are both of type number.
Any statement may be labeled with an identifier, but it really only makes sense to label loop statements and switch
statements. Labels are used in conjunction with the break and continue statements.
A break statement consists of the keyword break optionally followed by a statement label.
Execution of the break causes the enclosing loop or switch statement to exit. If the break statement is within
nested loops and/or switch statements and does not have a label, only the immediately enclosing loop or switch
statement is exited.
Execution of a break statement with a label causes the enclosing loop or switch statement with that label to exit,
regardless of nesting level.
Given an array of numbers, consider the problem of finding two numbers such that one is exactly ten times the
other. The following code solves this problem.
let ary = [7, 30, 9, 20, 3, 5];
let i, j;
id: for (i = 0; i < ary.length; i += 1) {
for (j = 0; j < ary.length; j += 1) {
if (ary[i] == 10 * ary[j] && ary[i] != 0) {
break id;
}
}
}
console.log(ary[i] + ", " + ary[j]);
Some programmers dislike the break statement, and indeed, there is usually a better way to solve a problem
without using it.
2.9.4.12 Continue Statements
A continue statement consists of the keyword continue optionally followed by a statement label.
A continue statement can only be used within a loop. This is unlike a break statement, which can also be used
within a switch statement.
Execution of the continue causes the enclosing loop to return to the test, or in the case of a traditional for loop, to
the increment and then the test. Depending on the result, the loop may then continue or exit.
If the continue statement is within nested loops and does not have a label, control returns to the innermost loop. If
it does have a label, control returns to the indicated loop.
The following code computes the sum of the values in the array, excluding strings.
let ary = [3, true, 4, false, 10, "hello"];
let sum = 0;
for (let i = 0; i < ary.length; i += 1) {
if (typeof(ary[i]) == "string") continue;
sum = sum + ary[i];
}
This code sets sum to 18, not 17, because true is counted as 1 and false is counted as 0.
While there is nothing actually wrong with the continue statement, refactoring the code to remove it almost
always results in a simpler and more understandable program.
2.9.4.13 Return Statements
When a function is called, it typically executes some code and then returns some value to the calling location.
It can reach the end of the function body and return the special value undefined;
It can execute a return statement that has no following expression and return the special value undefined;
It can execute a return statement with an expression, evaluate that expression, and return the result as the
value of the function; or
It can throw an exception, in which case the value is irrelevant.
return;
or
return expression;
Functions that are called for their side effects rather than for their value are sometimes called procedures.
Although of very limited use, JavaScript does allow the use of an empty statement consisting of a semicolon by
itself. The following statements are equivalent:
for (n = 1; n < 1000; n = 2 * n) {}
and
for (n = 1; n < 1000; n = 2 * n);
The for/of loop can be used for arrays, sets, maps, and strings. It has the syntax
for (let element of object) {
statement;
}
The for/of loop can only be used to loop over iterable objects. User-defined objects are not iterable unless code is
added to make them iterable.
2.9.5.2 For/in
The for/in loop can be used for user-defined objects, arrays, and strings. It has the syntax:
for (let element in object) {
statement;
}
For objects, the elements are the (enumerable) property names; the value of a property can be accessed with
object[element].
For arrays, the elements are the indices, along with any properties that may have been assigned to the array;
the value can be accessed with object[element].
The for/in loop treats arrays like any other object. It is much slower than a for/of loop.
For strings, the elements are the indices; the individual characters can be accessed with object[element].
An object may have non-enumerable, “hidden” properties, and a sparse array may have “missing” elements. These
are skipped over by the for/in loop.
2.9.5.3 Throw
The programmer can use the throw statement to deliberately cause an exception. There is no “Exception” type—
any type of value may be “thrown.”
The throw statement “throws” the exception to somewhere else, so as not to clutter up the normal execution of the
program with code to handle (hopefully rare) exceptional conditions. The purpose of the try/catch statement
(described in Section 2.9.5.4) is to catch a thrown exception.
If an exception occurs in the try portion of a try-catch-finally statement, the catch part handles the
exception.
Otherwise, if an exception occurs in a function, the function will return immediately to the point at which the
function was called. Execution continues as if the exception was thrown at that point (which may involve
returning up another level in a nest of function calls).
Otherwise, if an exception occurs in top-level code, or hasn’t been handled by the time the exception reaches
the top level, it is up to the system to do something about it. In an IDE, the exception will be reported to the
programmer. In a browser, the exception will probably be ignored.
2.9.5.4 Try-catch-finally
The try-catch-finally statement is used to separate error handling code from code that handles the “normal” case.
This statement works exactly like the corresponding statement in Java, but with two minor syntactic differences.
Either the catch part or the finally part, but not both, may be omitted.
Execution is as follows:
For example, the following code will put up an alert box containing the words “I am an exception”:
try {
throw "I am an exception";
}
catch (v) {
alert(v);
}
Many things that would throw an exception in other languages do not throw an exception in JavaScript. In
particular, arithmetic expressions never throw an exception.
A minor complexity arises because JavaScript guarantees that the finally part, if present, will always be
executed. If the code in the try or catch part tries to execute a return, break, or continue statement, control will
pass immediately to finally part, and the return, break, or continue statement will be postponed until after the
finally part has finished.
It is also possible for an exception to occur in either the catch or the finally parts of a try-catch-finally
statement. The new exception will replace the one being handled and will be treated as a new exception.
2.9.5.5 The with Statement
The with statement is not allowed in strict mode, but you may see it in older code.
with object {
statements
}
This uses the object as the default prefix for variables; that is, any variable var within the statements is treated as
object.var, wherever this makes sense.
<body>
Here are some prime numbers:
<script>
for (let n = 2; n <= 100; n = n + 1) {
if (isPrime(n)) {
console(n);
}
}
</script>
</body>
</html>
There is a problem with the above code; if called with 1, the isPrime function returns true, indicating that 1 is a
prime number (it is not). Correcting this is left as an exercise for the reader.
2.11 Testing
2.11.1 The Mocha Test Framework
Thorough testing results in better code and a good testing framework can make testing relatively painless.
Together, Mocha and Chai provide a useful testing framework for JavaScript. Here we present a very brief
introduction for using these tools in testing code on an HTML page.
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/8.0.1/mocha.js"></script>
The following line isn’t absolutely necessary, but will provide much neater output:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/8.0.1/mocha.css">
The above URLs are long and don’t fit on a single line in this book, but you should write them in a single line in
your code. Also, these are current versions of Mocha and Chai as I write this, but you may wish to use more recent
versions.
We’ll talk about the actual tests in the next section. The tests can be put anywhere on the page, or they can be on a
separate .js file. We will use the latter approach.
There are two things to put in the body. First, you need the following line to specify where on the page to put the
test results:
<div id="mocha"></div>
The final step, which should be at or near the bottom of the HTML body, is code to run the tests:
<script> mocha.run(); </script>
Here are a few of the most useful tests in the chai object. They should be relatively self-explanatory.
assert(expression)
assert.isTrue(expression)
Same as assert(expression)
assert.isFalse(expression)
assert.equal(actual, expected)
Usually the actual is a function call, and the expected is what the function should return.
assert.notEqual(actual, expected)
assert.strictEqual(actual, expected)
assert.notStrictEqual(actual, expected)
assert.deepEqual(actual, expected)
Checks the contents of objects, not just identity
All of the above may take a message as an additional parameter. This is useful only if there is helpful information
to provide.
</head>
<body>
This text goes before the results.
<!-- Run the tests and put the result in the "mocha" div -->
<script>
mocha.run();
</script>
</body>
</html>
The test code can be put directly in the HTML page or loaded from a file. In the above, the test code is on a file
named isPrimeMochaTest.js. The contents of that file are as follows:
describe("isPrime", function() {
let assert = chai.assert;
it("Tests if n is a prime number", function() {
assert(isPrime(2));
assert.isTrue(isPrime(3));
let x = 4;
assert.isFalse(isPrime(x));
assert.equal(isPrime(5), true);
});
});
(Remember that <script> tags are not used in a .js file.). For the sake of example, several of the possible assert
tests are used, and an extra let statement is thrown in to demonstrate that ordinary JavaScript code, not just
assertions, can be included.
If all goes well, two results are expected. In the top right corner of the HTML page (put there by the CSS file)
should be something like
passes: 1 failures: 0 duration: 0.00s 100%
isPrime
√ Tests if n is a prime number
Chapter 3
JavaScript
In More Detail
DOI: 10.1201/9781003359609-3
JavaScript is a large, complex, and constantly evolving language. The preceding sections
have been a whirlwind tour of its main features. What is missing, however, is anything about
making interactive web pages—and that, after all, is the main reason to use JavaScript.
If you are eager to get to client-side JavaScript programming, you should now know enough
to jump ahead to Chapter 4. If you prefer to deepen your knowledge of JavaScript, continue
reading.
as the first thing in a script; to turn strict mode on for just one function, put that string as the
first line inside the function.
Assigning to a variable that has not been declared with var, let, or const.
The with statement.
Defining a function within a conditional or a loop statement.
Using eval to create variables.
Using delete to delete a variable, object, function, or an undeletable property (such as
prototype).
In strict mode, the keyword this inside a function refers to the object that called the function.
Caution: Turning on strict mode within a function makes it illegal for that function to
have default parameters, a rest parameter, or parameter destructuring.
3.2 Identifiers
Identifiers consist of letters (as defined by Unicode), digits, underscores, and/or dollar signs;
the first character may not be a digit. By convention, the names of variables, properties,
functions, and methods should begin with a lowercase letter; names of constructors should
begin with a capital letter. Names may be any length.
Case is significant in JavaScript but not in HTML. Since JavaScript names are frequently
used in HTML, this can lead to some strange errors if you are not careful about case.
3.3 Destructuring
Destructuring is a way to assign the parts of an object or array into several different
variables.
We may wish to assign the parts of this object into separate variables, as
let givenName = p.givenName;
let familyName = p.familyName;
let occupation = p.occupation;
Property names need not be given in order, and not every name has to be mentioned. Property
names that are not actually part of the object may be given; they will receive the value
undefined.
To work with a similar object of the same type, say p2, you cannot use let again because
variables may only be declared once.
let {givenName, familyName, occupation} = p2;
// SyntaxError: Identifier 'givenName' has
// already been declared
Neither can you simply omit the word let because starting with an open brace, {, indicates a
block.
{givenName, familyName, occupation} = p2;
// SyntaxError: Unexpected token '='
Default values may be given, in case the object lacks the given properties.
({givenName: name, country = "USA"} = p);
// name = "Sally", country = "USA"
When a function is called, values are assigned to its parameters, and these act like assignment
statements; therefore, destructuring may also be used in the parameter list.
Destructuring may also be used with arrays. Commas are used to indicate array elements that
are skipped over.
let a = [11, 22, 33, 44];
let [, x, , y] = a;
// x = 22, y = 44
To improve readability, numbers may include underscores, for example, one million may be
written as 1_000_000.
NaN (“Not a Number”) is the result of expressions such as 0/0 and "one"/"two".
You can write this as Number.NaN, or just NaN.
Despite the name, NaN is a number, and typeof(NaN) will return "number".
Arithmetic involving NaN results in NaN.
Infinity is the result of expressions such as 1/0.
1.7976931348623157e+308.
All of the above are considered to be numbers. No arithmetic expression will ever cause an
exception.
Caution: The function isNaN(value) returns true if value is NaN, but it also returns
true for any value that cannot be converted to a number. To test if the value of variable
v really is NaN, use the test v != v. This works because NaN is the only value that is not
equal to itself.
A bigint is an integer written with an n suffix, for example, 123n. Bigints can have an
arbitrary number of digits. All the usual numeric operations are available on bigints, except
for unary plus: +123n is illegal.
Bigints and (ordinary) numbers cannot be mixed in an arithmetic expression, but bigints and
numbers can be compared (using <, <=, etc.). To convert between the two types, use
BigInt(number) and Number(bigint).
A hexadecimal literal begins with 0x or 0X. An octal literal begins with 0o or 0O.
In strict mode, it is illegal to write a number with a leading zero (e.g. 0123), except for zero
itself. In nonstrict mode, some implementations will treat such numbers as octal and others
will treat them as decimal.
3.4.2 Strings
A string is a sequence of zero or more UTF-16 characters enclosed in either single quotes
('hello'), double quotes ("hello"), or backticks (`hello`). UTF-32 is not supported.
Quote marks of one type may be used within a string enclosed by a different type of quote
mark, for example, "Don't go.". Quote marks of the same type as the enclosing type must
be escaped (see below).
Strings enclosed in backticks are special. They may span several lines, and anywhere in the
string that ${expression} occurs, the expression is evaluated and becomes part of the string.
Strings like this are called template literals.
There is no “character” data type. Some characters that cannot be written directly in a string
can be “escaped,” that is, specified by a backslash followed by a letter or hex number.
Characters written in this way still count as single characters.
\0 NUL
\b backspace
\f form feed
\n newline
\r carriage return
\t horizontal tab
\v vertical tab
\\ backslash
\' single quote
\" double quote
\` backtick
\xDD Unicode hex DD
\xDDDD Unicode hex DDDD
The length property of a string is the number of characters in it. Escaped characters,
although written with more than one character, still count as a single character; so
"abc\n".length is 4.
Strings can be indexed, so string[n] is the nth character in string. However, strings are
immutable, so you cannot assign a new value to string[n].
The comparisons <, <=, ==, !=, ===, !==, >=, > can be used with strings. The comparison is
according to the Unicode values of the letters, with all uppercase letters preceding (less than)
all lowercase letters.
3.4.3 Booleans
A boolean has one of two values: true or false.
JavaScript also has “truthy” and “falsy” values. When used as a test, the following values are
considered false:
0 and 0.0
The empty string, "" or " or ``
undefined
null
NaN
3.4.4 Symbols
A symbol can be created with or without a description:
let sym1 = Symbol();
let sym2 = Symbol("secret");
A symbol is a unique identifier; it cannot be confused with any other identifier. Two symbols
created with the same description are still different from each other.
Objects have properties, and those properties can be modified or added to. Suppose you have
an object that is not one you created—it may be a built-in object, or one from someone else’s
code—and you want to add a property to it. The property is strictly for your own use; its
existence should not affect anyone else’s code. The solution is to use a symbol as the property
name.
Alternatively, suppose you have an object, and you want to add meta-information to the
object—that is, information that is about the object, but isn’t part of the object. Again, use a
symbol as the property name.
Here is a short map of distances from the sun for various planets:
let sun_dist = {"Venus": 108.2, "Earth": 149.6, "Mars": 227.9};
The distances happen to be in millions of kilometers, but that may not be obvious. Let’s add
that information.
let unit = Symbol("mkm");
sun_dist[unit] = "millions of kilometers";
We can recover that information by asking for sun_dist[unit], but it is not visible to most
code. In particular, the for/in loop will skip right over it.
will print
Venus = 108.2
Earth = 149.6
Mars = 227.9
When a string is needed, for example for printing, JavaScript will automatically convert
almost anything into its string representation. It does not do this for symbols. To get a string
representation of a symbol, you have to explicitly call the toString() method.
console.log(unit.toString());
will print
"Symbol(mkm)"
will print
"secret"
3.4.5 Arrays
Arrays in JavaScript do not have a fixed size; elements may be added to or removed from
either end of the array.
array.push(elems)—Returns the new length of array after adding elems to the end.
array.pop()—Removes and returns the last element in array and updates its length.
array.unshift(elems)—Returns the new length of array after adding elems to the
beginning of array.
array.shift()—Removes and returns the first element in array and updates the array’s
length.
The push and pop operations may be used to implement stacks efficiently.
The pairs push and shift, or pop and unshift, may be used to implement queues; all four
methods together may be used to implement deques.
Because the unshift and shift methods actually move the elements in the array, they are
much less efficient than push and pop.
A sparse array is one in which only locations containing actual values take up memory in the
computer; all other locations appear to contain the special value undefined. Sparse arrays
occupy less memory but take more time to process.
Write a literal array using consecutive commas, for example, ["cat",, "dog",,,].
The length of this array is five (not six); the last comma is taken as a trailing
comma.
To better understand sparse arrays, it helps to remember that undefined is an actual value in
JavaScript, and as such takes up actual space in memory. But if we ask for the value in some
location of a sparse array and get undefined, this could mean either (1) there is nothing in
that location, or (2) the location really does contain the value undefined.
This has some practical implications. The for/in loop will loop through all indices of an
array, whether there is something at that location or not. The for/of loop will only loop
through actual values (some of which may be undefined).
The method array.hasOwnProperty(i) will return true if array[i] contains an actual value,
false if it doesn’t.
3.4.7 Sets
JavaScript does not supply the usual operations on sets (union, intersection, difference,
symmetric difference). Using the spread operator (...), which turns an array into a sequence
of values, these operations can be implemented as follows.
function union(a, b) {
return new Set([...a, ...b]);
}
function intersection(a, b) {
return new Set([...a].filter(x => b.has(x)));
}
function difference(a, b) {
return new Set([...a].filter(x => !b.has(x)));
}
function symmetricDifference(a, b) {
return difference(union(a, b), intersection(a, b));
}
3.4.8 Maps
In many languages, it is undesirable to use mutable objects as keys. This is not an issue in
JavaScript, which compares keys for identity rather than equality. No matter what changes
are made to an object used as a key, it retains its identity; a different “equal” object cannot be
used to recover the associated value.
Caution: Every object literal is a unique object. If you use an object literal as a key, you
will never be able to “recreate” that key. You can, however, iterate over the map.
The for (let var of iterator) {…} loop is the easiest way to use an iterator.
3.4.9 WeakMaps
JavaScript uses automatic garbage collection: Space is allocated for objects when needed,
and reclaimed when those objects become inaccessible. Objects become inaccessible when
the program no longer has any way to refer to them.
As a trivial example,
let car = {make: "Suburu", year: 2018};
car = {make: "Toyota", color: "white"};
After the second assignment, there is no longer any way to access the object {make:
"Suburu", year: 2018}, therefore it can be garbage collected.
A WeakMap is like a Map—it matches keys to values. Here are the differences:
A key that is in a Map can never be garbage collected; but a key in a WeakMap can be garbage
collected (and the entry deleted from the WeakMap) if there are no other references to it.
This last is the most important point. Objects that have only a temporary existence can be
used as keys in a WeakMap, thus allowing garbage collection to do its job.
Garbage collection can happen at unpredictable times. Consequently, the state of a WeakMap at
any given time may be nondeterministic. The only allowable methods on a WeakMap are those
for which this will not be an issue.
3.4.10 Promises
If you have a long-running task whose results are not needed immediately, such as a file
transfer, you may wish to start it running asynchronously while your code continues to do
other things. A promise is code that runs asynchronously and “promises” to complete a task
at some later time.
The following assignment statement creates a Promise object, saves it in promise, and starts
the function executing:
let promise = new Promise(function);
Caution: Keep in mind the distinction between a function and a function call. If we
define function f(){}, then f is the function itself, while f() is a call to that function.
The argument to the Promise constructor must be a function.
There are three ways a promise can terminate: By reporting “success,” by reporting “failure,”
or by throwing an exception. To deal with this, the function used by the promise should have
two parameters, each of which is also a function (sometimes called a callback). The promise
code should call the first function to report success, or the second to report failure.
It is often convenient to use arrow functions (described later) to write the callback functions
of a promise. Like this:
new Promise((onSuccess, onFailure) => {code});
In this syntax, code is the (possibly long-running) code to be executed, onSuccess is a
function to execute if code is successful, and onFailure is a function to execute if the code
fails. Note especially that the arguments given to the parameters onSuccess and onFailure
must be functions; each of these functions may take one argument, or none.
The promise’s then method supplies the functions for the promise to use.
The following example uses three functions: yes and no just print their argument, while
doSomething chooses a random number and “succeeds” if the number is greater than 0.5, or
throws an exception if the number is smaller. (For simplicity, the example uses Math.random
instead of some task that might actually take a long time.)
function yes(r) {
console.log("yes, " + r);
}
function no(e) {
console.log("no, " + e);
}
Example results:
"END"
"no, 0.18252962330109224 is too low"
"END"
"yes, 0.938714265780423"
The result returned by then is a Promise. This allows promises to be “chained,” so that the
value returned by then can be fed into another then. The following example will create a
random number, then multiply it by 100, then add 1000, then display the result.
let promise2 = new Promise(
someFun => { return someFun(Math.random());
}).then(function(result) { return 100 * result;
}).then(function(result) { return 1000 + result;
}).then(function(result) { console.log(result);
});
The syntax does not allow extra arguments to be passed to a Promise; any additional
information it needs must be taken from the environment.
3.4.11 Conversions
JavaScript performs most conversions (coercions) automatically. You can also do explicit
conversions with the functions Number(x), String(x), Boolean(x), BigInt(x),
parseInt(x, base), and parseFloat(x).
Caution: If you use the word new with one of the functions Number, String, or Boolean,
the result will be a “wrapper object,” that is, an object containing the value, not a value
of the named type. This will mostly continue to work (except for a Boolean object,
which is always “truthy”).
All objects have a toString method, which can be called as object.toString(). It is usually
a good idea to override this method for your own objects.
otherwise it returns NaN. parseFloat ignores any following characters that do not belong
in a number.
variable.toFixed(d)—Converts the number in variable to a string with d digits after
the decimal point.
variable.toString(base)—Converts the number in variable to a string representation
of that number in the given base (2 to 36).
For both toFixed and toString, if a literal integer is used in place of the variable, two dots
are required—one as part of the number, the second to indicate a method call (for example,
123..toString(8) or 123.0.toString(8)).
Conversions can be made between plain objects, maps, and arrays of two-element arrays.
array = Array.from(map);
array = Object.entries(object);
map = new Map(array);
map = new Map(Object.entries(object));
object = Object.fromEntries(map);
object = Object.fromEntries(array);
3.5 Math
The Math class supplies the constants Math.E (e), Math.PI (π), Math.LN2 (loge2), and
Math.LN10 (loge10), among others.
The Math class also supplies the standard logarithmic and trignometric functions exp, log,
log10, log2, sin, cos, tan, asin, acos, atan, as well as the hyperbolic functions sinh, cosh,
tanh, asinh, acosh, and atanh. All angles are in radians.
JavaScript is unusual in that the set of reserved words keeps changing. It was defined with
more reserved words than were actually used, to allow for future expansion. Subsequently
some new words were reserved, and some were unreserved. Table 3.1 should be a reasonably
current list.
Table 3.1 JavaScript Keywords
arguments else in super
Operators with higher precedence (indicated by larger numbers) are performed before those
with lower precedence. For example, in the expression 2 * 3 + 4 * 5, the multiplications
are done before the addition because multiplication has higher precedence than addition. A
number of things not often thought of as operators, for example parentheses and array
indexing, also have precedence.
When operators have equal precedence, their associativity (“left to right” or “right to left”)
determines which operations are done first. For example, subtraction is left associative, so 10
- 5 - 3 means (10 - 5) - 3 rather than 10 - (5 - 3). Almost all binary operators (except
exponentiation) are left associative; the assignment operators are right associative, so a = b
= c + 5 means a = (b = c + 5) rather than (a = b) = c + 5.
Parentheses can be used to override the above rules and specify an explicit order of
evaluation. Parentheses are also used to show the order of evaluation when it might not be
obvious.
Table 3.2 lists the most important operators. The meaning of many of them should be
obvious; the less common operators will be explained as needed.
Table 3.2 JavaScript’s “Good” Operators
Operator Purpose Associativity Precedence
( ) Grouping n/a 21
obj . prop Property access Left 20
ary[index] Index into array Left 20
new type(args) Object creation n/a 20
fun(args) Function call Left 20
obj ?. prop Optional chaining Left 20
new type Object creation Right 19
! value Logical NOT Right 17
+ value Unary plus Right 17
- value Unary minus Right 17
typeof value Type (as string) Right 17
void value Treat as undefined Right 17
delete obj.prop Remove property Right 17
expr ** expr Exponentiation Right 16
expr * expr Multiplication Left 15
expr / expr Division Left 15
expr % expr Remainder Left 15
expr + expr Addition Left 14
expr - expr Subtraction Left 14
expr < expr Less than Left 12
expr <= expr Less or equal Left 12
expr > expr Greater than Left 12
expr >= expr Greater or equal Left 12
expr in expr Object has Left 12
property
expr instanceof type Object is type Left 12
expr == expr Is equal to Left 11
expr != expr Is not equal to Left 11
Operator Purpose Associativity Precedence
expr === expr Strictly equal Left 11
expr !== expr Not strictly equal Left 11
expr && expr Logical AND Left 6
expr || expr Logical OR Left 5
test ? expr : expr If-then-else Right 4
id = expr Simple Right 3
assignment
id op expr, where op is one of**= *= /= Stands for id = id Right 3
%= += -=&&= ||= ??= op expr
yield expr Coroutine exit Right 2
expr , expr Multiple Left 1
evaluation
The logical operators && and || are short-circuit operators. That is, if the result is known
from the first (left-hand) expression, the second (right-hand) expression is not evaluated.
Because JavaScript has “truthy” and “falsy” values, the && and || operators don’t necessarily
return either true or false, but may instead return a truthy or falsy value.
expr1 && expr2—If expr1 is falsy, the result is expr1, else the result is expr2.
expr1 || expr2—If expr1 is truthy, the result is expr1, else the result is expr2.
Given a sequence of values connected with &&, the result is either the first falsy value
encountered, or the final truthy value. Similarly, given a sequence of values connected with
||, the result is either the first truthy value encountered, or the final falsy value. This fact is
sometimes used in a “clever” way; for example, the expression x || 5 has the value 5 if x is
zero (or some other falsy value), otherwise it has the value of x.
The various assignment operators are operators; that is, they have a value and may be
embedded in a larger expression, for example, y = 3 + (x += 5).
The void expression operator evaluates expression but returns the value undefined.
The nullish coalescing operator, ??, has the value of its left operand if that operand is not
null or undefined, otherwise it has the value of its right operand. It behaves a lot like ||—
given a sequence of values connected with ??, the result is either the first “defined” (neither
null nor undefined) value, or the final value.
&& has a higher precedence than ||, and both have a higher precedence than ??.
Note: When ?? is used in an expression with either && or ||, parentheses must be used to
show the desired order of operations.
When used as a prefix, ++ adds one to its operand before using the value of the operand in an
expression. When used as a suffix, ++ uses the original value of the operand in the enclosing
expression, and adds one to the operand afterward. Similar remarks hold for the -- operator.
These operators should only be used as complete statements, or as the increment part of a for
loop; other expressions are too confusing. For example, the statement x = x++ does nothing.
As complete statements, x += 1 is at least as clear as x++ and doesn’t require that many more
keystrokes.
The bitwise operators (&, |, ^, ~, >>, >>>, <<) convert their operands to 32-bit integers—a
data type that JavaScript supposedly does not have—perform the operation, and convert
back. This works, but is slow.
The reader may have noticed that the comparison operators == and != are in both the “good”
and the “bad” tables. It is fine to use these operators with values of the same type. If the
operands of == or != are of different types, JavaScript first tries to convert them to the same
type. In most cases this works, but the rules are complex and confusing. The operators ===
and !== do no type conversion, so values of different types are unequal.
3.10 Functions
3.10.1 Defining Functions
JavaScript provides several ways to define functions. As an example, the function square
may be defined in any of these ways:
This uses the Function() constructor. The arguments to the function are given as
strings, and the final string is the function body.
This is a named function literal. The scope of the name sqr is the function block,
so it is available only within the function body, where it can be used to invoke the
function recursively.
This is an arrow function. If there is only one parameter, the parentheses may be
omitted. The single expression after the arrow is the result.
This is another arrow function, showing that braces may be used, in which case the
return statement is needed.
All functions have a name property.
If the function is created using the Function constructor, the name is "anonymous".
If a name immediately follows the word function, that is the name of the function.
If an unnamed function is immediately assigned to a variable, that variable becomes the
name of the function.
Otherwise, the name of the function is the empty string.
Function statements may be defined within other functions, but they should be at the top level
of the function, not within another statement such as a loop or if statement. Such functions
are local to the enclosing function:
function hypotenuse(x, y) {
function square(x) { return x * x }
return Math.sqrt(square(x) + square(y));
}
Primitive values (number, string, boolean) are passed to a function by value; objects are
passed by reference. What this means is that functions receive a copy of primitive values, so
there is nothing that can be done to them that will be seen by the calling program. For
objects, functions receive a reference to the actual object; any changes made to the interior of
the object will be seen by the calling program.
In other words, changing the value of a parameter within a function does not change the value
outside the function, but changing the properties of an object passed as a parameter does
change their values outside the function.
JavaScript does not require that a function be called with the same number of arguments as it
has parameters. Excess arguments are ignored, while missing arguments have the value
undefined.
In a function, the special variable arguments is an array-like object that holds all the
arguments the function was called with, regardless of how many parameters were used in the
function definition. It can be indexed like an array, or looped over with for loops, but lacks
most of the other capabilities of an array. The arguments variable is used mostly in older
code; it isn’t allowed in strict mode. Modern code is more likely to use rest parameters (see
below).
Parameters may be given default values; if the argument is missing, the default value is used.
Default values have the form of assignments, and previous parameters may be used in the
expression. For example,
function foo(a, b = 10, c = a * b) {
return a + b + c;
}
console.log(foo(3)); // result is 43
Because arguments are matched to parameters by position (first argument goes to first
parameter, etc.), all parameters without default values must precede all parameters with
default values.
A rest parameter is a parameter that collects all the remaining arguments into an array. A rest
parameter is designated by preceding it with three dots (...). For example, if a function is
defined with
function foo(x, y, ...z) {
console.log(`x is ${x}`);
console.log(`y is ${y}`);
console.log(`z is ${z}`);
}
Since the ... collects all the remaining arguments, it can only be used as the last parameter.
The dots can also be used in reverse, in the arguments to a function. When placed before an
array in a function call, the values in the array are separated into individual arguments. This is
useful for functions that take an arbitrary number of arguments.
let ary = [2, 7, 1, 3];
let m = Math.max(...ary);
Used in this way, the dots are called a spread. Spreads can also be applied to sets and maps.
Functions can be printed. The call square.toString() will return a listing of the function,
much as it appears above.
In this example, the sort method uses the function to determine how to compare elements of
myArray.
The length property of a function is the number of parameters in its definition. A rest
parameter (one preceded by ...) is not included in the count.
The name property of a function is, of course, its name. JavaScript is quite clever at
determining the name of a function. For example, given the definition
let square = (x) => x * x;
the function is defined as an anonymous literal function, but the assignment to the variable
square is enough to let JavaScript decide that the function is named "square".
Functions may be recursive; that is, they may call themselves. A simple example is
computing the factorial function:
let factorial = function f(n) {
if (n == 0) return 1;
return n * f(n - 1);
};
This example uses a named function literal. With this definition, the name f can be used
only within the function definition, while the name factorial can be used both inside and
outside the function.
Function properties are often a good alternative to global variables. For example,
nextInt.counter = 0;
function nextInt() {
nextInt.counter += 1;
return nextInt.counter;
}
The first call to nextInt will return 1, the second will return 2, and so on because the
function changes the value of its counter property. Also, note that because functions are
hoisted (processed first), the assignment to a function property can occur lexically before the
function definition.
At this point fn is a function, specifically the function age() {return year - this.year}.
It is no longer attached to an object, therefore it is no longer a method. Unfortunately, it still
refers to this, which has also lost its attachment to the original object.
There are three methods that can help solve the problem with this.
f.call(obj, arg1, ..., argN) — Calls the function or method f with the given
arguments, using obj as the value of this. If no object is required, obj may be null.
fn.call(car, 2021) — Returns 3. Note that the function fn can be applied to any
object with a year property.
f.apply(obj, args) — Does the same as call, but expects an array of arguments.
fn.apply(car, [2021]) — Returns 3.
let g = f.bind(obj, arg1, ..., argN) — Returns a new function g that uses obj as the
value of this, and with the first N arguments filled in.
let g = fn.bind(car) — Assigns to g a function that expects an argument for the
year parameter.
let h = bind(car, 2021) — Assigns to h a function that expects no arguments
and returns 3.
3.10.6 Closures
Suppose one function is defined inside another; call them the “outer function” and the “inner
function.” The outer function forms the environment of the inner function, so the inner
function can use the local variables of the outer function. If the outer function returns, all its
local variables will (normally) be recycled.
But what if the inner function continues to exist after the outer function vanishes? That can
happen—the (previously) inner function could be stored in a variable, or perhaps returned as
a result of the outer function. If the inner function is executed, it still needs access to the local
variables of the outer function.
The solution is that those local variables don’t get recycled—they are “closed over” and kept
for use of the (previously) inner function.
function make_counter() {
let n = 0;
let count = function() {
n += 1;
return n;
}
return count;
}
Here the make_counter function declares a local variable n, which is used in the count
function. The count function is then returned as a result of make_counter, but it still has
access to the storage location used by variable n, which is “closed over” by count. This is
termed a closure. (The name n is recycled and no longer accessible, but the storage it used is
not released.)
Each time make_counter is called it creates a new local variable n, independent of any
previous ones. In this way, multiple independent counters can be created.
It isn’t necessary to use nested functions to create closures. A function created in a block,
using variables of that block, can be put in a variable with a scope larger than the block. The
result is still a closure.
let v; // outside the block
{ let n = 0;
let count = function() {
n += 1;
return n;
}
v = count;
}
console.log(v()); // 1
console.log(v()); // 2
3.10.7 Generators
A generator is a special kind of function that remembers where it is at in the function, and
when used again, resumes from where it left off. This is useful for producing a (possibly
infinite) sequence of values.
To define a generator:
To use a generator:
Call the function defined with function*. This returns a generator object, not a value
computed by the generator.
Use the generator object in a for/of loop (not for/in, which will do nothing); or
Call the generator’s next() method as many times as desired. This returns an object as
described above.
A generator can only be used once. Once a generator is done, it’s done. You can
always make a new one, though.
As an example, we will write a very simple generator that takes an argument n and repeatedly
cuts it in half, rounding down to the nearest integer, until 1 is returned.
function* half(n) {
yield n;
while (n > 1) {
n = Math.floor(n / 2);
yield n;
}
}
gen = half(10);
let obj = gen.next();
while (! obj.done) {
console.log(obj.value);
obj = gen.next();
}
3.10.8 Iterators
An iterable is any object that can be stepped through, one element after another. An iterator
is an object that implements a method for stepping through the elements of an iterable.
Arrays, sets, maps, and strings are all iterable objects; the for/of statement uses an iterator to
step through them.
You can make an iterator for objects you create. The requirements are:
The object must have a property whose name is the system-defined Symbol.iterator.
The value of that property must be a method named next, which takes no arguments and
returns an object with the fields value and done.
If done is true, the iteration ends and value is ignored.
The syntax for creating an iterator can get quite confusing. One simplification is to define the
required next() method as a generator, since generators are a kind of function and return the
required kind of value.
Next we will add to this object an iterator that will produce the values start, start+step,
and so on, up to but not including end.
range[Symbol.iterator] =
function* next() {
n = this.start;
while (n < this.end) {
yield n;
n += this.step;
}
};
Notice the use of the word this. As mentioned earlier, when this is used in a method (a
function belonging to an object), it refers to the containing object.
In the above, an iterator was added to an existing object. It can also be added when an object
is created.
{start:12, step: 5, end: 30,
[Symbol.iterator]: function* next() {…} }.
3.11 Objects
3.11.1 Definition of Objects
JavaScript is an object-oriented language.
In JavaScript, you can create objects without having to first define a class. This section is
about objects; classes will be covered later.
An object is a collection of named values (called properties or fields). Objects use dot
notation, as in other object-oriented languages, but they behave more like hash tables (also
called maps or dictionaries).
You can write an object literal by enclosing key:value pairs in braces. For example:
This defines the object car with the properties make and year. You can refer to the fields with
dot notation. For example, the statement
console.log(car.make + " " + car.year);
will write "Subaru 2018" to the console. If you don’t have a console open, you won’t see the
result; in that case you may wish to use
alert(car.make + " " + car.year);
instead.
We can add properties to an existing object:
car.mileage = 25041;
or delete them:
delete car.mileage;
Property names (the “key” part of key:value) are always either strings (which do not need to
be quoted) or symbols. It may not look that way; the following is legal:
let nums = {2: "two", 3.1416: "pi"}
In this code, the keys are actually the strings "2" and "3.1416".
There is a second way to access properties: You can use brackets, [], instead of dot notation.
Instead of nums.2 or nums.3.1416, which are illegal syntax, you can say nums[2] and
nums[3.1416]. Any expression within the brackets will be evaluated and the result converted
to a string; so nums[5-3] is the same as nums["2"].
Brackets can also be used when creating or adding to an object. Continuing the above
example, either of the following two statements could be used to add the property mileage to
car, or update the value of mileage if it is already a property:
car.mileage = 25300;
car["mileage"] = 25300;
When creating an object, brackets can be used to compute the name of a property (the key),
the value, or both.
let property = "make";
let make = "Subaru";
let age = 2;
car = {[property]: [make], year: 2020 - age}
If you have some variables containing values and you want to create an object with the same
names and values, there is a very convenient shorthand you can use. Writing just a variable
name is the same as adding a property with that name and the variable’s value. For example,
if
let make = "Subaru";
let year = 2018;
then
let car = {make, year}
is shorthand for
let car = {make: "Subaru", year: 2018}
To test whether an object has a particular property, use the in operator. For example, "make"
in car returns true.
You can use the for/in loop to step through all the properties of an object. The code
Another way to create the car object is to first create a “blank” object, then add properties to
it:
let car = Object(); // or let car = {};
car.make = "Subaru"; // don't use 'let' here
car.year = 2018;
Yet another way to create this object is to write a function that assigns values to properties of
the keyword this, then explicitly returns this as a result.
Notice that the function uses the names make and year both as variables and as property
names. This is by no means necessary; we could have used different names for the variables
as for the properties. But it is convenient to be able to use the same names, rather than having
to think up synonyms.
There is yet another way to create a car object. Again we write a function, but this time we
do not explicitly return this as a result.
Because there is no explicit return statement, this function will return undefined if called in
the usual way. Nevertheless, we can use it to get a car object by putting the keyword new in
the function call.
let car = new Car("Subaru", 2018);
Putting new in front of a function call does two things. It creates a new local variable named
this inside the function, and it implicitly returns this as a result.
A function used in this way acts as a constructor for objects. It is conventional to capitalize
the first letter of a constructor (hence, Car rather than car). Constructors are used when it is
desirable to define a number of similar objects, for example, a number of car objects.
Note: By default, a constructor returns the newly created object this. A different object,
but not a primitive, may be explicitly returned by using the return statement.
Attempting to return a primitive value has no effect.
When an object is created by calling a constructor, the test object instanceof constructor
(for example, car instanceof Car) will test whether the object was created using that
constructor. This is in contrast to typeof(car), which will return the string "object".
The Object.create(proto) method uses an existing object proto as the prototype for a new
object. The newly created object has no properties of its own; it is “transparent,” in the sense
that any attempt to read its properties will “see through it” and read the properties of the
prototype.
let car2 = Object.create(car);
car2.make = "VW";
car2.color = "blue";
Following these statements, car will be unchanged, but car2 will have "VW" as its make,
"blue" as its color, and 2018 as its year.
If obj1 is a variable whose value is an object, then the assignment obj2 = obj1 copies the
reference into obj2, not the object itself. The result is that obj1 and obj2 both point to the
same object. Any changes to that object are equally visible from both variables.
There is no way in JavaScript to directly copy an object. However, there is a way to copy all
the properties of an object, or even several objects, into another object
let newObj = Object.assign({}, oldObj);
The Object.assign method takes an empty object {}, given as the first parameter, and copies
all the properties of the second parameter into it, returning the result.
In fact, the assign method is quite general. It can take any number of objects as parameters,
and copy the properties of all succeeding objects into the first object. It also returns the first
object as a result.
Object.assign(newObj, oldObj1, …, oldObjN);
This is a shallow copy: The keys and values of the properties are copied, but if a value is
itself an object, it is the reference to that object that is copied, not the object itself.
3.11.4 Methods
A method is a function attached to an object.
let now = new Date().getFullYear();
let car = { make: "Subaru",
year: 2018,
age: function() {
return now - this.year;
}
}
In the above, new Date() returns a Date object, and its method getFullYear() returns a
four-digit number.
The above car object has a property named year. It isn’t a variable; you can’t just say year,
you have to say which object it belongs to. The keyword this means “this same object.”
A method inside an object literal can be abbreviated by leaving out the colon and the word
function. Within car, age could be defined as follows:
let cust = {
name: "Jones",
address: { number: 29, street: "main" }
}
With this object you might say cust.address.number. This is an example of chaining.
To avoid this error, the optional chaining operator ?. can be used. It converts errors into the
undefined value. Hence, cust?.address?.number will either work or it will return
undefined, but it won’t cause an error.
3.11.6 This
You keep using that word. I do not think it means what you think it means.
—William Goldman, The Princess Bride
In JavaScript, the keyword this has a number of meanings. The first two given below are the
most common uses.
In a function,
In nonstrict mode, this refers to the global object. In a browser, the global object
is the Window object.
In strict mode, this has the value undefined.
In a function inside a method, this has the same meaning it does in a top-level
function; it does not refer to the object that owns the method.
In an arrow function, the meaning of this is the same as it would be if it were in the
surrounding context (not in the arrow function).
In an event handler, this refers to the HTML element that received the event.
The value of this is defined at run time, not at compile time. For example, the word this
may be used in a function, where it initially has the value undefined. Later, that function may
be stored as a method of an object, with the result that this refers to the object.
The global object isn’t always Window. In Node.js the global object is global, while in Web
Workers it is self. For code that will work in any of these environments, use globalThis to
refer to the global object.
The sort method takes a function of two arguments that returns a negative, zero, or positive
value if the first argument to that function is less than, equal to, or greater than the second
argument, respectively.
let nums = [12, 43, 115, 9, 65, 1001, 902];
This last example sorts numbers according to their one’s digit (the number modulo 10).
Higher-order methods can be used to replace many kinds of loops, resulting in shorter and
more readable code. For example, the map method will apply a function to every element of
an array, producing an array of results.
a = [1, 2, 3, 4, 5];
asq = a.map(e => e ** 2);
// asq = [1, 4, 9, 16, 25]
The filter method removes unwanted values from an array, returning a new, shorter array.
The reduce method applies the function pairwise to all elements, returning a single value.
The next example finds the sum of the numbers in an array.
a = [1, 2, 3, 4, 5];
asum = a.reduce((x, y) => x + y);
// asum = 15
3.11.8 Prototypes
Every object has a prototype, which is another object that “stands behind it,” or that it is
“based on.” If not otherwise specified, an object’s prototype is Object.
If we call myCar.toString(), we get the string "[object Object]". While this isn’t very
useful, it does show that myCar has a toString method. Where did it come from? It came
from Object, which is a prototype for all objects.
If we use a for/in loop to print myCar we will see its make and year, but will not see the
toString property of its prototype. The for/in loop only loops through the direct properties
of an object, not the properties inherited from its prototype.
proto.toString = function() {
return this.year + " " + this.make; };
When we ask for a property of an object, be it a field like make or a method like toString,
JavaScript first looks at the object itself. If it is found, that is what is used. If it isn’t a
property of the object, JavaScript looks for it in the object’s prototype. If still not found,
JavaScript looks in the prototype’s prototype, and so on all the way up to Object. Eventually
the property will be found or undefined will be returned.
Setting a property of an object sets the property on that object; it does not look at its
prototype. If we set myCar.wheels = 4, the Car object is unaffected. Getting a value from an
object will look up to its prototype if necessary, but setting a value never does.
3.11.9 Descriptors
Objects have properties and values, but the properties themselves have descriptors. The
descriptors (or boolean flags) of a property are:
enumerable — true if the property will be listed when we ask for the keys of an object,
or when we loop over the properties.
writable — true if the value can be changed.
When we loop over the properties of the car object, printing its keys and values, all of the
fields that we defined (make, year, toString) will be processed because all three are
enumerable by default.
Object.getOwnPropertyDescriptor(obj, property);
Object.defineProperty(obj, property, {flag: boolean, …});
If we don’t want the toString method to appear when we loop over the car object, we can
write
Object.defineProperty(car, "toString",
{enumerable: false,
configurable: false});
Setting enumerable to false keeps toFunction from being visible as a key of car (it still
exists and can be used). Setting configurable to false prevents enumerable from ever being
changed back to true.
A class describes a category of objects. It is a blueprint, or recipe, for making objects of that
type. Classes are a relatively new addition to JavaScript, and are always in strict mode.
Classes are based on prototypes, and provide little if any advantage over just using
prototypes.
A class has a constructor and may have some number of fields, getters, setters, and methods.
To describe these, we will continue with our “car” example.
class Car {
fuel = "gasoline"; // note: no "let"
constructor(make, year) {
this.make = make;
this.year = year;
}
}
The word new calls the constructor. Within the constructor, make and year refer to the
arguments of the constructor, while this.make and this.year are new fields belonging to the
object being constructed. The constructor can be used to create other Car objects with
different values.
Technical note: An object created from a constructor gets a constructor property, and
that property has a name property, so myCar.constructor.name is "Car".
With the above definitions, the fields fuel, make, and year of myCar can be addressed
directly, with myCar.fuel, myCar.make, and myCar.year. Sometimes, to provide a bit more
“protection,” a class can contain setters and getters. These are methods with the special
syntax
set name(value) {…} and
get name() {…}
A setter typically sets the value of a field. Setters are often used to check the legality of a
value before saving it. It may save the information in a form other than the form provided to
it (for example, miles may be converted to kilometers).
A getter typically returns the value of an object’s field. Alternatively, a getter may compute a
value in some other way and return it, for example, converting kilometers back to miles.
To use these setters and getters, no change in the code is involved. If a field has a setter, any
attempt to change the value of a field (for example, myCar.make = "VW") will invoke that
setter. Any attempt to read the value of a field will invoke its getter.
When a setter is defined for a variable, every attempt to set the value of that variable calls its
setter. This can be a problem. In particular, the following code would cause an “infinite”
recursion, as the setter calls itself.
set make(value) {
this.make = value; // bad!
}
The recursion can be avoided by saving the variable under some other name. Since
underscores are legal in variable names, one convention is to make a new name by
prepending an underscore. This is only a convention; there is nothing special about
underscores.
set make(value) {
this._make = value; // okay
}
The new variable _make must be used throughout the class definition, while the old variable
make should be used outside the class definition.
Getters and setters should normally be defined in pairs. Variables with setters must be stored
under some other name, while variables without setters are unaffected.
Caution: While the use of a setter can protect against accidentally changing a value, it is
no protection against malicious users. With the above setter for make, the information is
actually stored in a variable _make, and this variable is directly accessible.
A method is a function belonging to an object; and a class is a kind of object. Inside a class, a
method is written like a function, except that the word function is omitted.
class Car {
fuel = "gasoline";
constructor(make, year) {
this._make = make;
this._year = year;
}
set year(arg) {
if (arg < 1903 ||
arg > new Date().getFullYear()) {
alert("Bad year: " + arg);
}
else {
this._year = arg;
}
}
age() { // method
let now = new Date().getFullYear();
return now - this._year;
}
toString() {
return this._year + " " + this._make;
}
}
An assignment to myCar will create an object with the fields make and year. After this, the
test myCar instanceof Car will return true.
Caution: The word this is evaluated at run time, and depends on the context in which it
occurs. Calling myCar.age() directly works fine, but myCar.age is an incomplete
function with no fixed value for this. The solution is to replace the definition age()
{…} with age = () => {…}, which creates a new age function for each Car.
3.11.10.2 Inheritance
Every class except Object extends (adds information to) some other class—its superclass. If
no superclass is specified, as in Person, it defaults to extending Object.
A class may have any number of subclasses. A subclass builds upon (“extends”) its
superclass by adding or replacing features.
When you ask JavaScript to create a new Customer, it starts by creating a new Object; then it
then it adds to that object all the features of Person; finally, it adds all the features of
Customer. In this way, an object of a class is built “on top of” an object of its superclass.
Every class must have a constructor, and the constructor should begin by calling the
constructor for its superclass, with super(args). This isn’t necessarily the very first thing that
must be done, but it must happen before any use of this is attempted.
The spread operator (...) is used in the above both to collect an arbitrary number of
arguments into an array and to supply an arbitrary number from an array.
Since the above classes have constructors, we can create objects with new Person() and new
Customer().
Note: Although the classes Person and Customer have no properties or methods, they
are not necessarily useless. Features can be added to them later, by assigning to the class
prototype, for example, Person.prototype.name = "anonymous";.
An object created from a class is still just an object. You can, for example, add properties to
it.
let friend = new Person();
friend.name = "Sally";
Technical note: The instanceof test will test for membership not only in the immediate
class but also in all superclasses, so both friend instanceof Person and friend
instanceof Object will return true.
With this in mind, let us revise the above classes so that every Person has a name and every
Customer has an id. We will also provide a setter and a getter for a person’s name, but insist
that a name be a string.
class Person {
constructor(name) {
this._name = name;
}
set name(value) {
if (typeof value == "string") {
this._name = value;
} else {
alert(value + " is not a string.");
}
}
Note: Objects don’t have to be created from a class in order to have getter and setter
methods. The syntax, using the get and set keywords, is the same as that shown above.
Fields and methods defined in one class may be redefined, or overridden, in a subclass. For
example, the Object class defines a toString method. This method isn’t very useful because
it always returns "[object Object]", but it does exist.
You can override a method simply by defining a new method with the same name. For
example, we might add the following method to the Person class:
toString() {
return "My name is " + <uri xlink_href="http://this.name">this.name</uri>";
}
Note: In most browsers, the console.log(obj) method does not use toString; instead,
it prints the structure of the object. If this isn’t what you want, you can call toString
explicitly, or simply concatenate an empty string to the argument, console.log(obj +
"").
Fields can also be overridden. However, the word this always refers to “the object before the
dot.” For example:
class Over {
value = 100;
show() {
return "value is " + this.value;
}
}
This prints "value is 50". Although show() is defined in Over, the variable under is of type
Under, so this.value is 50, not 100.
If value were not defined in Under, then under would inherit it from Over, and this.value
would be 100.
As noted earlier, you can construct an object that uses an existing object as its prototype.
let newObj = Object.create(oldObj);
The newObj will not initially have any properties of its own, but properties can be added
later. Any attempt to look up a property of newObj will look first in newObj itself, but if not
found there, JavaScript will look in its prototype, oldObj.
The method Object.getPrototypeOf(newObj) will return oldObj, but there is also an older
way to access the prototype: newObj. __proto__ . The non-enumerable __proto__ property
should not be used in new code.
The object sally has access to the methods declared in Customer plus the methods declared
in Person (unless they are overridden by methods with the same name in Customer).
When you declare a function or create a class with a constructor, that function or class gets a
property confusingly named prototype. The value of this property is an object containing
function names and definitions.
Note: Do not confuse the prototype property of a class, class.prototype, with an
object’s prototype, obj.__proto__.
You can attach a method to all instances of a class by assigning to its prototype:
Person.prototype.initial = function() {
return <uri xlink_href="http://this.name">this.name</uri>[0];
}
The method initial will return the first character of a person’s name (assuming that Person
has a name property).
A transpiler “compiles” newer syntactic features, such as the ?? operator, into older but
equivalent syntax. Babel is a well-known transpiler.
A polyfill is a function or a library of functions that may or may not be currently included. A
missing function can be added directly to the code. A library of polyfills may be loaded from
a polyfill server, although this increases load time. There is more controversy about which
polyfill library to use, if any, but polyfill.io is reasonably popular.
3.13 JSON
JSON (JavaScript Object Notation) is a way of representing data as text. This is useful for
storing the data on a file, or transmitting it to or from a server. JSON is human-readable and
editable; it is quite similar to data in JavaScript.
Both JSON methods can be given additional parameters, not described here, to modify values
as they are processed.
Limitations:
Any object can be given a toJSON() method if the default representation is not satisfactory.
Chapter 4
Client-Side JavaScript
DOI: 10.1201/9781003359609-4
After a few introductory sections, the remainder of the book is divided into
two parts: Graphical User Interfaces, which is all that many JavaScript
programmers need, and Using the DOM, which describes the powerful
understructure of web pages.
Most tags are containers. They consist of a start tag, some contents, and an
end tag. The contents are the innerHTML of the tag.The syntax is:
Container tags can contain text and other tags, to any level.
A few kinds of tags are empty, that is, not containers. For example, <br> is
a line break, and <hr> is a horizontal rule. No end tag is needed. Such tags
are sometimes written as <tagName/>.
Start tags can contain attributes, which have the form name="value". One
such attribute is id, to assign a unique identifier to an individual tag.
JavaScript is case-sensitive, but tag names and attributes in HTML are not
case sensitive.
Originally, all styling was done in the HTML itself. Today, most styles are
applied by one or more associated stylesheets, written in CSS, Cascading
Style Sheets.
Function definitions are best placed in the <head> of the HTML document.
Scripts in the <body> section are executed in order as the page is loaded,
and typically produce output that is displayed at that point in the page.
Large amounts of JavaScript and scripts that are used on more than a single
page should be put in a file or files, not on the HTML page.
Because the nodes are in a tree structure, there are Node properties that
allow movement in the tree: parentNode, firstChild, nextSibling,
previousSibling, and lastChild.
Text and comments are represented by Text and Comment nodes, not
Element nodes, so they do not show up in properties and methods that
return only Elements. For example, an Element has a childNodes property
that is a collection of all its children, and a children property that is a
collection of only those children that are Elements.
In addition to the web page as described by the HTML, there are a large
number of events happening all the time. Every keystroke, every mouse
movement, and many other things cause events to occur. An interactive web
page “listens for” and responds to a small subset of these events.
4.4.1 Events
GUI programs are different from other programs. Instead of one continuous
sequence of code that runs from beginning to end, GUI programs are event-
driven. When an “interesting” event occurs, some code is triggered to
handle it. The code finishes, then nothing more happens until another
“interesting” event occurs.
An “interesting” event is simply one that the programmer has written some
code to handle. Events are happening all the time; practically anything that
happens generates one or more events. The mouse moves over an HTML
element? That’s an event. The mouse moves out of an HTML element?
That’s another event. In fact, if the mouse moves at all, that’s an event.
There are over 70 kinds of events, all with names beginning with “on,” such
as onclick.
The key point is that events are happening all the time; the programmer
doesn’t have to do anything to create them. If the user clicks a button, that’s
an event that (normally) should be handled. If the user clicks a word in a
paragraph, probably that event should be ignored. The programmer gets to
decide which events to handle.
4.4.2 Widgets
Widgets are GUI elements. Each is represented by a tag in HTML, and a
corresponding Element in the DOM. Typical widgets are buttons, text
fields, text areas, checkboxes, radio buttons, scroll bars, and a number of
other types.
Most GUI elements can be written using an <input> tag with a type
attribute. If type is omitted, text is assumed; this is a box allowing one line
of text to be entered.
Here are the possible values for type: button, checkbox, color, date,
datetime-local, email, file, hidden, image, month, number, password,
radio, range, reset, search, submit, tel (telephone), text, time, url,
week.
There are a few widgets that have their own tag, rather than being a value of
type: button, fieldset, label, option, optgroup, output, select.
4.4.3 Buttons
Buttons are active elements: When the user clicks a button, it should cause
something to happen. Moreover, it is only polite to provide a visual cue that
something has been done. For example, if the user clicks a Save button to
save a file, and nothing visible occurs, the user may well get frustrated and
click the button over and over again.
<button onclick="alert('Hello')">Click me
</button>
Each of these will appear on the web page as a button containing the words
Click me. Each of them, when clicked, calls the alert function with the
argument 'Hello'. The <button> tag requires a closing tag; the <input>
tag does not. The <button> tag is newer and more flexible; for example, its
innerHTML can be an image (using the <img>) tag, rather than just text.
<button onclick="alert('Ouch!')">
<img src="band-aid.png"></button>
The text on a button can be changed by JavaScript. For buttons defined with
an input tag, the button’s text is in its value attribute; for buttons defined
with the button tag, the button’s text is in its innerHTML.
There are two commonly used types of collection in the DOM. The
getElementsByTagName method returns a live HTMLCollection of elements,
while the childNodes property of a node is a live NodeList of nodes.
Both types of collection have a length property, and both can be indexed
like an array or (redundantly) by the item(index) method. The fact that
these are “live” means that they can be thought of as a window into the
DOM, rather than a snapshot. If the DOM tree is changed, for instance by
insertion or deletion of nodes, the contents and length of these collections
change correspondingly. All HTMLCollections are live, but some NodeLists
are static—they don’t change as the DOM tree changes.
Note: Technically, any element can be made active. You can put an
onclick property in a text field, and it will work; but there is almost
no situation in which this would be a good idea.
For text fields, there isn’t a <text> tag, just a variation on the <input> tag.
Here’s how to read what the user has entered. First, find the text field
(perhaps by using document.getElementById), then read its value entry.
<input id='tx1' type="text">
<br>
<input type="button"
value="Look in text field"
onclick="alert('You entered: ' +
document.getElementById('tx1').value)">
This works as you might expect. Enter something in the text field, click the
button, and it shows up in an alert message.
The handler for the button can then be written as a simple function call.
<input type="button" value="Look in text field"
onclick="showText('tx1')">
A form is a container, with start tag <form> and end tag </form>. The
purpose is to contain various widgets, such as buttons and text fields, but
any valid HTML can also be included. By default, enclosing HTML in a
<form> does not make any visible difference on the screen.
We have already described the standard button type, using either <button>
or <input type="button">. There are two other button types particularly
relevant to forms. Each has an old version (with <input>) and a newer form
(with <button>). For comparison, we show all three kinds of buttons here.
First, the old way:
<input type="button" value="Click me">
<input type="submit" value="Send it">
<input type="reset" value="Forget it">
The old way puts the text of the button in a value property, so it can only be
text. The new way puts it between <button> and </button>, so it can be
almost anything.
A reset button has the default text Reset, and the default action of
clearing everything on the form to its original values. This action tends
to annoy users.
One very convenient feature of forms is that they define a scope. Any
widget within a form can have a name property, and this name can be used
by other widgets in the same form. For example:
<form>
<input name='tx1' type="text">
<input type="button"
value="Look in text field"
onclick="alert('You entered: ' + tx1.value)">
</form>
There is seldom any need for more than one form on a page, so it can be
referenced by document.getElementsByTagName("form")[0], or by its id
if it has one. It is possible to have more than one form on a page, but forms
may not be nested; that is, you cannot have a form inside a form.
A submit button within a form will, when clicked, submit that form.
Outside a form, a submit button can use the attribute form="formId" to
submit the form with that id.
The two most commonly used values of the method attribute are "get" and
"post".
URLs are limited to 2048 ASCII (not Unicode) characters, so this limits the
amount of data that can be submitted. For larger amounts of information,
post must be used.
A post request is more secure than a get request because it is not visible in
the URL, not cached, and does not remain in the browser’s history.
There are a few other method request types, such as head to request just the
head of a page but not the body, and put to ask the server to store data.
It is up to the server which method requests it will accept and process. For
example, a server might accept get requests but not post requests.
Code on the server side may be written in almost any language. Server-side
coding is beyond the scope of this book.
Some additional input widgets are available for choosing dates and times,
but these are not yet supported on all browsers.
Many of these widgets give no indication to the user what they are intended
for, so they should always be accompanied by a label, preferably with one
that includes the for attribute.
4.4.11 Events
User actions (typing something, moving the mouse, clicking the mouse,
etc.) cause events to occur. One or more event handlers will be executed
when certain events occur.
Each form element (“widget”) can have attributes that act as event handlers.
For example, a button may be defined as follows:
<input type="button" onclick="save()" value="Save">
Here, onclick is an event handler that tells JavaScript what to do; in this
case, call the save() function when the button is clicked. Event handlers
should generally be implemented as function calls.
Numerous events can occur, and not every widget can respond to every
event. Browsers differ somewhat in which form elements will handle which
events. This section describes the events and which form elements should
be able to handle them.
Modern devices may have touch screens or pens. The above onmouse events
may be replaced with onpointer events which work equally well with both
mouse and touch events.
The following events can be handled by the body element and the input
and textarea widgets.
The following events are often handled in the enclosing <form> tag.
For example, the following code displays a button which, when clicked,
writes a message to console.log:
<input type="button" value="Wow!" id="bait"
onclick="console.log(event.type +
event.target.id);">
4.4.12 Bubbling
When an event happens on an element, the element can either handle it or
ignore it. Either way, the element’s parent element then gets a chance to
handle the event, then the parent’s parent, and so on, all the way up to the
root (the global object). This process is called bubbling, and is generally
desirable.
For example, if a button is clicked, and the button is in a form, and the form
is in a document, and the document is in a window, any or all of these
elements can do something in response to the button click.
event.target is the most deeply nested element, and the one that first
has a chance to respond.
event.currentTarget is the one currently handling the event (it is
equal to this).
Here are some of the properties of a Window object. With the exception of
location, all of these properties are read only.
function display(str) {
let e = document.getElementById("para");
e.innerHTML = str;
}
function google(str) {
console.log(window);
win2 = window.location =
"<uri>https://google.com</uri>" + "?q=" + str;
}
function googleIt() {
display("Going to Google in 5 seconds.");
let term =
document.getElementById("search").value;
timer = setTimeout(google, 5000, term);
}
function cancel() {
display("");
clearTimeout(timer);
}
When the HTML page is loaded, it shows a button labeled Google, a text
field, and a second button labeled Cancel.
When the first button is clicked, it calls the googleIt() function. That
function calls display to find the Element with the id “para,” which is an
initially empty paragraph, and sets it to contain the text “Going to Google in
5 seconds.” Then the googleIt() function finds the text field with the id
“search” and gets its text into a variable named term. Finally, googleIt
sets a timer that will call the google function with the argument term after 5
seconds (5000 milliseconds).
Note: This will only work if the browser permits it, which depends on
the browser’s settings.
If the Cancel button is clicked within five seconds, the cancel method will
erase the text in the paragraph whose id is "para", then cancel the timer
(whose id has been saved in the global variable timer). Consequently, the
timer event will not occur, and google() will not be called.
If the Cancel button is not clicked, the google function will try to open a
browser tab to google.com, with the search term passed to it as an
argument, and will save the window id in a global variable named win2.
The constructor new Document() will return a new HTML page, complete
with an empty head and body.
In order, these (1) set the background color to light gray, (2) set the text
color to a hard-to-read dark gray, (3) set the border around the entire
document to a thick blue line, (4) change the border color to green, (5) set
the font to Courier New if available, or Impact if Courier New is not
available, and (6) set the font size to 24 points.
The above examples used document.body, but any selectable Element can
be used. Again, the use of CSS is preferred.
document.getElementsByClassName(className) — Returns an
HTMLCollection of all the Elements that have the property
class="className".
Once nodes are created, they can be added to the document with Node
methods.
Note: Replacing the innerHTML of an Element will cause the new text
to be parsed as HTML, while replacing its textContent will result in
the new text being displayed as written.
Nodes can be added, replaced, or deleted. Here are some of the methods of
a Node:
4.5.4 Elements
Every tag in an HTML page is represented by an Element object in the
DOM tree, and every attribute in a tag is represented by an Attribute
object in the DOM. Modifications to a DOM object are immediately
reflected in the appearance of the HTML page.
4.5.4.1 Element Properties
element.innerHTML — The text between the start tag and end tag.
element.outerHTML — A string consisting of the start tag, the
innerHTML, and the end tag.
element.attributes — A NamedNodeMap (see below) of the attributes
of element.
An Attr is a Node that represents the attributes of an HTML tag. It has name
and value properties.
A document is a node, but not an element; this results in some duplication
of code within the DOM. The following methods are identical to methods
of document, but search within the element rather than within the entire
document.
element.getElementsByTagName(tagName)
element.getElementsByClassName(className)
element.querySelector(selector)
element.querySelectorAll(selector)
HTML text can be parsed and inserted into the DOM with the following
method:
4.5.5 CharacterData
There are three kinds of CharacterData: Text, Comment, and
ProcessingInstruction (not covered here). These have all the properties
and methods of Node, plus the following:
data — The text content.
length — The number of characters in the text.
nodeName — The string "#text".
appendData(data) — Adds data to the end of the text.
insertData(offset, data) — Inserts data into the text at the given
offset.
deleteData(offset, count) — Removes count characters from the
text, starting at the given offset.
replaceData(offset, count, data) — Replaces count characters
starting at offset with data.
substringData(offset, count) — Returns count characters from the
text, starting at offset.
remove() — Removes the node from the DOM tree.
Line 51 defines a button which, when clicked, will call the showTree
method with the document node.
At line 19, showTree begins the creation of a string s, starting with some
indentation and the tagName of the parameter.
Lines 27 through 35 check if the argument has attributes, and if so, adds
them to string s.
Lines 40 to 42 get the children of the argument node e, all of which are
Elements, and recur with some added indentation.
The history of the HTML DOM follows a similar trajectory. Standards are
currently maintained by the Web Hypertext Application Technology
Working Group, https://whatwg.org/; this is the ultimate source for
complete, detailed information. More approachable documentation for
HTML, CSS, and JavaScript can be found at MDN Web Docs,
https://developer.mozilla.org/en-US/.
array.sort(f2) — Sorts the array in place and also returns the sorted
array. If f2(x, y) returns a negative result, x is considered to be less
than y; if zero, equal; if positive, x is greater than y..
For example, a.sort((x, y) => x - y)) sorts the array a in
ascending numeric order.
array.forEach(f) — Calls the function f for each element of array.
To be useful, f should have side effects, since any values returned by f
are ignored.
array.find(f) — Finds and returns the first value in array for which f
returns a truthy value (or undefined if none is found)
array.findIndex(f) — Finds and returns the first index of a value in
array for which f returns a truthy value (or -1 if none is found).
array.map(f) — Applies f to each element of array, returning an array
of the results. Holes, if any, are preserved.
array.flatMap(f) — Applies f to each element of array. If the
resulting array is multidimensional, one level of nesting is removed,
promoting all values to the next higher level.
array.filter(f) — Returns an array containing all and only those
values of array for which f returns a truthy value.
Literals are characters that stand for themselves. Examples include letters,
digits, and whitespace characters. The regular expression/cat/will try to
match an occurrence of the word “cat.”
Metacharacters are characters that have special meaning in a regular
expression. The metacharacters are: \ | ( ) [ { ^ $ * + . ?
Escaped characters are characters that are preceded by a backslash in
order to use them as literals rather than as metacharacters or because they
are difficult to represent otherwise. For example, the question mark has a
special meaning in a regular expression so if you want it to mean just the
literal character, you have to escape it.: \?
A character class represents any one of the set of characters. There are several
predefined character classes:
\w A word character; same as [a-zA-Z0-9_]
\W A nonword character; same as [^a-zA-Z0-9_]
\s A whitespace character
\S A non-whitespace character
\d A digit; same as [0-9]
\D A nondigit; same as [^0-9]
(A period, not escaped) Any character except a line terminator
Other character classes can be described by using brackets. For example, the
character class [aeiouAEIOU] could be used to recognize vowels.
attributes, 110
automatic garbage collection, 62
Automatic Semicolon Insertion, 22
Babel, 107
backticks, 55
bad operators, 72
bigint, 8, 54
bind (function method), 80
bitwise operators, 72
blank object, 88
block, 18, 23, 27
block scope, 18
body tag, 2, 110
boolean, 8
break statement, 35
break , omission of, 34
browser wars, 147
bubbling, 129
button , 114
Dart, xvii
Date methods, 13
document.writeln function, 5
DOM, 111
dot notation, 85
Eclipse, 3
ECMAScript, xvii, 147
element methods, 140
element properties, 140
Element , in DOM, 112
elements, 140
email widget, 123
empty statement, 38
end tag, 109
entries (map function), 61
entries (object method), 67
enumerable (object property), 96
EPSILON , 54
equality tests, 21
escaped characters, 55
escaped characters in regex, 157
event handler, 115, 125
event-driven, 113
events, 112, 125
expression, 69
expressions as statements, 27
extending a class, 102
falsy, 56
fields of a class, 99
fields of an object, 85
file widget, 124
filter (array method), 95
flags, 96
Flanagan, David, xv
floating point comparison, 20
for/in loop, 39
for/of loop, 38
form, 113, 118
form submission, 121
form verification, 120
form, defining a scope, 119
frames, 130
from (array method), 67
fromEntries (object method), 67
function literal, 74
function methods, 79
function statement, 73
functions, defining, 24, 73
functions, properties of, 78
generator, 82
generator object, 82
get request, 121
getElementById (document method), 115, 135
getElementsByClassName (document method), 135
getElementsByTagName (document method), 115, 135
getOwnPropertyDescriptor (object method), 97
getPrototypeOf (object method), 95, 106
getters, of a class, 100
global object, 93
global scope, 18
Goldman, William, 92
google example, 132
Graphical User Interface, 113
GUI, 113
hexadecimal literal, 55
hidden widget, 125
higher-order methods, 151
hoisting, 24
HTML, xvii
HTML DOM, 111
HTML page, 109
HTML tag, 110
HTMLCollection , 116, 137
IDE, 1, 3
Identifiers, 17, 50
identity, 21
IEEE 754, 53
if statement, 28
iframes, 130
image widget, 124
immutable, 56
in operator, 87
infinite loop, 29
Infinity , 54
inheritance, 102
innerHTML , 109, 115
input (HTML tag), 114
instanceof , 89
Integrated Development Environment, 1
intersection (set function), 61
ISO 8601, 14
iterable, 83
iterable types, 10
Java operators, 20
JavaScript Object Notation, 108
JavaScript-specific statements, 38
JavaScript: The Definitive Guide, xv
JavaScript: The Good Parts, 72
JScript, 147
jsfiddle.net, 3
JSON, 108
key:value pairs, 85
keywords, 68
machine address, 90
make_counter example, 80
map (array method), 94
Map methods, 12
Maps, 11, 61
Math constants and functions, 67
MAX_SAFE_INTEGER , 54
MAX_VALUE , 54
meta-information, 57
metacharacters, 157
method, 77, 79, 91, 99, 101
MIN_SAFE_INTEGER , 54
MIN_VALUE , 54
Mocha, 43
modulus, 18
object, 8
object literal, 85
object properties, 85
objects, copying, 90
objects, creating, 85
objects, defining, 9
octal literal, 55
onEvent , 126–128
parameter, 75
parameters, with default value, 76
parse (JSON method), 108
parseFloat function, 66
parseInt function, 66
passive element, 116
password widget, 122
pattern attribute, 120
placeholder attribute, 120
pointer, 90
polyfill, 107
pop (array method), 59
post request, 122
precedence, 69
precedence, boolean operators, 72
preventDefault (event method), 129
prime number example, 42
Princess Bride, The, 92
procedures, 38
Promise , 63
Prompt function, 4
properties, 78
property:valuepairs, 9
prototype, 89, 95
prototype, class, 106
push (array method), 59
put request, 122
reference, 90
regexp (regular expression), 14
regexp methods, 15–16
regular expression, 14, 157
required attribute, 120
reserved words, 68
reset button, 118
rest parameter, 76
return statement, 37
right associative, 69
Safari, 3
scope, 18
scope in loops, 32
script tag, 2, 110
search widget, 122
selectors, node, 136
semicolons, 22
set, 11
set functions, 11
Sets, 60
setters, of a class, 100
shallow copy, 77, 90
shift (array method), 59
short-circuit operators, 71
showTree example, 142
slider (range) widget, 125
sloppy mode, 18
sort method, 94
sparse arrays, 10, 59
spread operator, 61, 103
stacks, 59
start tag, 109
statements, familiar, 25
static collection, 116
stopPropagation (event method), 129
strict equality, 20
strict mode, xvii, 49
strictly equal, 19
string, 8, 55
string methods, 153
stringify (JSON method), 108
stylesheets, 110
subclass, 102
Sublime Text, 3
submit button, 118
super (constructor call), 103
superclass, 103
switch statement, 33
symbol, 8, 57
Symbol.iterator , 83
symmetricDifference (set function), 61
undefined , 19
undefined type, 8
undefined , in sparse arrays, 60
underscore.js, 20
underscores in numbers, 53
Unicode, 50
Uniform Resource Locator, 121
union (set function), 61
unshift (array method), 59
URL, 121
URL widget, 123
use strict , xvii
var declaration, 18
Visual Studio, 3
Visual Studio Code, 3
void operator, 72
WeakMap , 62
Web Workers, 93
week widget, 123
WHATWG, 147
while loop, 29
widgets, 113
window example, 132
window methods, 131
window object, 129
window properties, 130
with s tatement, 50
wrapper object, 66
writable (object property), 96
yield statement, 82