Understanding Programming Through JavaScript
Understanding Programming Through JavaScript
Programming
through
JavaScript
by
Eric Roberts
Charles Simonyi Professor of Computer Science, emeritus
Stanford University
I also want to thank my colleagues at Stanford over the last several years, who
have helped make this book possible. My colleague Jerry Cain and I taught a pilot
version of a JavaScript-based introductory course in the spring of 2016-17. We
learned a great deal from that exercise and have integrated those insights and
understandings into the final version of this book. I want to express my profound
gratitude to Jerry who, in addition to being a wonderful colleague in the pilot
offering of the course, has been a tireless reader of this text through many drafts.
His comments have been amazingly thorough and have made the text far stronger. I
also owe considerable thanks to my colleagues Chris Piech, Keith Schwarz, and
Marty Stepp for their extensive contributions to the book. And I also need to
express my thanks to several generations of section leaders and the students in the
pilot versions of the course, all of whom have helped make it so exciting to teach
this material.
As always, my greatest thanks are due to my wife Lauren Rusk, who has again
worked her magic as my developmental editor. Lauren’s expertise has added
considerable clarity and polish to the text. Without her, nothing would ever come
out as well as it should.
vii
Contents
1 A Gentle Introduction 1
1.1 Introducing Karel 2
1.2 Teaching Karel to solve problems 4
1.3 Control statements 10
1.4 Stepwise refinement 17
1.5 Algorithms in Karel’s world 23
Summary 24
Review questions 26
Exercises 26
2 Introducing JavaScript 39
2.1 Data and types 40
2.2 Numeric data 41
2.3 Variables 44
2.4 Functions 49
2.5 String data 53
2.6 Running JavaScript in the browser 56
2.7 Testing and debugging 61
2.8 Software maintenance 69
Summary 71
Review questions 73
Exercises 76
3 Control Statements 79
3.1 Boolean data 80
3.2 The if statement 85
3.3 The switch statement 88
3.4 The while statement 90
3.5 The for statement 94
3.6 Algorithmic programming 100
3.7 Avoiding fuzzy standards of truth 109
Summary 109
Review questions 110
Exercises 112
Contents
viii
4 Simple Graphics 117
4.1 A graphical version of “Hello World” 118
4.2 Classes, objects, and methods 119
4.3 Graphical objects 123
4.4 The graphics window 133
4.5 Creating graphical applications 135
Summary 144
Review questions 146
Exercises 147
5 Functions 155
5.1 A quick review of functions 156
5.2 Libraries 160
5.3 A library to support randomness 163
5.4 The mechanics of function calls 170
5.5 Recursive functions 176
Summary 187
Review questions 188
Exercises 189
7 Strings 235
7.1 Binary representation 236
7.2 String operations 243
7.3 Classifying characters 249
7.4 Common string patterns 251
7.5 String applications 253
7.6 Reading from the console 260
Summary 262
Review questions 263
Exercises 265
ix
8 Arrays 273
8.1 Introduction to arrays 274
8.2 Array operations 281
8.3 Using arrays for tabulation 288
8.4 Reading text from files 292
8.5 Multidimensional arrays 294
8.6 Image processing 295
Summary 304
Review questions 305
Exercises 306
9 Objects 313
9.1 Objects in JavaScript 314
9.2 Using objects as maps 317
9.3 Representing points 321
9.4 Rational numbers 330
9.5 Linking objects together 338
Summary 342
Review questions 342
Exercises 343
11 Inheritance 387
11.1 Class hierarchies 388
11.2 Defining an employee hierarchy 391
11.3 Extending graphical classes 395
11.4 Decomposition and inheritance 399
11.5 Alternatives to inheritance 404
Summary 405
Review questions 406
Exercises 407
x
12 JavaScript and the Web 415
12.1 A simple interactive example 416
12.2 An expanded look at HTML 418
12.3 Controlling style using CSS 426
12.4 Connecting JavaScript and HTML 432
12.5 Storing data in the [Link] file 443
Summary 449
Review questions 451
Exercises 452
Index 455
xi
CHAPTER 1
A Gentle Introduction
In many schools today . . . the computer is being used to
program the child. In my vision, the child programs the
computer and, in doing so, both acquires a sense of
mastery over a piece of the most modern and powerful
technology and establishes an intimate contact with some
of the deepest ideas from science, from mathematics, and
from the art of intellectual model-building.
— Seymour Papert, Mindstorms, 1980
In the 1960s, Professor Seymour Papert at MIT used a language called LOGO to teach programming to
schoolchildren in the Boston area, who wrote programs to control a robotic turtle. The turtle could move
forward or backward, rotate a specified number of degrees around its center, and draw pictures on large
sheets of paper with a pen mounted on its underside. The LOGO turtle thereby became the first
programming microworld, designed to teach the basics of computation in a simplified environment.
2 A Gentle Introduction
Computer science is one of the most dynamic and vibrant fields of study in today’s
world. Most students recognize that obtaining a degree in computer science
provides outstanding career options. Fewer, however, realize that computer science
offers extraordinary opportunities for creativity, intellectual challenge, and finding
solutions to important problems facing the world.
To avoid overwhelming you with the intricacies inherent in those languages, this
book introduce programming in the context of a simplified environment called a
microworld. By design, microworlds are easy to understand and make it possible
for you to start programming right away. In the process, you will learn the
fundamental concepts of programming without having to master a lot of extraneous
details.
Many different microworlds have flourished over the years, including the Project
LOGO Turtle described briefly on the title page of this chapter. This book begins
by introducing a microworld called Karel that we have used with great success at
Stanford University for more than 30 years. Working with Karel enables you to
solve challenging problems from the very beginning. And because Karel
encourages imagination and creativity, you can have fun along the way.
Programming in Karel
Karel is a very simple robot living in an equally simple world. By giving Karel a
set of instructions, you can direct it to perform certain tasks within its world. Those
instructions constitute a program. Generically, the text that makes up a program is
called code. When you write a Karel program, you must do so in a precise way so
that Karel can correctly interpret the instructions. Every program you write must
obey a set of syntactic rules that define whether that program is legal.
The rules of Karel’s programming language are similar to those of other, more
sophisticated languages. The difference is that Karel’s programming language is
tiny—so small, in fact, that you can learn everything there is to know about the
Karel language in less than an hour. Even so, you will discover that solving a
problem in Karel’s world can be quite challenging. Solving problems is the essence
of programming. By learning the rules, you unlock the power of problem-solving.
Karel’s world
Karel’s world is defined by streets running from west to east and avenues running
from south to north. The intersection of a street and an avenue is called a corner.
Karel can only be positioned on a corner and must be facing in one of the four
standard compass directions (north, east, south, and west). In the following sample
world, Karel is facing east at the corner of 1st Street and 1st Avenue:
Several other components of Karel’s world can be seen in this example. The
gray, diamond-shaped object in front of Karel is a beeper. Rich Pattis describes
beepers as “plastic cones which emit a quiet beeping noise.” These noises are
audible only if Karel and the beeper are on the same corner. For example, Karel is
unaware of the beeper in the diagram as it appears and will discover the beeper only
if Karel moves forward to the next corner. The solid lines in the diagram are walls.
Karel’s world always has walls along the edges and may also contain internal walls.
Karel’s world is used to tell stories in which the geometry of the world can be
interpreted in different ways. In some cases, it is appropriate to view the world as
streets and avenues in a two-dimensional plane. In others, you need to forget about
streets and avenues and think about Karel’s world in a different way. In the
diagram shown in the example, it is easiest to imagine that you are seeing the world
from the side. Karel is standing on the ground, and the interior walls form a ledge
4 A Gentle Introduction
that Karel must surmount. Beepers, moreover, are used to represent any objects that
appear in the story. You just need to use your imagination.
Getting started
The first few steps in solving this problem are simple enough. You need to tell
Karel to move forward, pick up the beeper, and then move forward again to reach
the base of the ledge. The Karel simulator allows you to execute instructions by
typing them into an interactive window called the Karel console. The first three
steps in the program therefore look like this:
From here, Karel’s next step is to turn left to begin climbing the ledge. That
operation is also easy, because Karel’s set of built-in functions includes turnLeft.
Calling turnLeft at the end of the preceding program leaves Karel facing north on
the corner of 3rd Avenue and 1st Street. If you then call the move instruction, Karel
will move north to reach the following position:
The next thing you need to do is get Karel to turn right so that it is again facing
east. While this operation is conceptually as easy as getting Karel to turn left, there
is a slight problem: Karel’s language includes a turnLeft instruction, but no
turnRight instruction. It’s as if you bought the economy model only to discover
that it is missing an important feature.
At this point, you have your first opportunity to begin thinking like a
programmer. You have access to a set of Karel functions, but not exactly the set
you need. What can you do? Can you accomplish the effect of a turnRight
function using only the capabilities you have? The answer, of course, is yes. You
6 A Gentle Introduction
can turn right by turning left three times. After three left turns, Karel will be facing
in the desired direction. The next three steps in the program might therefore be
turnLeft()
turnLeft()
turnLeft()
Although turning left three times has the desired effect, it is hardly an elegant
solution. What you as the programmer want to say is
turnRight()
The only difficulty is that Karel doesn’t yet have a definition for the turnRight
function. To include turnRight in a program, you first have to teach Karel what
turnRight means.
Defining functions
One of the most powerful features of the Karel programming language is the ability
to define new functions. Whenever you have a sequence of Karel operations that
performs some useful task—such as turning right—you can give that sequence a
name. The operation of encapsulating a sequence of instructions under a new name
is called defining a function. The format for defining a function looks like this:
function name() {
statements that make up the body of the function
}
The first word in this pattern is function, which is used in both Karel and
JavaScript to define a function. Words that have a specific, predefined meaning in a
programming language are called keywords. To make them easier to recognize, the
program listings in this book display keywords in blue.
Once you have defined a function like turnRight, you can think of it as a new
built-in function, just like move or turnLeft. In a sense, defining a function is like
buying an upgrade for your robot that includes the missing operations.
1.2 Teaching Karel to solve problems 7
Instead of typing each instruction into the console, it makes sense to define a new
function that contains this sequence of instructions. You can then call that function
with a single name. In this book, functions that represent complete programs begin
with an uppercase letter and have names that describe their purpose as clearly as
possible. Figure 1-2 at the top of the next page defines a program-level function
named MoveBeeperToLedge together with the function turnRight.
Figure 1-2 also illustrates the technique of syntax coloring, which is the practice
of using different colors in a program listing to make it easy to recognize different
components of the program. In the program listings that appear as separate figures,
this book uses green to identify comments, blue to identify language keywords like
function, cyan for quoted strings, and black for the rest of the program text.
Depending on what editor you use to write your programs, the colors chosen for the
various program components may be different from the ones used in Figure 1-2.
Many editors will use additional colors to make further distinctions. For you as a
programmer, the primary value of syntax coloring is that it allows you to see
immediately where each program component starts and ends.
8 A Gentle Introduction
"use turns";
This statement asks Karel to use the turns library, which includes definitions for
the functions turnRight and turnAround. All the Karel examples in the rest of
this chapter use the turns library.
1.2 Teaching Karel to solve problems 9
Decomposition
Whenever you begin to solve a programming problem—no matter whether that
program is written in Karel or a more advanced programming language—your first
task is to figure out how to divide the complete problem into smaller pieces called
subproblems, each of which can be implemented as a separate function. That
process is called decomposition. Decomposition is one of the most powerful
strategies that programmers use to manage complexity, and you will see it again and
again throughout this book.
before after
Karel’s job is to fill each of the two potholes—the one on 2nd Avenue and the one
on 5th Avenue—with a beeper and then continue on to the next corner, ending up in
the position shown on the right.
Although you could solve this problem using the four predefined instructions,
you can use functions to improve the structure of your program. If nothing else,
you can use turnAround and turnRight to shorten the program and make its
intent clearer. More importantly, you can use decomposition to break the problem
down into subproblems and then solve those problems independently. You can, for
example, divide the problem of filling the pothole into the following subproblems:
1. Move one block forward to reach the first pothole on 2nd Avenue.
2. Fill the pothole by dropping a beeper into it.
3. Move three blocks forward to reach the second pothole on 5th Avenue.
4. Fill the pothole by dropping a beeper into it.
5. Move one block forward to reach the desired final position.
If you think about the problem in this way, you can use functions to ensure that the
program reflects your conception of the problem structure, as shown in Figure 1-3.
As with any programming problem, there are other decomposition strategies you
might have tried. Some strategies make the program easier to read, while others
10 A Gentle Introduction
only make the meaning more opaque. As your programming problems become
more complex, decomposition will turn out to be one of the most important aspects
of the design process.
Statements that affect the order in which a program executes instructions are
called control statements. Control statements fall into the following two classes:
Conditional statements
To give you a sense of where conditional statements might come in handy, let’s go
back to the pothole-filling program presented at the end of section 1.2. Before
filling the pothole in the fillPothole function, Karel might want to check
whether some other repair crew has already filled the hole on that corner with a
beeper. If so, Karel does not need to put down a second one. To represent such
checks in the context of a program, you need to use the if statement, which has one
of the following two forms:
if (conditional test) {
statements to be executed only if the condition is true
}
or
if (conditional test) {
statements to be executed if the condition is true
} else {
statements to be executed if the condition is false
}
12 A Gentle Introduction
The first form of the if statement is useful when you want to perform an action
only under certain conditions. The second is appropriate when you need to choose
between two alternative courses of action.
The conditional test shown in the first line of these patterns must be replaced by
one of the tests Karel can perform on its environment, listed in Figure 1-4. Like
function calls, tests include an empty set of parentheses, which are part of the Karel
syntax. Every test in the list is paired with a second test that checks the opposite
condition. For example, you can use the frontIsClear condition to check
whether the path ahead of Karel is clear or the frontIsBlocked condition to see
whether a wall is blocking the way. Choosing the right condition requires you to
think about the logic of the problem and see which condition is easiest to test.
You can use the if statement to modify the definition of the fillPothole
function so that Karel puts down a beeper only if there is not already a beeper on
that corner. The new definition of fillPothole looks like this:
function fillPothole() {
turnRight();
move();
if (noBeepersPresent()) {
putBeeper();
}
turnAround();
move();
turnRight();
}
1.3 Control statements 13
The if statement in this example illustrates several features common to all control
statements in Karel. The control statement begins with a header, which indicates
the type of control statement along with any additional information to control the
program flow. In this case, the header is
if (noBeepersPresent())
which shows that the statements enclosed within the braces should be executed only
if the noBeepersPresent test is true. The statements enclosed in braces represent
the body of the control statement.
function fillPothole() {
if (rightIsClear()) {
turnRight();
move();
if (noBeepersPresent()) {
putBeeper();
}
turnAround();
move();
turnRight();
}
}
As you can see from the spacing used in this example, the body of each control
statement is indented with respect to the statements that enclose it. The indentation
makes it much easier to see exactly which statements will be affected by the control
statement. Such indentation is particularly important when the body of a control
statement contains other control statements. Control statements that occur inside
other control statements are said to be nested.
Iterative statements
In solving Karel problems, you will often find that repetition is a necessary part of
your solution. If you were really going to program a robot to fill potholes, it would
hardly be worthwhile to have it fill just one. The value of having a robot perform
14 A Gentle Introduction
such a task comes from the fact that the robot could repeatedly execute its program
to fill one pothole after another.
Your mission is to write a program that instructs Karel to fill all the holes in this
road. Note that the road reaches a dead end after 11th Avenue, which means that
you have exactly five holes to fill.
Since you know from this example that there are exactly five holes to fill, the
control statement that you need is a repeat statement, which specifies that you
want to repeat some operation a predetermined number of times. The repeat
statement looks like this:
function FillFivePotholes() {
repeat (5) {
move();
fillPothole();
move();
}
}
The repeat statement is useful only when you know in advance the number of
repetitions you need to perform. In most applications, the number of repetitions is
controlled by the specific nature of the problem. For example, it seems unlikely that
a pothole-filling robot could always count on there being exactly five potholes. It
would be much better if Karel could continue to fill holes until it encountered some
condition that caused it to stop, such as reaching the end of the street. Such a
program would be more general in its application and would work correctly in
1.3 Control statements 15
either of the following worlds as well as any other world in which the potholes were
spaced exactly two corners apart:
To write a general program that works with any of these worlds, you need to use
a while statement. In Karel, a while statement has the following general form:
The conditional test is chosen from the set of conditions listed in Figure 1-4.
To solve the pothole-filling problem, Karel needs to check whether the path in
front is clear by invoking the condition frontIsClear. If you use the
frontIsClear condition in a while loop, Karel will repeatedly execute the loop
until it hits a wall. The while statement therefore makes it possible to solve the
somewhat more general problem of repairing a roadway, as long as the potholes
appear at every even-numbered corner and the end of the roadway is marked by a
wall. The following definition of the function FillRegularPotholes
accomplishes this task:
function FillRegularPotholes() {
while (frontIsClear()) {
move();
fillPothole();
move();
}
}
To change the program so that it solves this more general problem requires you
to think about the overall strategy in a different way. Instead of having a loop that
cycles through each pothole, you need to have the program call fillPothole at
every intersection along the roadway.
This strategic analysis suggests that the solution to the general problem might be
as simple as the following definition:
function FillAllPotholes() {
while (frontIsClear()) {
fillPothole();
move();
}
}
Unfortunately, the solution is not quite so easy. The program as written contains a
logical flaw—the sort of error that programmers call a bug. This book uses the bug
symbol on the right to mark functions that contain errors, to ensure that you don’t
accidentally use those examples as models for your own code.
The bug in this example turns out to be relatively subtle. It would be easy to
miss, even if you thought you had tested the program thoroughly. In particular, the
program works correctly on all the pothole-filling worlds you’ve seen so far and on
many that you haven’t. It fails only if there is a pothole in the very last avenue on
the street, as illustrated by the following before-and-after diagram:
before after
In this example, Karel stops without filling the last pothole. In fact, if you watch
the execution carefully, you’ll see that Karel never even goes down into that last
pothole to check whether it needs filling. What’s the problem here?
Following through the logic of the program carefully, you’ll discover that the
bug lies in the structure of the loop in FillAllPotholes, which looks like this:
while (frontIsClear()) {
fillPothole();
move();
}
1.4 Stepwise refinement 17
As soon as Karel finishes filling the pothole on 6th Avenue, it executes the move
instruction and returns to the top of the while loop. At that point, Karel is standing
at the corner of 7th Avenue and 2nd street, up against the boundary wall. Because
the frontIsClear test now fails, the while loop exits without checking the last
segment of the roadway.
The situation in Karel’s world has much the same structure. In order to fill potholes
in a street that is seven corners long, Karel has to check for seven potholes but only
has to move six times. Because Karel starts and finishes at an end of the roadway, it
needs to execute one fewer move instruction than the number of corners it checks.
Once you discover this bug, fixing it is actually quite easy. Before Karel stops at
the end of the roadway, all the program has to do is make a special-case check for a
pothole at the final intersection, as follows:
function FillAllPotholes() {
while (frontIsClear()) {
fillPothole();
move();
}
fillPothole();
}
The complete program appears in Figure 1-5 at the top of the next page.
The key to solving this problem is to decompose the program in the right way.
This task is more complex than the others you have seen, which makes choosing
appropriate subproblems more important to obtaining a successful solution.
1.4 Stepwise refinement 19
function CollectBeeperTowers() {
collectAllBeepers();
dropAllBeepers();
returnHome();
}
At this level, the problem is easy to understand. Even though you have not written
the code for the functions in the body of CollectBeeperTowers, it is important to
convince yourself that, as long as you believe that the functions you are about to
write will solve the subproblems correctly, you will have a solution to the problem
as a whole.
get to the final corner. The fact that you need to repeat an operation for each tower
suggests that you need to use a while loop.
But what does this while loop look like? First of all, you should think about the
conditional test. You want Karel to stop when it hits the wall at the end of the row,
which means that you want Karel to keep going as long as the space in front is clear.
The collectAllBeepers function will therefore include a while loop that uses
the frontIsClear test. At each position, you want Karel to collect all the beepers
in the tower beginning on that corner. If you give that operation a name like
collectOneTower, you can then write a definition for the collectAllBeepers
function even though you haven’t yet filled in the details. You do, however, have to
be careful. To avoid the fencepost problem described on page 17, the code must
call collectOneTower after the last cycle of the loop, as follows:
function collectAllBeepers {
while (frontIsClear()) {
collectOneTower();
move();
}
collectOneTower();
}
As you can see, this function has the same structure as the FillAllPotholes
function in Figure 1-5. The only difference is that collectAllBeepers calls
collectOneTower where the earlier one called fillPothole. These two
programs are each examples of a general strategy that looks like this:
while (frontIsClear()) {
Perform some operation.
move();
}
Perform the same operation for the final corner.
You can use this strategy whenever you need to perform an operation on every
corner as you move along a path that ends at a wall. If you remember the general
strategy, you can quickly write the code whenever you encounter a problem of a
similar form. Reusable strategies of this sort come up frequently in programming
and are referred to as programming idioms, or patterns. The more patterns you
know, the easier it will be for you to find one that fits a particular type of problem.
former case, you need to collect the beepers in the tower. In the latter case, you can
simply move on. This situation at first suggests that you need an if statement in
which you call beepersPresent to see whether a tower exists.
Before you add such a statement to the code, it is worth giving some thought to
whether you need to make this test. Often, programs can be made much simpler by
observing that cases that at first seem special can be treated in precisely the same
way as the more general situation. With the current problem, you might decide that
there is a tower of beepers on every avenue but that some of those towers are zero
beepers high. Making use of this insight simplifies the program because you no
longer have to test whether there is a tower on a particular avenue.
function collectOneTower() {
turnLeft();
collectLineOfBeepers();
turnAround();
moveToWall();
turnLeft();
}
Finishing up
Although the hard work has been done, a few loose ends still need to be resolved
because several functions are as yet unwritten. Fortunately, each of these functions
can easily be coded without any further decomposition. The function moveToWall,
for example, looks like this:
function moveToWall() {
while (frontIsClear()) {
move();
}
}
CollectBeeperTowers-p1
1.5 Algorithms in Karel’s world 23
Even before you have a chance to study algorithms in more detail, it is useful to
consider a simple algorithm in Karel’s domain. Suppose, for example, that you
want to teach Karel to escape from any maze that contains no loops. In Karel’s
world, a maze might look like this:
24 A Gentle Introduction
Karel’s job is to navigate the corridors of the maze until it finds the beeper marking
the exit. The program, however, must be general enough to solve any loop-free
maze, not just the one pictured here.
For any loop-free maze (and in fact for any maze in which no loop surrounds
Karel’s initial position), you can use a simple strategy called the right-hand rule, in
which you start by putting your right hand on the wall and then go through the maze
without ever taking your hand off the wall. Another way to express this strategy is
to proceed through the maze one step at a time, always taking the rightmost
available path. The program that implements the right-hand rule turns out to be
easy to implement in Karel and fits in a single function:
function SolveMaze() {
while (noBeepersPresent()) {
turnRight();
while (frontIsBlocked()) {
turnLeft();
}
move();
}
}
At the beginning of the outer while loop, Karel turns right to check whether that
path is available. The inner while loop then turns left until an opening appears.
When that happens, Karel moves forward, and the entire process continues until
Karel reaches the beeper marking the end of the maze.
Summary
In this chapter, you’ve had a chance to meet Karel, a very simple robot living in a
very simple world. Starting off with Karel makes it possible to learn the
fundamentals of programming without having to master the many complexities that
Summary 25
come with a full-scale programming language. The important points in this chapter
include the following:
"use turns";
• The best strategy for solving a large problem is to divide it into successively
smaller subproblems, each of which is implemented as a separate function. This
process is called decomposition or stepwise refinement.
• The Karel programming language includes control statements that fall into two
classes. Conditional statements allow you to execute other statements only if a
particular condition holds. Iterative statements allow you to repeat a sequence of
statements, either a specified number of times or as long as a condition holds.
• The syntactic rules for each of Karel’s control statements appear in the boxes to
the right.
26 A Gentle Introduction
• The conditions that Karel can test appear in Figure 1-4 on page 12.
• When you are using iterative statements, it is important to avoid the fencepost
error, which consists of failing to recognize that the number of move instructions
necessary to cover a distance is one fewer than the number of corners.
• In computer science, an algorithm is a solution strategy. Algorithms are one of
the most important topics in the field.
Review questions
1. In your own words, explain the meaning and purpose of a programming
microworld.
4. Define each of the following aspects of Karel’s world: street, avenue, corner,
wall, and beeper.
6. What are the two functions included in the Karel library named turns?
8. What control statement do you use to execute statements only if some condition
applies? What are the two forms of this statement?
9. What two statements does Karel offer for repeating a group of statements?
10. What condition would you use to test whether Karel can move forward from its
current position? What condition would you use to test whether there are any
beepers on the current corner?
Exercises
1. Only one of the two functions in the turns library is defined explicitly in this
chapter. Write a Karel function that implements the other.
Exercises 27
2. Suppose that Karel has settled into its house, which is the square area in the
center of the following diagram:
Karel starts off in the northwest corner of its house as shown in the diagram.
The problem is to program Karel to collect the newspaper—represented by a
beeper—from outside the doorway and then to return to its initial position.
This exercise is extremely simple and is intended mostly to get you started.
You can assume that every part of the world looks just as it does in the
diagram. The house is exactly this size, the door is always in the position
shown, and the beeper is just outside the door. Thus, all you have to do is write
the sequence of statements necessary to have Karel perform the following
tasks:
Even though the program requires just a few lines, it is still worth getting at
least a little practice in decomposition. In your solution, decompose the
program so that it includes a function for each step shown in the outline.
3. Implement a Karel function named backup that has the effect of moving Karel
backward one square but leaves it facing in the same direction. For example,
the following before-and-after diagram shows the effect of calling backup if
Karel is facing east at the corner of 1st Street and 2nd Avenue:
before after
If Karel’s back is against a wall, the backup function should fail when Karel
tries to execute the move instruction that is part of the implementation.
28 A Gentle Introduction
4. Write a program that teaches Karel to climb a mountain exactly like this:
5. Generalize the program you wrote in exercise 3 so that Karel is able to climb a
stair-step mountain of any height. Thus, in addition to climbing the mountain
in that exercise, it should be able to scale a molehill like
Exercises 29
For those who live in colder climates, winter can be a bitter time. The trees
have lost their leaves and stand as monuments to the ravages of the season, as
shown in the following sample world:
In this sample world, the vertical wall sections represent barren tree trunks.
Karel’s job is to climb each of the trunks and adorn the top of each tree with a
cluster of four leaves arranged in a square like this:
30 A Gentle Introduction
Thus, when Karel is done, the scene will look like this:
The situation that Karel faces need not match exactly the one shown in the
diagram. There may be more trees; Karel simply continues the process until
there are no beepers left in the beeper bag. The trees may also be of different
heights or spaced differently than the ones shown in the diagram. Your task is
to design a program that is general enough to solve any such problem, subject
to the following assumptions:
• Karel starts at the origin facing east, somewhere west of the first tree.
• The trees are always separated by at least two corners, so that the leaves at
the top don’t interfere with one another.
• The trees always end at least two corners below the top, so that the leaf
cluster will not run into the top wall.
• Karel has just enough beepers to outfit all the trees. The original number of
beepers must therefore be four times the number of trees.
• Karel should finish facing east at the bottom of the last tree.
Think hard about what the parts of this program are and how you can break it
down into simpler subproblems. What if there were only one tree? How would
that simplify the problem, and how can you use the one-tree solution to help
solve the more general case?
7. Suppose that it’s Halloween and Karel is going Trick-or-Treating. Karel starts
off at the west end of a dead-end street that contains houses on both sides of
street, such as the one pictured in the following diagram:
Exercises 31
Each house has a front porch at some point along its front side. Karel’s mission
is to go to each house, step into the porch area, and see if the porch contains a
treat, represented by a beeper. If it does, Karel should pick up the treat. If not,
Karel should move on to the next house. Karel must check every porch on both
sides of the street and should end up at the original intersection, facing in the
opposite direction. Thus, after executing your program in the world shown
above, Karel should finish in the following position:
• Karel starts at the west end of a street, facing east, with an empty beeper
bag.
• The houses on the street are packed closely together, with no space between
adjacent houses.
• There may be any number of houses, which typically vary in size.
• The side of each house facing the street is a solid wall except for a small
porch, which is always one intersection wide. The porch can appear at any
point in the front wall.
32 A Gentle Introduction
8. Now that Karel has mastered Halloween, it’s time to celebrate a different
holiday. Karel has decided to deliver beeper valentines to every student in an
elementary school class that is using Karel to learn about programming. Karel
does not remember exactly how many desks are in each horizontal row but does
remember that there are precisely three rows of desks and that the classroom
looks something like the one in the following diagram:
• Karel starts at 1st Avenue and 1st Street, facing east, with an infinite number
of beepers in its bag.
• There are exactly three rows of student desks, positioned as shown in the
diagram, just to the south of 3rd, 5th, and 7th Streets.
• Karel does not know how many desks are in each row (which may differ),
how many blank spaces there are between the desks, or how many spaces
exist between the desks at the ends of each row and the walls of the
classroom. What Karel does know is that each of the desks is exactly one
unit wide and that there are no desks right up against the wall.
When Karel is finished, all the desks in the room should have a valentine, as
shown in the following diagram:
Exercises 33
To complete the paint-by-numbers task, all Karel has to do is walk from left to
right across each street, pick up each pile of beepers, and then redistribute the
beepers from that pile, one at a time, on each successive corner.
34 A Gentle Introduction
To get a sense of how this process works, consider what happens when Karel
gets to 11th Street. At the beginning of the row, Karel is standing on the first
corner with an empty beeper bag, as follows:
Karel then walks down the street and finds the pile of three beepers on 4th
Avenue. When Karel gets to that corner, it picks up the beepers. This step
leaves Karel in the following position with three beepers in its bag:
From here, the next step is to put the beepers down, one at a time, starting with
the corner in which the pile was found. Executing this step leads to the
following configuration, where Karel again has an empty beeper bag:
Karel then repeats this process for the second beeper pile, ending up in the
following position at the end of the row:
Your job is to write the program that converts a paint-by-number picture of this
sort into the corresponding completed masterpiece. In writing the program,
Karel can count on the following facts about the world:
• The world contains an arbitrary number of beeper piles but no interior walls.
• The beeper piles never have so many beepers that they cause Karel to run
into a wall or another beeper pile.
• Karel always starts facing east in the southwest corner (1st Street and 1st
Avenue) with an empty beeper bag.
• Karel must finish execution facing east at the northeast corner of the world.
10. More than a decade after Hurricane Katrina, considerable damage remains
along the Gulf Coast, and some communities have yet to rebuild. As part of its
plans to improve the nation’s infrastructure, the government has established a
program named Katrina Automated RELief (or KAREL) to send house-building
robots to repair the damaged area. Your job is to program those robots.
Each robot begins at the west end of a street that might look like this:
Each beeper in the figure represents a pile of debris where a house once stood.
Karel’s job is to walk along the street and build a new house in the place
marked by each beeper. Those houses, moreover, need to be raised on stilts to
avoid damage from the next storm. Each house should look exactly like this:
The new house should be centered where the bit of debris was left, which
means that the first house in the diagram above would be constructed with its
left edge along 2nd Avenue.
36 A Gentle Introduction
At the end of the run, Karel should be at the east end of the street, having
created a set of houses that look like this for the initial conditions shown:
In solving this problem, you can count on the following facts about the world:
• Karel starts off facing east at the corner of 1st Street and 1st Avenue with an
infinite number of beepers in its beeper bag.
• The beepers indicating the positions at which houses should be built will be
spaced so there’s room to build the houses without overlapping them or
hitting walls.
• Karel must end up facing east at the southeast corner of the world.
Moreover, Karel should not run into a wall if it builds a house that extends
into that final corner.
11. In this exercise, your job is to get Karel to create a checkerboard pattern of
beepers inside an empty rectangular world, as illustrated in the before-and-after
diagram in Figure 1-8.
Exercises 37
Another special case you need to consider is that of a world that is only one
column wide or one row high.
12. Program Karel to place a single beeper at the center of 1st Street. For example,
if Karel starts in the world
the program should finish its execution with Karel standing on a beeper in the
following position:
Note that the final configuration of the world should have only a single beeper
at the midpoint of 1st Street. Along the way, Karel is allowed to place
38 A Gentle Introduction
additional beepers wherever it wants to, but must pick them all up again before
it finishes.
In solving this problem, you may count on the following:
• Karel starts at 1st Avenue and 1st Street, facing east, with an infinite
number of beepers in its bag.
• The initial state of the world includes no interior walls or beepers.
• The world need not be square, but you may assume that it is at least as tall
as it is wide.
• If the width of the world is odd, Karel must put the beeper in the center
square. If the width is even, Karel may drop the beeper on either of the two
center squares.
• It does not matter which direction Karel is facing at the end of the run.
There are many different algorithms you can use to solve this problem. The
interesting challenge is to come up with a strategy that works.
CHAPTER 2
Introducing JavaScript
Computer programs are the most complex things that
humans make.
— Douglas Crockford, JavaScript:
The Good Parts, 2008
Douglas Crockford has written extensively about JavaScript and has for many years championed the virtues
of a language that acquired, particularly in its early years, a largely undeserved negative reputation. In his
2008 book JavaScript: The Good Parts, Crockford maintains that “in JavaScript, there is a beautiful,
elegant, highly expressive language” as long as you look past its largely superficial and entirely avoidable
shortcomings. As we have discovered by using JavaScript in Stanford’s introductory programming course,
it is not at all difficult to write programs that reflect its beauty, elegance, and expressiveness simply by
focusing on the good parts of the language. The purpose of this book is to teach you how JavaScript can
support the creation of highly readable, well-structured programs.
40 Introducing JavaScript
The Karel microworld from Chapter 1 offers a gentle introduction to the idea of
programming, but it is missing at least one critically important concept. Although
beepers make it possible for Karel to manipulate the contents of its world, Karel
offers no effective mechanism for working with data. In computing, the word data
is usually synonymous with information. Computers derive most of their power
from their ability to manipulate information in great quantity and at high speed. In
much of Europe, computer science is commonly referred to as informatics, which
emphasizes the central role that information plays.
Before you can appreciate the power of computing, you need to learn at least the
basics of a programming language that makes it possible to work with data. The
programs in this book use a programming language called JavaScript, which has
become the standard language for writing interactive web applications. The first
version of JavaScript appeared in 1995, reportedly written by a single programmer
at the Netscape Communications Corporation in just ten days. Because of its
popularity, JavaScript is built into every major web browser, which means that any
device with a browser can run JavaScript programs without any additional software.
The focus of this book, however, is not on the JavaScript language itself but
rather on the programs that you write using that language. This book does not cover
all of JavaScript and deliberately avoids those aspects of the language that are easy
to misuse. Even so, the subset of JavaScript you will learn with this book gives you
the tools you need to write exciting applications that use the best features of the
JavaScript language.
In computer science, a data type is defined by two properties: a domain and a set
of operations. The domain is simply the set of values that are elements of that type.
For numeric data, the domain consists of numbers like 0, 42, −273, and 3.14159265.
For string data, the domain comprises sequences of characters that appear on the
keyboard or that can be displayed on the screen. The set of operations is the
2.2 Numeric data 41
toolbox that allows you to manipulate values of that type. For numeric data, the set
of operations includes addition, subtraction, multiplication, and division, along with
a variety of more sophisticated functions. For string data, however, it is hard to
imagine what an operation like multiplication might mean. Using string data
requires a different set of operations such as combining two strings to form a longer
one or comparing two strings to see if they are in alphabetic order. The general rule
is that the set of operations must be appropriate to the elements of the domain. The
two components together—the domain and the operations—define a data type.
Note that large numbers, such as the value of one million shown in the last example,
are written without using commas to separate the digits into groups of three.
2.9979 × 108
2.9979E+8
In JavaScript’s scientific notation, the letter E stands for the words times 10 to the
power.
Arithmetic expressions
The real power of numeric data comes from the fact that JavaScript allows you to
perform computation by applying mathematical operations, ranging in complexity
from addition and subtraction up to highly sophisticated mathematical functions.
As in mathematics, JavaScript allows you to express those calculations through the
use of operators, such as + and - for addition and subtraction.
As you are learning how JavaScript works, it is useful to have access to some
application that allows you to enter JavaScript expressions and see what values they
produce. The website associated with this textbook includes an application that
does precisely that, and there are similar facilities available in other JavaScript
environments. The examples in this book illustrate interactions with JavaScript in
the context of a window called the JavaScript console, but those examples should
be easy to follow even if you are using a different environment.
To get a sense of how interactions with the JavaScript console work, suppose
that you want to solve the following problem, which the singer-songwriter, political
satirist, and mathematician Tom Lehrer proposed in his song “New Math” in 1965:
To find the answer, all you have to do is enter the subtraction into the JavaScript
console, as follows:
Tom Lehrer
+ Addition
– Subtraction (or negation, if written with no value to its left)
* Multiplication
/ Division
% Remainder
2.2 Numeric data 43
The only operator that may seem unfamiliar is %, which computes the remainder of
one value divided by another. For example, 7 % 3 has the value 1, because 7 divided
by 3 leaves a remainder of 1. If one number is evenly divisible by another, there is
no remainder, so that, for example, 12 % 4 has the value 0. In this book, the %
operator is used only with positive integers for which its meaning is easy to
understand.
If you leave out the parentheses, JavaScript first divides 7 by 2 and then adds 4 and
3.5 to produce the value 7.5, as follows:
Precedence
The order in which JavaScript evaluates the operators in an expression is governed
by their precedence, which is a measure of how tightly each operator binds to the
expressions on either side, which are called its operands. If two operators compete
for the same operand, the one with higher precedence is applied first. If two
operators have the same precedence, they are applied in the order specified by their
associativity, which indicates whether that operator groups to the left or to the right.
Most operators in JavaScript are left-associative, which means that the leftmost
operator is evaluated first; a few, however, are right-associative and group from
right to left, as you can see in Figure 2-1 at the top of the next page.
44 Introducing JavaScript
Figure 2-1 shows a complete precedence table for the JavaScript operators, many
of which you will have little or no occasion to use. As additional operators are
introduced later in this book, you can look them up in this table to see where they fit
in the precedence hierarchy. Since the purpose of the precedence rules is to ensure
that JavaScript expressions obey the same rules as their mathematical counterparts,
you can usually rely on your intuition. Moreover, if you are ever in any doubt, you
can always include parentheses to make the order of operations explicit.
2.3 Variables
When you write a program that works with data values, it is often convenient to use
names to refer to a value that can change as the program runs. In programming,
names that refer to values are called variables.
The name of the variable appears on the label and is used to tell different boxes
apart. If you have three variables in a program, each variable will have a different
name. The value corresponds to the contents of the box. The name of the box is
fixed, but you can change the value as often as you like.
Declaring variables
If you need to create a new variable in JavaScript, the standard approach in modern
versions of JavaScript is to include a line in your program that begins with the
keyword let followed by the name of the variable, an equal sign, the initial value
for that variable, and finally a semicolon. A program line that introduces a new
variable is called a declaration. The following declaration, for example, introduces
a variable named r and assigns it the value 10:
let r = 10;
Conceptually, this declaration creates a box inside the computer’s memory, gives it
the name r, and stores the value 10 in the box, like this:
Assignment
Once you have declared a variable, you can change its value by using an
assignment statement, which looks just like a declaration, but without the let
keyword at the beginning. For example, if you execute the assignment statement
r = 2.5;
The value that appears to the right of the equal sign in either a declaration or an
assignment statement can be any JavaScript expression. For example, you can
compute the average of the numbers 3, 4, and 5 using the following declaration:
let average = (3 + 4 + 5) / 3;
Assignment statements are often used to modify the current value of a variable.
For example, you can add the value of deposit to balance using the statement
which takes the current value of balance, adds the value of deposit, and then
stores the result back in balance. Assignment statements of this form are so
common that JavaScript allows you to use the following shorthand:
46 Introducing JavaScript
balance += deposit;
Similarly, you can subtract the value of surcharge from balance by writing
balance -= surcharge;
is equivalent to
The parentheses are included in this pattern to emphasize that the expression is
evaluated before op is applied. Such statements are called shorthand assignments.
These operators, however, are not quite as simple as the preceding paragraph
suggests. The ++ and -- operators can each be written in two different ways. The
operator can come after the operand to which it applies, as in the expression x++, or
before the operand, as in ++x. The first form, in which the operator follows the
operand, is called the suffix form; the second, in which the operator precedes the
operand, is called the prefix form.
If all you do is execute the ++ or -- operator in isolation, the prefix and suffix
operators have precisely the same effect. You notice the difference only if you use
these operators as part of a larger expression. Then, like all operators, the increment
and decrement operators produce a value, but the value depends on where the
operator is written relative to the operand. The two cases are as follows:
x++ Calculates the value of x first, and then increments it. The value of
the expression is the original value before the increment occurs.
++x Increments the value of x first, and then uses the new value as the
value of the ++ operation as a whole.
The -- operator behaves similarly, except that the value is decremented rather than
incremented.
2.3 Variables 47
At first glance, this feature may seem esoteric and unnecessary, and in some
ways it is. The ++ and -- operators are certainly not essential. Moreover, there are
few circumstances in which embedding these operators in a larger expression leads
to programs that are demonstrably better than those that separate the process of
using the value and incrementing the value. On the other hand, ++ and -- are
firmly entrenched in the historical tradition shared by the languages C, C++, Java,
and JavaScript. Programmers use them so frequently that they have become
standard idioms in these languages. In light of their widespread use in programs,
you need to understand these operators so that you can make sense of existing code.
Naming conventions
The names used for variables, constants, functions, and so forth are collectively
known as identifiers. In JavaScript, the rules for identifier formation are
1. The identifier must start with a letter, an underscore (_), or a dollar sign ($).
2. All other characters must be letters, digits, underscores, or dollar signs. This
book uses underscores only as separators in constant names and makes no use
at all of the dollar sign, which is typically reserved for JavaScript libraries.
3. The identifier must not be one of the reserved keywords listed in Figure 2-2.
You can make your programs more readable by using variable names that
immediately suggest the meaning of that variable. If r, for example, refers to the
radius of a circle, that name makes sense because it follows standard mathematical
convention. In most cases, however, it is better to use longer names that make it
clear to anyone reading your program exactly what value a variable contains. For
example, if you need a variable to keep track of the number of pages in a document,
it is better to use a name like numberOfPages than an abbreviated form like np.
48 Introducing JavaScript
The variable name numberOfPages may at first look a little odd because of the
capital letters that appear in the middle of the name. That name, however, follows
what has become a widely accepted standard for naming variables. By convention,
variable names in JavaScript begin with a lowercase letter but include uppercase
letters at the beginning of each new word. This convention is called camel case
because it creates uppercase “humps” in the middle of the variable name.
Constants
You can also make your programs more readable by giving names to values that do
not change as a program runs. Such values are called constants. Modern versions
of JavaScript support the declaration of constants simply by replacing the keyword
let in the declaration with the keyword const. For example, if you are writing a
program that needs to undertake geometrical calculations involving circles, it is
useful to have a constant named PI whose value is a reasonable approximation of
the mathematical constant π. Although you will discover later in this chapter that
the constant PI is already defined in one of the standard libraries, you could always
define it yourself by writing the following declaration:
const PI = 3.14159265;
Sequential calculations
The ability to define variables and constants makes arithmetic calculations easier to
follow, even in the console window. The following sequence of statements, for
example, calculates the area of a circle of radius 10:
JavaScript does not include an operator for raising a number to a power, so the
2
easiest way to express the computation of r is simply to multiply r by itself.
2.4 Functions 49
2.4 Functions
As you discovered when you wrote simple Karel programs in Chapter 1, you don’t
need to enter all your computational operations in the console window but can
instead store those steps as a function. The big difference between functions in
Karel and JavaScript is that functions can use information supplied by their callers
and then give back information in return. The caller sends information to the
function by specifying values inside the parentheses that indicate a function call.
These values are called arguments. Inside the function, each of these arguments is
assigned to a variable called a parameter. The function uses these parameters to
compute a result, which is delivered back to the caller. This process is called
returning a result.
expresses a relationship between the value of x and the value of the function. This
relationship is depicted in the graph to the right, which shows how the value of the
function changes with respect to the value of x.
function f(x) {
return x * x - 5;
}
In this definition, x is the parameter variable, which is set by the argument passed
by the caller. For example, if you were to call f(2), the variable x would be set to
the value 2. The return statement specifies the computation needed to calculate
the result. Multiplying x by itself gives the value 4; subtracting 5 gives the final
result of −1, which is passed back to the caller.
Once you have defined the function f, you can call it from the console like this:
50 Introducing JavaScript
Assuming that you have defined the constant PI as shown on page 48, you can
use the following function to calculate the area of a circle:
function circleArea(r) {
return PI * r * r;
}
To call the circleArea function, all you need to do is specify a value for the
radius. For example, given these definitions of PI and circleArea, you can then
execute the following commands in the console window:
You can use functions to compute values that come up in practical situations that
are largely outside of traditional mathematics. For example, if you travel outside
the United States, you will discover that the rest of the world measures temperatures
in Celsius rather than Fahrenheit. The formula to convert a Celsius temperature to
its Fahrenheit equivalent is
which you can easily translate into the following JavaScript function:
function celsiusToFahrenheit(c) {
return 9 / 5 * c + 32;
}
Functions can take more than one argument, in which case both the parameter
names in the definition and the argument values in the call are separated by
commas. For example, the function
converts a length specified in feet and inches to the equivalent length in centimeters.
Even though a JavaScript function can take more than one argument, a function
can return only one result. It is therefore impossible to write a JavaScript function
that converts a length in centimeters into two independent values, one of which
represents the whole number of feet and one that represents the number of extra
inches left over. As you will see later in this chapter and again in Chapter 9, there
are several strategies that will allow you to come close to achieving this goal.
Library functions
Like all modern languages, JavaScript predefines certain collections of functions
and other useful definitions and makes those collections available to programmers
as libraries. One of the most useful libraries in JavaScript is the Math library,
which includes several mathematical definitions that come up often when you are
writing programs, even when those programs don’t seem particularly mathematical.
52 Introducing JavaScript
Like most built-in libraries in JavaScript, the Math library is implemented as part
of a class, which for the moment you can think of simply as a structure that unifies a
related set of definitions. Figure 2-3 lists several constants and functions available
in the Math library.
In JavaScript, you can use the facilities available in a class by writing the class
name, a dot, and the name of the constant or function you want to use. For
example, the expression [Link] represents the constant named PI in the Math
2.5 String data 53
You can use the functions from the Math class in writing your own functions.
The following function uses the Pythagorean theorem to compute the distance from
the origin to the point (x, y):
function distance(x, y) {
return [Link](x * x + y * y);
} Distance formula
JavaScript allows you to use either single or double quotation marks to specify a
string, but it is good practice to pick a style and then use it consistently. The
programs in this book use double quotation marks, mostly because that convention
is common across a wide range of programming languages. The only exception is
when the string itself contains a double quotation mark, as in '"', which specifies a
one-character string consisting of a double quotation mark. You can also include a
quotation mark in a string by preceding it with a backslash character (\). Thus, you
can also write the one-character string containing a double quotation mark as "\"".
For the most part, you can use strings as a JavaScript data type in much the same
way that you use numbers. You can, for example, declare string variables and
assign them values, just as you would with numeric variables. For example, the
declaration
declares a variable called name and initializes it to the four-character string "Eric".
As with the code used earlier in the chapter to declare numeric variables, the easiest
way to represent a string-valued variable is to draw a box with the name on the
outside and the value on the inside, like this:
The quotation marks are not part of the string but are nonetheless included in box
diagrams to make it easier to see where the string begins and ends.
String operations
In Section 2.1, you learned that data types are defined by two properties: a domain
and a set of operations. For strings, the domain is the set of all sequences of
characters. In JavaScript, most string operations are defined as part of the String
class, which is covered in detail in Chapter 7. For the moment, it is sufficient to
learn just two string operations:
In JavaScript, you can determine the length of a string by adding .length to the
end of the string expression. For example, [Link] has the value 26.
has the value 4, because both of the operands to + are numbers. Conversely,
"abc" + "def"
The concatenation operator also allows you to combine string data with other
data types. If one of the operands to + is a string but the other is some other value,
JavaScript automatically converts that value to a string before performing the
concatenation. For example, the expression
"Fahrenheit " + 451
produces the string "Fahrenheit 451" because JavaScript converts the numeric
value 451 to the string "451" before combining the strings together.
function doubleString(str) {
return str + str;
}
returns two copies of the supplied string joined together. This function enables the
following sample run:
Similarly, you can use the function that follows to add the string "s" to the end
of a word to create a simple plural form:
56 Introducing JavaScript
function simplePlural(word) {
return word + "s";
}
This function does not work for all English words, many of which require adding
"es" instead of "s" depending on the final consonants. You will have a chance to
solve that more sophisticated problem in Chapter 7.
You can also use concatenation to provide a partial solution to the problem
raised earlier in the chapter of converting a distance in centimeters to the equivalent
distance in feet and inches. Although JavaScript does not allow you to return two
separate values from a function, you can display the correct answer by returning a
string that contains both of the desired values, as illustrated by the following
function, which makes use of the same constants introduced earlier in the chapter:
function centimetersToFeetAndInches(cm) {
let totalInches = cm / CENTIMETERS_PER_INCH;
let feet = [Link](totalInches / INCHES_PER_FOOT);
let inches = totalInches % INCHES_PER_FOOT;
return feet + "ft " + inches + "in";
}
That advice was followed by the four-line text of the “Hello World” program, which
became part of the heritage shared by all C programmers.
function HelloWorld() {
[Link]("hello, world");
}
The body of the HelloWorld function calls the built-in function [Link] and
asks it to display the string "hello world" on the JavaScript console.
You will need at least two applications to get started. First, you need a text
editor, which will allow you to create JavaScript program files. All modern
computers come with some kind of text editor, but you will find it easier to write
your programs if the editor you use understands the structure of JavaScript well
enough to catch simple typographical errors and help you understand the different
programming constructs by displaying them in colors that indicate their function.
Second, you need a web browser that can read and display web pages. It doesn’t
matter which browser you use as long as it is modern enough to interpret JavaScript
Version 6, which was released in 2015. If you are using a browser that is older than
that, you should update your browser to the current version.
The first thing you need to do is use your editor to type in the [Link]
program exactly as it appears in Figure 2-4 and then save it in a new folder on your
computer. That step, however, only gets you part of the way toward running the
program in the browser. To complete the task, you need to learn more about the
structure of the web and how to embed JavaScript programs within a web page.
the contents of the page. The browser then interprets the content of the page and
displays it on the screen.
Modern web pages use three distinct but interrelated technologies to define the
contents of the page:
1. The structure and contents of the page are defined using a file written using the
Hypertext Markup Language or HTML.
2. The visual appearance of the page is specified using Cascading Style Sheets or
CSS.
3. Any interactive behavior of the page is represented using one or more files,
which are conventionally written in JavaScript.
If you want to create professional-quality web pages, you need to learn something
about these technologies. Because this book focuses on programming in JavaScript,
the early chapters present only enough about HTML and CSS to let you run
JavaScript programs. Chapter 12 covers these technologies in more detail.
The [Link] file begins with a special tag that marks the file as a standard
HTML index:
<!DOCTYPE html>
After the <!DOCTYPE> tag, HTML tags usually occur in pairs. The first tag opens a
section of the HTML file. The second tag, which uses the same keyword preceded
by a slash character, closes that section. For example, the entire HTML text in the
[Link] file begins with a tag named <html> and ends with the corresponding
closing tag </html>. To make it clear to the reader exactly what parts of the
HTML file are included within each pair of tags, the lines between the opening and
closing tag are typically indented.
A standard HTML file includes two sections between the <html> and </html>
markers. The first of these is the <head> section, which defines features of the
page as a whole; the second is the <body> section, which defines the page contents.
60 Introducing JavaScript
As with other paired tags, the <head> and <body> sections end with the tags
</head> and </body>, respectively.
For simple JavaScript-based web pages, the <head> section contains two types
of interior tags. The first of these is the <title> section, which defines the title
that appears at the top of the web page. The <title> section has the form
where you can replace the italicized text with whatever text you want to use as the
title. By convention, the web programs in this book use the name of the program
file as the title, so that the <title> section for the [Link] program
would be
<title>HelloWorld</title>
The other component of the <head> section is one or more <script> tags that
specify the names of the JavaScript files to load. Each of these <script> tags has
the following form:
<script src="filename"></script>
In this pattern, you need to replace the filename marker with the actual name of the
file. To load [Link], for example, you would use the following tag:
<script src="[Link]"></script>
As your programs become larger and more sophisticated, they will often require
more that one JavaScript file. In some cases, those additional JavaScript files will
be general-purpose libraries, but there will also be cases in which it makes sense to
subdivide a complex application into several JavaScript files, each of which is
responsible for some part of the complete program. In either of these cases, you
need to include additional <script> tags in the <head> section to load any
JavaScript libraries or program components that your application requires.
Although the “Hello World” program does not technically require any libraries,
it turns out that adding a library to the <head> section will make your life as a
programmer much easier. Remember that one of your tasks in Kernighan and
Ritchie’s checklist is to “find out where the output went.” Most browsers make the
console log hard to find, mostly to minimize confusion for the average web user,
who could easily be distracted by messages appearing in the console log. To make
console output easier to find, the [Link] files used in this book include the
following <script> tag to load a library called [Link], which displays
the console log as part of the web page itself:
<script src="[Link]"></script>
2.7 Testing and debugging 61
For simple JavaScript-based web pages that contain no other content, the
<body> section will be empty, with nothing between the opening and closing tags.
The <body> tag, however, must specify an onload attribute to get the program
started. The value of the onload attribute is a JavaScript expression, which is
ordinarily a function call. For example, to trigger a call to the HelloWorld
function when the page has finished loading all the necessary JavaScript code, the
onload attribute would have the value "HelloWorld()".
The complete contents of the [Link] file for the “Hello World” program
appear in Figure 2-5. You can use this file as a template for the [Link] files
you need to implement other JavaScript-based web pages.
Programming defensively
Even though it is impossible to avoid bugs altogether, you can reduce the number of
bugs by being careful during the programming process. Just as it’s important to
drive defensively in your car, it makes sense to program defensively as you write
your code. The most important aspect of defensive programming is looking over
your programs to ensure that they do what you intend them to do. You will also
find that taking the time to make your code as clear and readable as possible will
help avoid problems down the road.
You can, however, get JavaScript to help you out. The original versions of
JavaScript were overly permissive in the sense that they failed to check for common
programming errors, such as forgetting to declare a variable. In standard
JavaScript, forgetting to declare a variable does not generate an error message; what
happens instead is that JavaScript automatically creates that variable as part of the
entire web document, which is usually not what the programmer intended.
"use strict";
at the beginning of a JavaScript file. Doing so makes it possible for the JavaScript
interpreter to do a much better job of identifying potential problems in your code.
From here on, all the programs in this text will use this feature.
To a surprising extent, the challenges that people face while debugging are not
so much technical as they are psychological. To become a successful debugger, the
most important thing is to start thinking in new ways that get you beyond the
psychological barriers that stand in your way. There is no magical, step-by-step
approach to finding the problems, which are usually of your own making. What
you need is logic, creativity, patience, and a considerable amount of practice.
2.7 Testing and debugging 63
It is equally important to recognize that each phase in the programming process Phases and roles in the
programming process
requires a fundamentally different approach. As you move back and forth among
the various phases, you need to adopt different ways of thinking. In my experience, Design = Architect
the best way to illustrate how these approaches differ is to associate each phase with Coding = Engineer
a profession that depends on much the same skills and modes of thought. Testing = Vandal
Debugging = Detective
During the design phase, you have to think like an architect. You need to have a
sense not only of the problem that must be solved but also an understanding of the
underlying aesthetics of different solution strategies. Those aesthetic judgments are
not entirely free from constraints. You know what’s needed, you recognize what’s
possible, and you choose the best design that lies within those constraints.
When you move to the coding phase, your role shifts to that of the engineer.
Now your job is to apply your understanding of programming to transform a
theoretical design into an actual implementation. This phase is by no means
mechanical and requires a significant amount of creativity, but your goal is to
produce a program that you believe implements the design.
64 Introducing JavaScript
In many respects, the testing phase is the most difficult aspect of the process to
understand. When you act as a tester, your role is not to establish that the program
works, but just the opposite. Your job is to break it. A tester therefore needs to
assume the role of a vandal. You need to search deliberately for anything that
might go wrong and take real joy in finding any flaws. It is in this phase of the
programming process that the most difficult psychological barriers arise. As the
coder, you want the program to work; as the tester, you want it to fail. Many people
have trouble shifting focus in this way. After all, it’s hard to be overjoyed at
pointing out the stupid mistakes the coder made when you also happen to be that
coder. Even so, you need to make this shift.
Finally, your job in the debugging phase is that of a detective. The testing
process reveals the existence of errors but does not necessarily reveal why they
occur. Your job during the debugging phase is to sort through all the available
evidence, create a hypothesis about what is going wrong, check that hypothesis
through additional testing, and then make the necessary corrections.
As with testing, the debugging phase is full of psychological pitfalls when you
act in the detective role. When you were writing the code in your role as engineer,
you believed that it worked correctly when you designed it in your role as architect.
You now have to discover why it doesn’t, which means that you have to discard any
preconceptions you’ve retained from those earlier phases and approach the problem
with a fresh perspective. Making that shift successfully is always a difficult
challenge. Code that looked correct to you once is likely to look just as good when
you come back to it a second time.
What you need to keep in mind is that the testing phase has determined that the
program is not working correctly. There must be a problem somewhere. It’s not
the browser or JavaScript that’s misbehaving or some unfortunate conjunction of
the planets. As Cassius reminds Brutus in Shakespeare’s Julius Caesar, “the fault,
dear Brutus, is not in our stars, but in ourselves.” You introduced the error when
you wrote the code, and it is your job to find it.
This book will offer additional suggestions about debugging as you learn how to
write more complex programs, but the following principle will serve you better than
any specific debugging strategy or technique:
Most people who come upon a problem in their code go back to the original
problem and try to figure out why their program isn’t doing what they wanted.
Although such an approach can be helpful in some cases, it is far more likely that
this kind of thinking will make you blind to the real problem. If you make an
2.7 Testing and debugging 65
unwarranted assumption the first time around, you are likely to make it again, and
be left not seeing any reason why your program isn’t doing the right thing. You
need instead to gather information about what your program is in fact doing and
then try to work out where it goes wrong.
to the program. If the message "I got here" appears on the console, you know
that the program got to that point in the code. It is often even more helpful to have
the call to [Link] display the value of an important variable. If, for
example, you expect the variable n to have the value 100 at some point in the code,
you can add the line
If running the program shows that n has the value 0 instead, you know that
something has gone wrong prior to this point. Narrowing down the region of the
program in which the problem might be located puts you in a much better position
to find and correct the error.
a x2 + b x + c = 0
66 Introducing JavaScript
As you know from secondary school, this equation has two solutions given by the
formula
x =
The first solution is obtained by using + in place of the ± symbol; the second is
obtained by using – instead. The problem I give students is to write a function that
takes a, b, and c as parameters and displays the two resulting solutions for x.
Although the majority can solve this problem correctly, there are always a
number of students—as much as 20 percent of a large class—who turn in functions
that look something like this:
function quadratic(a, b, c) {
let root = [Link](b*b - 4*a*c);
let x1 = (-b + root) / 2*a;
let x2 = (-b - root) / 2*a;
[Link]("x1 = " + x1);
[Link]("x2 = " + x2);
}
2.7 Testing and debugging 67
The real lesson in this example, however, lies in the fact that many students
compound their mistake by failing to discover it. Most of the students who make
this error fail to test their programs for any values of the coefficient a other than 1,
since those are the easiest answers to compute by hand. If a is 1, it doesn’t matter
whether you multiply or divide by a because the answer will be the same. Worse
still, students who test their program for other values of a often fail to notice that
their programs give incorrect answers. I often get sample runs that look like this:
This sample run asserts that x = 32 and x = 16 are solutions to the equation
8 x2 – 6 x + 1 = 0
but it is easy to check that neither of these values in fact satisfy the equation. Even
so, students happily submit programs that generate this sample run without noticing
that the answers are wrong.
program indicates exactly what the correct answers should be. A complete sample
run of the TestQuadratic program looks like this:
2.8 Software maintenance 69
Software requires maintenance for two principal reasons. First, even after
considerable testing and, in some cases, years of field use, bugs can still survive in
the original code. Then, when some unanticipated situation arises, the bug,
previously dormant, causes the program to fail. Thus, debugging is an essential part
of program maintenance. It is not, however, the most important part. Far more
consequential, especially in terms of the impact on the overall cost of program
maintenance, is that programs need to change in response to changing requirements.
Users often want new features in their applications, and software developers try to
provide those features to maintain customer loyalty. In either case—whether one
wants to repair a bug or add a feature—someone has to look at the program, figure
out what’s going on, make the necessary changes, verify that those changes work,
and then release a new version. This process is difficult, time-consuming,
expensive, and prone to error.
Many novice programmers are disturbed to learn that there is no precise set of
rules you can follow to ensure good programming style. Software engineering is
not a cookbook sort of process. Instead it is a skill blended with more than a little
bit of artistry. Practice is critical. One learns to write well-structured programs by
writing them, and by reading others, much as one learns to be a novelist. Becoming
an effective programmer requires discipline—the discipline not to cut corners or to
forget, in the rush to complete a project, about that future maintainer. Good
programming practice also requires developing an aesthetic sense of what it means
for a program to be readable and well presented.
• Write both your code and your comments with future maintainers in mind.
• Choose names for variables, constants, and functions that convey their purpose.
• Use indentation to highlight the hierarchical structure of your programs.
• Design your programs so that they are easy to modify as requirements change.
The last point in this list deserves additional discussion. Given that programs
will inevitably change over their lifetimes, it is good programming practice to help
future maintainers make the necessary changes. A useful strategy to support
ongoing maintenance is to use constant definitions for values that you expect might
change at some point down the road.
At some later point, however, the explosive growth of networking would force you
to raise this bound.
Making that change would be easy if you had defined a constant but hard if you
had instead written the number 127. In that case, you would need to change all
instances of 127 that refer to the number of hosts. Some instances of 127 might
refer to things other than the limit on the number of hosts, and it would be just as
important not to change any of those values. In the likely event that you had made a
mistake in that process, you would have a very hard time tracking down the bug.
Summary 71
Summary
In this chapter, you have started your journey toward programming in JavaScript by
considering several example programs that make use of two different data types:
numbers and strings. Important points introduced in the chapter include:
• The primary focus of this book is not the JavaScript language itself but rather the
principles you need to understand the fundamentals of programming. To reduce
the number of language details you need to master, this text relies on the features
that Douglas Crockford, whose contributions are described at the beginning of
the chapter, identifies as the “good parts” of JavaScript.
• Data values come in many different types, each of which is defined by a domain
and a set of operations.
• Numbers in JavaScript are written in conventional decimal notation. JavaScript
also allows you to write numbers in scientific notation by adding the letter E and
an exponent indicating the power of 10 by which the number is multiplied.
• Expressions consist of individual terms connected by operators. The
subexpressions to which an operator applies are called its operands.
• The order of operations is determined by rules of precedence. The complete
table of operators and their precedence appears in Figure 2-1 on page 44.
• Variables in JavaScript have two attributes: a name and a value. Variables used
in a JavaScript program are declared using a line of the form
let identifier = expression;
• JavaScript includes the operators ++ and --, which add and subtract 1 from a
variable, respectively. These operators may appear either before or after their
72 Introducing JavaScript
operand. The placement determines whether the operation occurs before or after
the value is retrieved.
• A function is a block of code that has been organized into a separate unit and
given a name. Other parts of the program can then call that function, possibly
passing it arguments and receiving a result returned by that function.
• Variables declared inside the body of a function are called local variables and
are visible only inside that function. Variables declared outside of any function
are global variables, which can be used anywhere in the program. This book
avoids using global variables, because they make programs harder to maintain.
• A function that returns a value must have a return statement that specifies the
result. Functions may return values of any type.
• JavaScript’s Math library defines a variety of functions that implement such
standard mathematical functions as sqrt, sin, and cos. A list of the more
common mathematical functions appears in Figure 2-3 on page 52.
• A string is a sequence of characters taken together as a unit. In JavaScript, you
write a string by enclosing its characters in quotation marks. JavaScript accepts
either single or double quotation marks for this purpose.
• Although strings support many additional operations that will be presented in
Chapter 7, the examples in this chapter and the next few chapters use only the
length field and the + operator. If both operands to + are numeric, the values
are added; if either operand is a string, both operands are converted to strings
and concatenated end to end.
• In the defining document for the programming language C, Brian Kernighan and
Dennis Ritchie suggest that the first program written in any language should be
one that prints the string "hello, world". This book follows their advice.
• JavaScript was designed for use in conjunction with the World Wide Web.
JavaScript programs typically run in the context of a web browser.
• Modern web pages use three distinct technologies to define the contents of a web
page. The structure and contents of the page are defined using HTML (Hypertext
Markup Language), the visual appearance is specified using CSS (Cascading
Style Sheets), and the interactive behavior is defined using JavaScript.
• Every JavaScript program that runs in a browser must include an [Link]
file, which defines the overall structure of the page, loads the necessary
JavaScript programs and libraries, and specifies a JavaScript expression to be
evaluated when the page is loaded. These [Link] files have a
conventional form, which appears in Figure 2-5.
• JavaScript files are loaded into the browser by means of <script> tags in the
[Link] file, each of which has the following form:
<script src="filename"></script>
Review questions 73
• The four phases of the programming process are design, coding, testing, and
debugging, although it is best to view these phases as interrelated rather than
sequential. Professional programmers typically code one piece of a program,
test it, debug it, and then go back and work on the next piece.
• Each phase in the programming process requires you to behave in a different
way. During the design phase, you act as an architect. When you are coding,
you function as an engineer. During testing, you must act like a vandal, striving
to break the program, not to prove that it works. When debugging, you need to
think like a detective employing all the cleverness and insight of a Sherlock
Holmes.
• When you are trying to find a bug, it is more important to understand what your
program is doing than to understand what it isn’t doing.
• In seeking to understand what your program is doing, your most helpful resource
is the [Link] function.
• The most serious problems programmers face during the testing and debugging
phases are psychological rather than technical. It is extremely easy to let your
assumptions and desires get in the way of understanding where the problems lie.
• It is good programming practice to include test programs along with the
definitions of any functions that you write.
• Programs require maintenance over their life cycles both to correct bugs and to
add new features as user requirements change.
Review questions
1. What are the two attributes that define a data type?
3. Rewrite the following numbers using JavaScript’s form for scientific notation:
a) 6.02252 × 1023
b) 29979250000.0
c) 0.00000000529167
d) 3.1415926535
74 Introducing JavaScript
6. True or false: The - operator has the same precedence when it is used before an
operand to indicate negation as it does when it is used to indicate subtraction.
7. By applying the appropriate precedence rules, calculate the result of each of the
following expressions:
a) 6 + 5 / 4 - 3
b) 2 + 2 * (2 * 2 - 2) % 2 / 2
c) 10 + 9 * ((8 + 7) % 6) + 5 * 4 % 3 * 2 + 1
d) 1 + 2 + (3 + 4) * ((5 * 6 % 7 * 8) - 9) - 10
8. What shorthand assignment statement would you use to multiply the value of
the variable salary by 2?
9. What is the most common way in JavaScript to write a statement that has the
same effect as the statement
x = x + 1;
10. In your own words, explain the difference between the prefix and suffix forms
of the increment and decrement operators.
12. What is the possible range of values returned by the function [Link]?
14. If a string is stored in the variable str, how would you determine its length?
16. How does JavaScript decide whether to interpret the + operator as addition or
concatenation?
17. Given the definition of the doubleString function on page 55, what value
does JavaScript produce if you call doubleString(2)? In light of this
behavior, would it be reasonable to shorten the name of the function to
double? Why or why not?
19. What did Brian Kernighan and Dennis Ritchie suggest should be the first
program you write in any language? What reasons did they offer for starting
with a program that simple?
20. What are the three technologies used to specify a web page? What aspects of
the web page do each of these technologies control?
21. What is the conventional name of the HTML file that defines a web page?
22. What is the syntax of the HTML tag used to load JavaScript files into the
browser?
23. What is the name of the JavaScript library used in this chapter to implement
programs that write output to the console? What reasons does the chapter give
for using this library to replace the standard system console?
24. How can you enable rigorous error checking in a JavaScript program?
25. What are the four phases of the programming process identified in this chapter?
For each of those phases, what professional role does the chapter offer as a
model for how to perform that phase?
26. True or false: Professional programmers work through the four phases of the
programming process in order, finishing each one before moving on to the next.
76 Introducing JavaScript
27. True or false: When you are testing your program, your primary goal is to show
that it works.
28. What piece of advice does the chapter offer to help you think effectively about
debugging?
29. What built-in function does the text identify as the most useful debugging tool?
31. What guidelines does this chapter offer to improve your programming style?
Exercises
1. How would you implement the following mathematical function in JavaScript:
2
f (x) = x – 5x + 6
2. Write a function quotient that takes two numbers, x and y (which you may
assume are both positive integers), and returns the integral quotient of x / y,
discarding any remainder. For example, calling quotient(9, 4) should
return 2 because four goes into nine twice with a remainder of one left over.
This function is easy to write if you use the [Link] function; the
challenge in this exercise is to write quotient using only the standard
arithmetic operators.
5. Write a function that computes the area of a triangle given values for its base
and its height, which are defined as shown in the following diagram:
Given any triangle, the area is always one half of the base times the height.
6. Write a function quote that takes a string value and adds double quotation
marks at both the beginning and the end. Your function definition should allow
you to replicate the following console session:
As the lines at the end of this example indicate, the quote function can make it
easier to see where a string begins and ends, particularly if the string contains
spaces.
In Orwell’s novel, Syme and his colleagues at the Ministry of Truth are
engaged in simplifying English into a more regular language called Newspeak.
As Orwell describes in his appendix entitled “The Principles of Newspeak,”
words can take a variety of prefixes to eliminate the need for the massive
number of words we have in English. For example, Orwell writes,
8. Use an editor to create the program [Link] and the file [Link]
exactly as they appear in Figures 2-4 and 2-5. Use your browser to open the
[Link] file to show that you can get JavaScript programs working.
9. For each of exercises 3 through 7, write a test program that displays some
representative values of the functions along the lines of the TestQuadratic
function in Figure 2-7. Create an [Link] file that loads a JavaScript file
containing both the function definition and the test program, and then show that
your program works by reading the [Link] file into your browser.
CHAPTER 3
Control Statements
I had a running compiler and nobody would touch it. . . .
They carefully told me, computers could only do
arithmetic; they could not do programs.
— Grace Murray Hopper, as quoted in
Charlene Billings, Grace Hopper: Navy
Admiral and Computing Pioneer, 1989
Grace Murray Hopper studied mathematics and physics at Vassar College and went on to earn her Ph.D. in
mathematics at Yale. During the Second World War, Hopper joined the United States Navy and was
posted to the Bureau of Ordinance Computation at Harvard University, where she worked with computing
pioneer Howard Aiken. Hopper became one of the first programmers of the Mark I digital computer,
which was one of the first machines capable of performing complex calculations. Hopper made several
contributions to computing in its early years and was one of the major contributors to the development of
the language COBOL, which continues to have widespread use in business programming applications. In
1985, Hopper became the first woman promoted to the rank of admiral. During her life, Grace Murray
Hopper served as the most visible example of a successful woman in computer science. In recognition of
that contribution, there is now a biennial Celebration of Women in Computing, named in her honor.
80 Control Statements
Students often believe that there must be some rule that determines when they
need to use each of the various control statements a programming language
provides. That’s not how programming works. Control statements are tools for
solving problems. Before you can determine what control statement makes sense in
a particular context, you have to give serious thought to the problem you are trying
to solve and the strategy you should choose to solve it. You write the code for a
program after you have decided how to solve the underlying problem. There is
nothing automatic about the programming process.
The fact that there are no magic rules that turn a problem statement into a
working program is what makes programming such a valuable skill. If it were
possible to carry out the programming process according to some well-defined
algorithm, it would be easy to automate the process and eliminate the need for
programmers entirely. Programming consists of solving problems, many of which
are extremely complex and require considerable ingenuity and creativity to solve.
Solving such problems is what makes computer programming hard; it is also what
makes programming interesting and fun.
working with data of this type. Boolean values are represented in JavaScript using a
built-in type whose domain consists of exactly two values: true and false.
JavaScript defines several operators that work with Boolean values. These
operators fall into two classes—relational operators and logical operators—which
the next two sections discuss.
Relational operators
The simplest questions you can ask in JavaScript are those that compare two data
values. You might want, for example, to determine whether two values are equal or
whether one is greater than or smaller than another. Traditional mathematics uses
the operators =, ≠, <, >, ≤, and ≥ to signify the relationships equal to, not equal to,
less than, greater than, less than or equal to, and greater than or equal to,
respectively. Unfortunately, because several of these symbols don’t appear on a
standard keyboard, JavaScript represents these operators in a slightly different form,
which uses the following character combinations in place of the usual mathematical
symbols:
=== Equal to
!== Not equal to
< Less than
> Greater than
<= Less than or equal to
>= Greater than or equal to
Collectively, these operators are called relational operators because they test the
relationship between two values. Like the arithmetic operators introduced in
Chapter 2, relational operators appear between the two values to which they apply.
For example, if you need to check whether the value of x is less than 0, you can use
the expression x < 0.
At first glance, the relational operators === and !== probably seem a bit strange.
Because the single equal sign had already been reserved to indicate assignment, the
designers of the C programming language from which JavaScript is derived
introduced a new operator consisting of two adjacent equal signs to specify equality.
The designers of JavaScript retained the == operator, but defined it in such a
confusing way that it is hard for anyone—novices and experienced programmers
alike—to use it correctly. In JavaScript, the operators that check for exact equality
and exact inequality are === and !==, and you will save yourself a great deal of
confusion if you use these operators in preference to the shorter forms.
82 Control Statements
Logical operators
In addition to the relational operators, which take values of any type and produce
Boolean results, JavaScript defines three operators that take Boolean operands and
combine them to form other Boolean values:
These operators are called logical operators and are listed in decreasing order of
precedence.
Although the operators &&, ||, and ! correspond to the English words and, or,
and not, it is important to remember that English is somewhat imprecise when it
comes to logic. To avoid that imprecision, it helps to think of these operators in a
more formal, mathematical way. Logicians define these operators using truth
tables, which show how the value of a Boolean expression changes as the values of
its operands change. For example, the truth table for the && operator, given Boolean
values p and q, is
The last column of the table indicates the value of the Boolean expression p && q,
given the individual values of the Boolean variables p and q shown in the first two
columns. Thus, the first line in the truth table shows that when p is false and q is
false, the value of the expression p && q is also false.
Even though the || operator corresponds to the English word or, it does not
indicate one or the other, as it often does in English, but instead indicates either or
both, which is its mathematical meaning.
3.1 Boolean data 83
If you need to determine how a more complex logical expression operates, you can
break it down into these primitive operations and build up a truth table for the
individual pieces of the expression.
In most cases, logical expressions are not so complicated that you need a truth
table to figure them out. The only case that often causes confusion is when the !
operator comes up in conjunction with && or ||. When English speakers talk about
situations that are not true (as is the case when you work with the ! operator), a
statement whose meaning is clear to human listeners is often at odds with
mathematical logic. Whenever you find that you need to express a condition
involving the word not, you should use extra care to avoid errors.
As an example, suppose you wanted to express the idea “x is not equal to either
2 or 3” as part of a program. Just reading from the English version of this
conditional test, new programmers are likely to code this expression as follows:
x !== 2 || x !== 3
As noted in Chapter 1, this book uses the bug symbol to mark sections of code that
contain deliberate errors. In this case, the problem is that an informal English
translation of the code does not correspond to its interpretation in JavaScript. If you
look at this conditional test from a mathematical point of view, you can see that the
expression is true if either (a) x is not equal to 2 or (b) x is not equal to 3. No
matter what value x has, one of the statements must be true, since, if x is 2, it
cannot also be equal to 3, and vice versa. To fix this problem, you need to refine
your understanding of the English expression so that it states the condition more
precisely. That is, you want the condition to be true whenever “it is not the case
that either x is 2 or x is 3.” You could translate this expression directly to
JavaScript by writing
but the resulting expression would be a bit ungainly. The question you really want
to ask is whether both of the following conditions are true:
If you think about the question in this form, you can write the test as
for any logical expressions p and q. This transformation rule and its symmetric
counterpart
are called De Morgan’s laws after the British mathematician Augustus De Morgan.
Forgetting to apply these rules and relying instead on the English style of logic is a
Augustus De Morgan
common source of programming errors.
Short-circuit evaluation
JavaScript interprets the && and || operators in a way that differs from the
interpretation used in many other programming languages. In the programming
language Pascal, for example, evaluating these operators (which are written as AND
and OR) requires evaluating both halves of the condition, even when the result can
be determined partway through the process.
The designers of JavaScript (or, more accurately, the designers of the languages
on which JavaScript is based) took a different approach that is usually more
convenient for programmers. Whenever JavaScript evaluates an expression of the
form
or
exp1 || exp2
the individual subexpressions are always evaluated from left to right, and evaluation
ends as soon as the answer can be determined. For example, if exp1 is false in the
expression involving &&, there is no need to evaluate exp2 since the final answer will
always be false. Similarly, in the example using ||, there is no need to evaluate
the second operand if the first operand is true. This style of evaluation, which
stops as soon as the answer is known, is called short-circuit evaluation.
compound condition is meaningful only if the first part comes out a certain way.
For example, suppose you want to express the combined condition that (1) the value
of the integer x is nonzero and (2) x divides evenly into y. You can express this
conditional test in JavaScript as
(x !== 0)
You can use the if statement to implement several of the simplest functions in
JavaScript’s Math class. For example, you can implement abs as follows:
function abs(x) {
if (x < 0) {
return -x;
} else {
return x;
}
}
Similarly, you can implement max—at least for two arguments—like this:
86 Control Statements
function max(x, y) {
if (x > y) {
return x;
} else {
return y;
}
}
As you almost certainly realized during your study of Karel, the choice of
whether to use the if or the if-else form depends on the structure of the problem.
You use the simple if statement when your problem requires code to be executed
only if a particular condition applies. You use the if-else form for situations in
which the program must choose between two independent sets of actions. You can
often make this decision based on how you would describe the problem in English.
If that description contains the word otherwise or some similar expression, there is a
good chance that you’ll need the if-else form. If the English description conveys
no such notion, the simple form of the if statement is probably sufficient.
if (condition)
statement1;
statement2;
and think that the if statement controls the execution of both statements. Without
the braces, statement2 is outside the range of the if and will always be executed.
There are two situations in which the programs in this book relax the rule that
every clause in an if statement requires curly braces. The first of these is when an
if statement fits on a single line and has no else clause. The second is when the
else clause consists of another test to check for some additional condition. Such
statements are called cascading if statements and may involve any number of
else if lines. For example, the following function implements the sign function
from the Math class, which returns −1, 0, or +1 depending on whether the value of x
is positive, zero, or negative:
3.2 The if statement 87
function sign(x) {
if (x < 0) {
return -1;
} else if (x === 0) {
return 0;
} else {
return 1;
}
}
Note that there is no need to check explicitly for the x > 0 condition. If the program
reaches that last else clause, there is no other possibility (assuming that x is a
number), since the earlier tests have eliminated the negative and zero cases.
In many situations, it makes more sense to use the switch statement to choose
among a set of independent cases than to adopt the cascading if form. The switch
statement is described in section 3.3.
The ?: operator
The JavaScript programming language provides another, more compact mechanism
for expressing conditional execution that can be extremely useful in certain
situations: the ?: operator. (This operator is referred to as question-mark colon,
even though the two characters do not actually appear adjacent to each other in the
code.) Unlike any other operator in JavaScript, ?: requires three operands. The
general form of the operation is
if (condition) {
Use the value of expression1
} else {
Use the value of expression2
}
For example, you can use the ?: operator to implement the function max in the
following streamlined form:
88 Control Statements
function max(x, y) {
return (x > y) ? x : y;
}
The parentheses around the condition are not technically required, but many
JavaScript programmers include them in this context to enhance the readability of
the code.
switch (e)
case c1:
statements
break;
the entire switch statement. If none of the case constants match the value of the
control expression, the statements in the default clause are executed.
The template shown in the syntax box deliberately suggests that the break
statements are a required part of the syntax. I encourage you to think of the switch
syntax in precisely that form. JavaScript is defined so that if the break statement is
missing, the program starts executing statements from the next clause after it
finishes the selected one. While this design can be useful in a few unusual
situations, it tends to cause more problems than it solves. To reinforce the
importance of remembering to include the break statement, every case clause in
this text ends with an explicit break or a return statement.
The one exception to this rule is that multiple case lines specifying different
constants can appear together, one after another, before the same statement group.
For example, a switch statement might include the following code:
case 1:
case 2:
statements
break;
which indicates that the specified statements should be executed if the switch
expression is either 1 or 2. The JavaScript interpreter treats this construction as two
case clauses, the first of which is empty. Because the empty clause contains no
break statement, a program that selects that path simply continues on with the
second clause. From a conceptual point of view, however, you are probably better
off thinking of this construction as a single case clause that represents two
possibilities.
The default clause is optional in the switch statement. If none of the cases
match and there is no default clause, the program simply continues on with the
next statement after the switch statement without taking any action at all. To
avoid the possibility that the program might ignore an unexpected case, it is good
programming practice to include a default clause in every switch statement
unless you are certain that you’ve enumerated all the possibilities.
Because switch statements can be rather long, programs are easier to read if the
case clauses themselves are short. If there is room to do so, it also helps to put the
case identifier, the statements forming the body of the clause, and the break or
return statement all together on the same line. This style is illustrated in the
monthName function in Figure 3-1, which uses a switch statement to translate a
numeric month into its name.
90 Control Statements
The default clause in Figure 3-1 deserves special mention. If the month
parameter is not one of the legal values between 1 and 12, the monthName function
returns the predefined JavaScript constant undefined, which is often used as a
marker to indicate that no meaningful result exists.
There are two important principles to observe about the operation of a while
loop:
1. The conditional test is performed before every cycle of the loop, including the
first. If the test is false initially, the body of the loop is not executed at all.
3.4 The while statement 91
2. The conditional test is performed only at the beginning of a loop cycle. If that
condition happens to become false at some point during the loop, the program
doesn’t notice that fact until it has executed a complete cycle. At that point, the
program evaluates the test condition again. If it is still false, the loop
terminates.
Learning how to use the while loop effectively usually requires looking at
several examples in which a while loop appears in the solution strategy. One
application that has particular historical significance is that of finding the largest
proper divisor of an integer, which is one of the first programs run on the
Small-Scale Experimental Machine at Manchester University—the first computer to
implement the stored-program architecture that is used in essentially all computers
today. The author of the program was Tom Kilburn, the lead engineer on the team
that built the machine, which its inventors nicknamed the “Baby.”
Because the Baby had extremely limited capabilities, Kilburn’s algorithm had to
be almost absurdly simple. Given a number N, the program simply counted down
from N − 1 until it found a number that divided evenly into N. Taking at least some
advantage of JavaScript’s extended set of operations, Kilburn’s algorithm might
look like this:
function largestFactor(n) {
let factor = n - 1;
while (n % factor !== 0) {
factor--;
}
return factor;
}
When the second of these calculations was run on the Manchester Baby on June 21,
1948, the program took 52 minutes to compute the answer. In the process, it
demonstrated both the efficacy and the reliability of the Baby’s architecture.
As a second example, suppose that you have been asked to write a function
digitSum that adds up the digits in a positive integer. Calling digitSum(1729)
92 Control Statements
should therefore produce the result 19, which is 1 + 7 + 2 + 9. How would you go
about implementing such a function?
The first thing that your function needs to do is keep track of a running total.
The usual strategy for doing so is to declare a variable called sum, initialize it to 0,
add each digit to sum one at a time, and finally return the value of sum. That much
of the structure, with the rest of the problem written in English, appears below:
function digitSum(n) {
let sum = 0;
For each digit in the number, add that digit to sum.
return sum;
}
Programs that are written partly in a programming language and partly in English
are called pseudocode.
The sentence
clearly specifies a loop structure of some sort, since there is an operation that needs
to be repeated for each digit in the number. If it were easy to determine how many
digits a number contained, you might choose to use the for loop described later in
this chapter to run through precisely that many cycles. As it happens, finding out
how many digits there are in a number is just as hard as adding them up in the first
place. The best way to write this program is just to keep adding in digits until you
discover that you have added the last one. Loops that run until some condition
occurs are most often coded using the while statement.
The essence of this problem lies in determining how to break up a number into
its component digits. The last digit of an integer n is simply the remainder left over
when n is divided by 10, which is the result of the expression n % 10. The rest of
the number—the integer that consists of all digits except the last one—is given by
[Link](n / 10), which is denoted in mathematics as ⎣n / 10⎦ . For example,
if n has the value 1729, you can use these two expressions to break that number into
two parts, 172 and 9, as shown in the following diagram:
3.4 The while statement 93
Thus, in order to add up the digits in the number, all you need to do is add the value
n % 10 to the variable sum on each cycle of the loop and then replace the value of n
by [Link](n / 10). The next cycle will add in the second-to-last digit from
the original number, and so on, until all the digits have been processed.
But how do you know when to stop? As you compute [Link](n / 10) in
each cycle, you will eventually reach the point at which n becomes 0. At that point,
you’ve processed all the digits in the number and can exit from the loop. Thus, the
while loop needed for the problem is
while (n > 0) {
sum += n % 10;
n = [Link](n / 10);
}
The function returns a string in which value appears at the right edge of a field that
is width characters wide. The first line of the function uses the concatenation
operator to convert value to a string and then adds it to the end of a string that
contains no characters at all, which is called the empty string. The effect is
therefore to initialize the variable str so that it contains the string representation of
value. From here, the function uses concatenation to add spaces to the beginning
of str until it has attained the desired length and then returns the padded string to
the caller. You will have a chance to see alignRight in action in the following
section.
The operation of the for loop is determined by the three italicized expressions
on the for control line: init, test, and step. The init expression indicates how the
for loop should be initialized and typically consists of a variable declaration
specifying an initial value. In a for loop, this variable is called the index variable.
The test expression is a conditional test written exactly like the test in a while
statement. As long as the test expression is true, the loop continues. The step
expression indicates how the index variable changes at the end of each cycle.
The interpretation of the init, test, and step expressions is easiest to illustrate by
example. The most common for loop uses the following header line:
The loop begins by declaring the index variable i and initializing it to 0. The loop
increments the value of n at the end of each cycle and continues as long as i is less
than n. The loop therefore runs for a total of n cycles, with i taking on the values 0,
1, 2, and so forth, up to the final value n – 1.
starts by setting the value of i to start and then continues as long as the value of i is
less than or equal to finish. This loop therefore uses the variable i to count from
start to finish.
You can use this form of the for loop idiom to define a function called fact
that takes an integer n and returns its factorial, which is defined as the product of
3.5 The for statement 95
0! = 1 (by definition)
1! = 1 = 1
2! = 2 = 1×2
3! = 6 = 1×2×3
4! = 24 = 1×2×3×4
5! = 120 = 1×2×3×4×5
6! = 720 = 1×2×3×4×5×6
7! = 5040 = 1×2×3×4×5×6×7
8! = 40320 = 1×2×3×4×5×6×7×8
9! = 362880 = 1×2×3×4×5×6×7×8×9
10! = 3628800 = 1 × 2 × 3 × 4 × 5 × 6 × 7 × 8 × 9 × 10
function fact(n) {
let result = 1;
for (let i = 1; i <= n; i++) {
result *= i;
}
return result;
}
In its current form, you can use the fact function in the console window or in
other functions. It would, however, also be useful if you could generate a table of
factorials such as the one shown above. To do so, the simplest approach would be
to use the JavaScript console to display the output by calling the function
[Link], which displays its argument on the console and then moves the
cursor to the beginning of the next line. The [Link] program in
Figure 3-3 displays a list of factorials that extends through the range given by the
96 Control Statements
Note that the FactorialTable program uses alignRight to display the values in
fixed-width columns.
Although the step component of the for loop usually increments the index
variable, that is not the only possibility. You can, for example, count by twos by
replacing i++ with i += 2 or count backwards using i--. As an illustration of
counting in the reverse direction, the following function counts down from an initial
value until it reaches 0:
function countdown(start) {
for (let t = start; t >= 0; t--) {
[Link](t);
}
}
The Countdown program demonstrates that any variable can be used as an index
variable. In this case, the variable is called t, presumably because that is the
traditional variable for a rocket countdown, as in the phrase “T minus 10 seconds
and counting.”
3.5 The for statement 97
[Link]
98 Control Statements
The expressions init, test, and step in the for loop pattern are optional, but the
semicolons must appear. If init is missing, no initialization is performed. If test is
missing, it is assumed to be true. If step is missing, no action occurs at the end of
the loop cycle.
init;
while (test) {
statements
step;
}
Even though you can rewrite a for statement using while, there are advantages
to using the for statement whenever you can. With a for statement, all the
information you need in order to know how many cycles will be executed is
contained in the header line of the statement. For example, if you see the statement
in a program, you know that the body of the loop will be executed 10 times, once
for each of the values of i between 0 and 9. In the corresponding while loop
let i = 0;
while (i < 10) {
. . . body . . .
i++;
}
the increment operation at the bottom can easily get lost if the body is large.
Although the code fragments using for and while are similar, they are not
identical. The primary difference lies in the treatment of the declaration of the
index variable. In the most recent for loop example, the variable i is defined only
inside the body of the loop and undefined outside of it. The region in which a
variable is defined is called its scope. The scope of the index variable in a for
loop, for example, is the code inside the curly braces that surround the loop body.
3.5 The for statement 99
The code to draw this multiplication table appears in Figure 3-4. To create the
individual entries, you need a pair of nested for loops: an outer loop that runs
through each of the rows and an inner loop that runs through each of the entries in
100 Control Statements
each row. The code inside the inner for loop will be executed once for every row
and column, for a total of 100 individual entries in the table.
The outer loop runs through each value of i from 1 to 10 and is responsible for
displaying one row of the table on each cycle. To do so, the code first declares the
variable line and initializes it to be the empty string. The inner loop then runs
through the values of j from 1 to 10 and concatenates the product of i and j to the
end of line, once again using the alignRight function to ensure that the columns
have the same width. When the inner loop is complete, the program calls
[Link] to display the completed line of the multiplication table.
A useful way to get some practice using nested for loops is to write programs
that draw patterns on the console by displaying lines of characters. As a simple
example, the following function draws a triangle in which the number of stars
increases by one in each row:
function drawConsoleTriangle(size) {
for (let i = 1; i <= size; i++) {
let line = "";
for (let j = 0; j < i; j++) {
line += "*";
}
[Link](line);
}
}
You will have a chance to create several similar displays in the exercises.
The next few sections offer several examples of how control statements can be used
to implement several historically important algorithms.
You then continue to apply this formula to each new guess until the answer is as
close to the actual value as you need it to be.
To get more of a sense of how the Babylonian method works, it helps to consider
a simple example. Suppose that you want to calculate, as the scribes who incised
the cuneiform tablet did, the square root of 2. One possible first guess for g is 1,
which is half the value of n. The first approximation step therefore computes the
following average:
The value 1.5 is closer to the actual square root of 2—which is approximately
1.4142136—so the process is on the right track.
To calculate the next approximation, all you need to do is plug into the formula
as the next value of g, and calculate the new average, as follows:
From this point, you simply repeat the calculation with as the new value of g:
After just four cycles, the Babylonian method has produced an approximation to
the square root of 2 that is correct to eight decimal digits. Moreover, because each
step generates an approximation that is closer to the exact value, you can repeat the
process to produce an approximation with any desired level of accuracy.
Figure 3-7 shows the definition of a sqrt function that uses the Babylonian
method to approximate the square root of its argument. The function uses a while
loop to continue the process until the approximation reaches the desired level of
precision. In this implementation, the while loop continues until the difference
between the square of the current approximation and the original number is no
larger than the value of the constant TOLERANCE.
104 Control Statements
Suppose that you have been asked to write a function that accepts two positive
integers x and y as input and returns their greatest common divisor. From the
caller’s point of view, what you want is a function gcd(x, y) that takes the two
integers as arguments and returns another integer that is their greatest common
divisor. The header line for this function is therefore
function gcd(x, y)
In many ways, the most obvious approach to calculating the gcd is simply to try
every possibility. To start, you simply “guess” that gcd(x, y) is the smaller of x
and y, because any larger value could not possibly divide evenly into a smaller
number. You then proceed by dividing x and y by your guess and seeing if it
divides evenly into both. If it does, you have the answer; if not, you subtract 1 from
your guess and try again. A strategy that tries every possibility is often called a
brute-force approach.
The brute-force approach to calculating the gcd function looks like this in
JavaScript:
function gcd(x, y) {
let guess = [Link](x, y);
while (x % guess !== 0 || y % guess !== 0) {
guess--;
}
return guess;
}
Before you decide that this implementation is in fact a valid algorithm for
computing the gcd function, you need to ask yourself several questions about the
code. Will the brute-force implementation of gcd always give the correct answer?
Will it always terminate, or might the function continue forever?
To determine whether the program gives the correct answer, you need to look at
the condition in the while loop
As always, the while condition indicates under what circumstances the loop will
continue. To find out what condition causes the loop to terminate, you have to
negate the while condition. Negating a condition involving && or || can be tricky
unless you remember how to apply De Morgan’s laws, which were introduced in the
section on “Logical operators” earlier in this chapter. De Morgan’s laws indicate
that the following condition must hold when the while loop exits:
106 Control Statements
From this condition, you can see immediately that the final value of guess is
certainly a common divisor. To recognize that it is in fact the greatest common
divisor, you have to think about the strategy embodied in the while loop. The
critical factor to notice in the strategy is that the program counts backward through
all the possibilities. The greatest common divisor can never be larger than x or y,
and the brute-force search therefore begins with the smaller of these two values. If
the program ever gets out of the while loop, it must have already tried each value
between the starting point and the current value of guess. Thus, if there were a
larger value that divided evenly into both x and y, the program would already have
found it in an earlier iteration of the while loop.
In recognizing that the function terminates, the key insight is that the value of
guess must eventually reach 1, unless a larger common divisor is found. At this
point, the while loop will surely terminate, because 1 will divide evenly into both x
and y, no matter what values those variables have.
Euclid’s algorithm
Brute force is not, however, the only effective strategy. Although brute-force
algorithms have their place in other contexts, they are a poor choice for the gcd
function if you are concerned about efficiency. For example, if you call
gcd(1000005, 1000000)
the brute-force algorithm will run through the body of the while loop almost a
million times before it comes up with the answer 5, even though you can instantly
arrive at that result just by thinking about the two numbers.
What you need to find is an algorithm that is guaranteed to terminate with the
correct answer but that requires fewer steps than the brute-force approach. This is
where cleverness and a clear understanding of the problem pay off. Fortunately, the
necessary creative insight was described sometime around 300 BCE by the Greek
mathematician Euclid, whose Elements (book 7, proposition II) contains an elegant
solution to this problem. In modern English, Euclid’s algorithm can be described as
follows:
You can easily translate this algorithmic description into the following code:
3.6 Algorithmic programming 107
function gcd(x, y) {
let r = x % y;
while (r !== 0) {
x = y;
y = r;
r = x % y;
}
return y;
}
This implementation of the gcd function also correctly finds the greatest common
divisor of two integers. It differs from the brute-force implementation in two
respects. On the one hand, it computes the result much more quickly. On the other,
it is more difficult to prove correct.
The problem is to find a new measuring stick that you can lay end to end on top of
each of these sticks so that it precisely covers each of the distances x and y.
Euclid’s algorithm begins by marking off the large stick in units of the shorter
one, like this:
Unless the smaller number is an exact divisor of the larger one, there is some
remainder, as indicated by the shaded section of the lower stick. In this case, 15
goes into 51 three times with 6 left over, which means that the shaded region is 6
units long. The fundamental insight that Euclid had is that the greatest common
divisor for the original two distances must also be the greatest common divisor of
the length of the shorter stick and the length of the shaded region in the diagram.
108 Control Statements
Given this observation, you can solve the original problem by reducing it to a
simpler problem involving smaller numbers. Here, the new numbers are 15 and 6,
and you can find their greatest common divisor by reapplying Euclid’s algorithm.
You start by representing the new values, xʹ and yʹ , as measuring sticks of the
appropriate length. You then mark off the larger stick in units of the smaller one.
Once again, this process results in a leftover region, which this time has length 3. If
you then repeat the process one more time, you discover that the shaded region of
length 3 is itself the common divisor of xʹ and yʹ and, therefore, by Euclid’s
proposition, of the original numbers x and y. That 3 is indeed a common divisor of
the original numbers is demonstrated by the following diagram:
Euclid supplies a complete proof of his proposition in the Elements. If you are
intrigued by how mathematicians thought about such problems more than 2000
years ago, you may find it interesting to look up translations of the original Greek
source.
Although Euclid’s algorithm and the brute-force algorithm correctly compute the
greatest common divisor of two integers, there is an enormous difference in the
efficiency between the two algorithmic strategies. Suppose once again that you call
gcd(1000005, 1000000)
The brute-force algorithm requires on the order of a million steps to find the answer;
Euclid’s algorithm requires only two. At the beginning of Euclid’s algorithm, x is
1000005, y is 1000000, and r is set to 5 during the first cycle of the loop. Since
the value of r is not 0, the program sets x to 1000000, sets y to 5, and starts again.
On the second cycle, the new value of r is 0, so the program exits from the while
loop and reports that the answer is 5.
The two strategies for computing greatest common divisors presented in this
chapter offer a clear demonstration that the choice of algorithm can have a profound
effect on the efficiency of the solution. If you continue your study of computer
science beyond what is covered in this book, you will learn how to quantify such
differences in performance along with several general approaches for improving
algorithmic efficiency.
3.7 Avoiding fuzzy standards of truth 109
If you want to write programs that are easy to read and maintain, you should
absolutely avoid relying on these fuzzy definitions of truth and falsity and make
sure—as this book does—that every test produces a legitimate Boolean value. In
his book, JavaScript: The Good Parts, Douglas Crockford lists the “surprisingly
large number of falsy values” in his appendix on JavaScript’s “awful parts.” But
you might also take the following advice from an even older source:
Let what you say be simply “Yes” or “No”; anything more than this
comes from evil.
—Matthew 5:37, The New English Bible
Summary
The purpose of this chapter is to introduce the most common control statements
available in JavaScript and give you various examples of their use. The important
points include:
• One of the most useful types in any modern programming language is Boolean
data, for which the domain consists of just two values: true and false.
• You can generate Boolean values using the relational operators (<, <=, >, >=,
===, and !==), and you can combine Boolean values using the logical operators
(&&, ||, and !).
• The logical operators && and || are evaluated in left-to-right order in such a way
that the evaluation stops as soon as the program can determine the result. This
behavior is called short-circuit evaluation.
• Control statements fall into two classes: conditional and iterative.
110 Control Statements
where init typically declares and initializes an index variable, test specifies the
conditions under which the loop continues, and step determines what operations
are performed at the end of each cycle.
• An algorithm is a strategy that is clear and unambiguous, effective, and finite.
• There are usually many different algorithms for solving a particular problem.
Algorithms for solving a problem often vary dramatically in their efficiency.
Choosing the algorithm that best fits the application is an important part of your
task as a programmer.
• JavaScript does not insist that conditional tests have the values true or false,
but instead allows programmers to slip into using a fuzzier standard of truth. If
you want to write programs that are easy to read and maintain, you should avoid
writing any code that relies on this unfortunate feature of the language.
Review questions
1. What are the JavaScript keywords for the two Boolean values?
2. How would you write a Boolean expression to test whether the value of the
integer variable n was in the range 0 to 9, inclusive?
6. What does it mean to say that two control statements are nested?
Review questions 111
8. What rule does this chapter suggest with respect to the final statement in any
case or default clause?
9. What special value is used in the monthName function in Figure 3-1 to indicate
an illegal numeric month?
10. What was the nickname of the Small-Scale Experimental Machine developed at
Manchester University that was in many respects the first modern digital
computer?
11. Suppose the body of a while loop contains a statement that, when executed,
causes the condition for that while loop to become false. Does the loop
terminate immediately at that point or does it complete the current cycle?
12. What term do computer scientists use to refer to an incomplete program written
partly in a programming language and partly in English?
13. Why is it important for the comments preceding the digitSum function shown
in Figure 3-2 to require that the argument value be positive? What would
happen if the argument were negative?
14. What is the empty string and how do you write it in JavaScript?
15. What is the purpose of each of the three expressions that appear in the control
line of a for statement?
16. What for loop control line would you use in each of the following situations:
a) Counting from 1 to 100.
b) Counting by sevens starting at 0 until the number has more than two digits.
c) Counting backward by twos from 100 to 0.
18. Use Euclid’s algorithm to compute the greatest common divisor of 7735 and
4185. What values does the local variable r take on during the calculation?
20. What do the terms falsy and truthy signify in JavaScript? What strategy does
this book suggest for avoiding the ambiguity associated with these terms?
112 Control Statements
Exercises
1. Using the two definitions of the max function—one with an if statement and
one with the ?: operator—as examples, write corresponding implementations
of the min function, which returns the smaller of its two arguments.
2. Write a function max3 that returns the largest of its three arguments.
3. As a way to pass the time on long bus trips, young people growing up in the
United States have been known to sing the following rather repetitive song:
99 bottles of beer on the wall.
99 bottles of beer.
You take one down, pass it around.
98 bottles of beer on the wall.
98 bottles of beer on the wall. . . .
Anyway, you get the idea. Write a JavaScript program to display the lyrics of
this song using [Link]. In testing your program, it would make sense
to use some constant other than 99 as the initial number of bottles.
4. While we’re on the subject of silly songs, another old standby is “This Old
Man,” for which the first verse is
This old man, he played 1.
He played knick-knack on my thumb.
With a knick-knack, paddy-whack,
Give your dog a bone.
This old man came rolling home.
Each subsequent verse is the same, except for the number and the rhyming
word at the end of the second line, which get replaced as follows:
2—shoe 5—hive 8—pate
3—knee 6—sticks 9—spine
4—door 7—heaven 10—shin
5. Write a function that takes a positive integer N and then calculates and displays
the sum of the first N odd integers. For example, if N is 4, your function should
display the value 16, which is 1 + 3 + 5 + 7.
Write a program that displays the integers between 1 and 100 that are divisible
by either 6 or 7 but not both.
Exercises 113
7. Using the digitSum function as a model, define a function that takes a number
and returns a number that contains the same digits in the reverse order, as
illustrated by the following sample run:
The idea in this exercise is not to take the integer apart character by character,
which you will not learn how to do until Chapter 7. Instead, you need to use
arithmetic to compute the reversed integer as you go. For example, in the call
to reverseDigits(1729), the new integer will be 9 after the first cycle of the
loop, 92 after the second, 927 after the third, and 9271 after the fourth.
8. The digital root of an integer n is defined as the result of summing the digits
repeatedly until only a single digit remains. For example, the digital root of
1729 can be calculated using the following steps:
Step 1: 1 + 7 + 2 + 9 → 19
Step 2: 1 + 9 → 10
Step 3: 1 + 0 → 1
Because the total at the end of step 3 is the single digit 1, that value is the
digital root. Write a function digitalRoot that returns this value.
This program is easy to write if you simply add a second loop that counts
backwards at the end of the code from drawConsoleTriangle. You can get
a better sense of the flexibility of the for statement in JavaScript if you instead
use a single outer loop that changes direction when it reaches the desired width.
13. The first program written for the Manchester Baby found the largest factor of a
number. A more interesting problem is to find the complete set of factors.
Write a function printFactors(n) that lists all the factors in the form of a
single line that includes the number n, an equal sign, and the individual factors
separated by asterisks, as illustrated in the following console log:
Exercises 115
14. The German mathematician Gottfried Wilhelm von Leibniz discovered the
rather remarkable fact that the mathematical constant π can be computed using
the following mathematical relationship:
The formula to the right of the equal sign represents an infinite series; each
fraction represents a term in that series. If you start with 1, subtract one-third,
add one-fifth, and so on, for each of the odd integers, you get a number that
gets closer and closer to the value of π/4 as you go along.
Write a program that calculates an approximation of π consisting of the first
10,000 terms in Leibniz’s series.
15. Modify the program in the preceding exercise so that instead of specifying the
index of the final term, the program displays those terms in the Fibonacci
sequence that are smaller than 10,000.
16. An integer greater than 1 is said to be prime if it has no divisors other than
itself and one. The number 17, for example, is prime because it has no factors
other than 1 and 17. The number 91, however, is not prime because it is
divisible by 7 and 13. Write a predicate function isPrime(n) that returns
true if the integer n is prime, and false otherwise. As an initial strategy,
implement isPrime using a brute-force algorithm that simply tests every
possible divisor. Once you have that version working, try to come up with
improvements to your algorithm that increase its efficiency without affecting its
correctness.
17. Greek mathematicians took a special interest in numbers that are equal to the
sum of their proper divisors (a proper divisor of n is any divisor less than n
itself). They called such numbers perfect numbers. For example, 6 is a perfect
number because it is the sum of 1, 2, and 3, which are the integers less than 6
that divide evenly into 6. Similarly, 28 is a perfect number because it is the
sum of 1, 2, 4, 7, and 14.
18. Although Euclid’s algorithm for calculating the greatest common divisor is one
of the oldest to be dignified with that term, there are other algorithms that date
116 Control Statements
back many centuries. In the Middle Ages, one of the problems that required
sophisticated algorithmic thinking was determining the date of Easter, which
falls on the first Sunday after the first full moon following the vernal equinox.
Given this definition, the calculation involves interacting cycles of the day of
the week, the orbit of the moon, and the passage of the sun through the zodiac.
Early algorithms for solving this problem date back to the third century and are
described in the writings of the eighth-century scholar known as the Venerable
Bede. In 1800, the German mathematician Carl Friedrich Gauss published an
algorithm for determining the date of Easter that was purely computational in
the sense that it relied on arithmetic rather than looking up values in tables. His
algorithm—translated from the German—appears in Figure 3-8.
Unfortunately, the algorithm in Figure 3-8 works only for years in the 18th
and 19th centuries. It is easy, however, to search the web for extensions that
work for all years. Once you have completed your implementation of Gauss’s
algorithm, undertake the necessary research to implement a more general
approach.
CHAPTER 4
Simple Graphics
A display connected to a digital computer gives us a
chance to gain familiarity with concepts not realizable in
the physical world. It is a looking glass into a
mathematical wonderland.
— Ivan Sutherland, “The Ultimate Display,” 1965
Ivan Sutherland was born in Nebraska and developed a passion for computers while still in high school,
when a family friend gave him the opportunity to program a tiny relay-based machine called SIMON.
Since computer science was not yet an academic discipline, Sutherland majored in electrical engineering at
Pittsburgh’s Carnegie Institute of Technology (now Carnegie Mellon University) and then went on to get a
Master’s degree at Caltech and a Ph.D. from MIT. His doctoral thesis, “Sketchpad: A man-machine
graphical communications system,” became one of the cornerstones of computer graphics and introduced
the idea of the graphical user interface, which has become an essential feature of modern software. After
completing his degree, Sutherland held faculty positions at Harvard, the University of Utah, and Caltech
before leaving academia to found a computer-graphics company. Sutherland received the ACM Turing
Award in 1988.
118 Simple Graphics
This chapter introduces you to the facilities in the Portable Graphics Library, a
collection of tools for writing simple graphical applications. The discussion in this
chapter provides enough information to get you started; more advanced features of
the graphics library will be introduced as they are needed.
The linked image cannot be displayed. The file may have been moved, renamed, or deleted. Verify that the link points to the correct file and location.
4.2 Classes, objects, and methods 119
<script src="[Link]"></script>
The main function for the [Link] program looks like this:
function GraphicsHelloWorld() {
let gw = GWindow(GWINDOW_WIDTH, GWINDOW_HEIGHT);
let msg = GLabel("hello, world", 50, 100);
[Link](msg);
}
The body of GraphicsHelloWorld begins with two variable declarations, one for
the variable gw, which stands for “graphics window,” and one for the variable msg,
which refers to the message on the screen. The declarations themselves have the
same form as the ones you have seen earlier. Each declares a variable and
initializes it to a value. What’s different are the types of these values.
Creating objects
The [Link] program includes two declarations that create
objects:
The functions GWindow and GLabel are part of the [Link] library and
create new objects of those classes. The GWindow class represents a graphical
window on the screen, and the GLabel class represents a string that can appear in
that window. Functions that create new objects are called factory methods and
typically start with an uppercase letter.
The declaration
uses the GWindow factory method to create an object whose class is GWindow. The
parameters GWINDOW_WIDTH and GWINDOW_HEIGHT specify the window size in
units called pixels, which are the tiny dots that cover the face of the display. The
call to GWindow therefore creates a new GWindow object that is 500 pixels wide and
200 pixels high. That object is then assigned to a variable named gw, which makes
it possible for the program to refer to the window in the rest of the code.
Even though the declarations of the variables gw and msg create the necessary
objects, these lines alone do not cause the GLabel to appear in the GWindow. To
get the message to appear, the program has to tell the GWindow object stored in gw
to add the GLabel stored in msg to its internal list of graphical objects to display on
the window. This step in the process is the responsibility of the last line in the
[Link] program, which looks like this:
[Link](msg);
Understanding how this statement works requires you to learn a little more about
the way that JavaScript works with objects.
rather than on the more traditional model in which data and operations are seen as
separate. In object-oriented programming, the generic term for anything that
triggers a particular behavior in an object is called a message. In JavaScript, the
object-oriented idea of sending a message to an object is implemented by calling a
function associated with that object. Functions that are associated with an object are
called methods, and the object on which the method is invoked is called the
receiver.
[Link](arguments)
In the method call [Link](msg), the graphics window stored in gw is the receiver,
and add is the name of the method that responds to the message. The argument msg
lets the implementation of the GWindow class know what graphical object to add,
which in this case is the GLabel stored in the msg variable. The GWindow responds
by displaying the message at the specified coordinates on the screen, which creates
the following image:
As you can see from the screen image, the desired message is there. Although it’s
not very large or exciting, you’ll have a chance to spice it up later in the chapter.
References
In JavaScript, the value stored in a variable like gw is not the entire object but
instead a reference, which is a value internal to the computer that serves as a link to
the data in the actual object. In the [Link] program, the
declaration
As the arrow suggests, the reference stored in gw points to a larger value that
represents the graphics window on the screen.
The declaration
operates in a similar fashion. This line creates a GLabel object and assigns a
reference to that object to the variable msg, as follows:
Although it is often possible to ignore the distinction between a reference and its
associated object, it is important to understand that assigning an object value to a
variable does not copy the entire object but instead copies only the reference. For
example, if you were to write the declaration
JavaScript would not create a second object but would instead arrange it so that msg
and msg2 both contained references to the same object, as follows:
If you need to create a second GLabel—even one that has the same contents—you
need to call the GLabel factory method.
Encapsulation
The diagrams for the GLabel objects in the preceding section show only the data
values that are stored inside those objects. In addition to these values, objects also
contain the methods that apply to objects of that class, even though you won’t see
the code for those methods unless you look inside the graphics library. The internal
data values and the code for the methods are not available to the function that
creates the GLabel but are instead securely packaged inside the object. This model
of packaging data and code together is called encapsulation.
4.3 Graphical objects 123
For the most part, the [Link] program looks much the same as the
[Link] program from Figure 4-1. It includes—as all of the
graphics programs do in this book—constant definitions indicating the size of the
graphics window and a main program that begins by creating a GWindow of the
desired size and assigning it to the variable gw.
which creates a GRect object used to display the rectangle in the window. In this
call, the first two arguments, 150 and 50, indicate the x and y coordinates at which
the rectangle should be positioned; the second two arguments, 200 and 100, specify
the width and height of the rectangle. As in the earlier call to GWindow, each of
these values is measured in pixels, but it is important to keep in mind that the
coordinate values in the y direction increase as you move down the screen, with the
(0, 0) origin in the upper left corner. To maintain consistency with this convention,
the origin of a graphical object is usually defined to be its upper left corner. The
GRect object stored in the variable rect is therefore positioned so that its upper
left corner is at the point (150, 50) relative to the upper left corner of the window.
This geometry is illustrated in Figure 4-4.
[Link]("Blue");
4.3 Graphical objects 125
sends the rectangle object a setColor message asking it to change its color. The
argument to setColor is a string representing one of the many color names that
JavaScript defines, which are listed in Figure 4-5. In this case, the setColor call
tells the rectangle to set its color to blue.
If the 140 standard web colors listed in Figure 4-5 are not enough for you,
JavaScript allows you to specify 16,777,216 different colors by indicating the
proportion of the three primary colors of light: red, green, and blue. To do so, all
you need to do is specify the color as a string in the form "#rrggbb", where rr
indicates the red value, gg indicates the green value, and bb indicates the blue value.
Each of these values is expressed as a two-digit number written in hexadecimal, or
base 16. You may already be familiar with this form of color specification from
designing web pages. If not, you will have a chance to learn more about
hexadecimal notation in Chapter 7.
126 Simple Graphics
[Link](true);
[Link](rect);
which sends an add message to the graphics window, asking it to add the graphical
object stored in rect to the contents of the window. Adding the rectangle produces
the final contents of the display.
By default, the GRect function creates rectangles that are unfilled. Thus, if you
left this statement out of [Link], the result would look like this:
For filled shapes, you can set the color of the interior by calling setFillColor
with any of the color names from Figure 4-5. For example, if you replace the call to
setColor("Blue") in Figure 4-3 with a call to setFilledColor("Cyan"), the
rectangle would be filled in cyan but outlined in black, like this:
4.3 Graphical objects 127
The relationship between the GRect and the GOval classes is most easily
illustrated by example. The following function definition takes the code from the
earlier [Link] program and extends it by adding a GOval with the
same coordinates and dimensions:
function GRectPlusGOval() {
let gw = GWindow(GWINDOW_WIDTH, GWINDOW_HEIGHT);
let rect = GRect(150, 50, 200, 100);
[Link](true);
[Link]("Blue");
[Link](rect);
let oval = GOval(150, 50, 200, 100);
[Link](true);
[Link]("Red");
[Link](oval);
}
There are two important things to notice in this example. First, the red GOval
extends so that its edges touch the boundary of the rectangle. Second, the GOval,
which was added after the GRect, hides the portions of the rectangle that lie
underneath the oval. If you were to add these figures in the opposite order, all you
would see is the blue GRect, because the entire GOval would be within the
boundaries of the GRect.
128 Simple Graphics
creates a GLine object running from the point (0, 0) in the upper left corner of the
graphics window to the point at the opposite corner in the lower right.
The following function uses the GLine class to draw the two diagonals across
the graphics window:
function DrawDiagonals() {
let gw = GWindow(GWINDOW_WIDTH, GWINDOW_HEIGHT);
[Link](GLine(0, 0, GWINDOW_WIDTH, GWINDOW_HEIGHT));
[Link](GLine(0, GWINDOW_HEIGHT, GWINDOW_WIDTH, 0));
}
In all likelihood, you already know about fonts from working with other
computer applications and have an intuitive sense that fonts determine the style in
which characters appear. More formally, a font is an encoding that maps characters
into images that appear on the screen. To change the font of the GLabel, you need
to send it a setFont message, which might look like this:
This call to the setFont method tells the GLabel stored in msg to change its font
to one in which the height of a text line is 36 pixels and the font family is Times
New Roman, used by The New York Times. If you include the setFont call in the
program, the graphics window will look like this:
The string passed as the argument to setFont is written using CSS, which, as
noted in Chapter 2, is the technology the web uses to specify the visual appearance
of the page. This string specifies several properties of the font, which appear in the
following order:
• The font style, which can be used to indicate an alternative form of the font.
This specification is ordinarily omitted from the font string to indicate a normal
font but may appear as italic or oblique to indicate an italic variant or a
slanted one.
• The font weight, which specifies how dark the font should be. This specification
is omitted for normal fonts but may appear as bold to specify a boldface one.
• The font size, which specifies how tall the characters should be by indicating the
distance between two successive lines of text. In CSS, the font size is usually
specified in pixel units as a number followed by the suffix px, as in the 36px
specification used in the most recent call to setFont.
• The font family, which specifies the name associated with the font. If the name
of the font contains spaces, it must be quoted, usually using single quotation
marks because the font specification appears inside a double-quoted string.
Setting the text in Times New Roman, for example, therefore requires the font
string to include 'Times New Roman'. Because different computers support
different fonts, CSS allows a font specification to include several family names
separated by commas. The browser will then use the first font family that is
available.
CSS defines several generic family names, which do not identify a specific font
but instead describe a type of font that is always available in some form. The most
common generic family names appear in Figure 4-6. It is good practice to end the
list of preferred font families with one of these generic names to ensure that your
program will run on the widest possible set of browsers.
130 Simple Graphics
As you probably know from using your word processor, it can be fun to
experiment with different fonts. On most Macintosh systems, for example, there is
a font called Lucida Blackletter that produces a script reminiscent of the style of
illuminated manuscripts of medieval times. To set the message in this font, you
could change the setFont call in this program to
Note that the font string includes the generic family name Serif as an alternative.
If the browser displaying the page could not find a font called Lucida Blackletter, it
could then substitute one of the standard serif fonts, such as Times New Roman. If,
however, it were able to load the Lucida Blackletter font successfully, the output
would look something like this:
The GLabel class uses its own geometric model, which is similar to the ones
that typesetters have used over the centuries since Gutenberg’s invention of the
printing press. The notion of a font, of course, originally comes from printing.
Printers would load different sizes and styles of type into their presses to control the
way in which characters appeared on a page. The terminology that the graphics
library uses to describe both fonts and labels also derives from the typesetting
world. You will find it easier to understand the behavior of the GLabel class if you
learn the following terms:
• The origin is the point at which the text of a label begins. In languages that read
left to right, the origin is the point on the baseline at the left edge of the first
character. In languages that read right to left, the origin is the point at the right
edge of the first character, at the right end of the line.
• The height is the distance between successive baselines in multiline text.
• The ascent is the maximum distance that characters extend above the baseline.
• The descent is the maximum distance that characters extend below the baseline.
The interpretation of these terms in the context of the GLabel class is illustrated in
Figure 4-7.
The GLabel class includes methods that allow you to determine these properties.
For example, the GLabel class includes a method called getAscent to determine
the ascent of the font in which the label appears. In addition, it includes a method
called getWidth that determines the horizontal extent of the GLabel.
These methods make it possible to center a label in the window, although they
raise an interesting question. The only function you’ve seen to create a GLabel
takes its initial coordinates as parameters. If you want to center a label, you won’t
know those coordinates until after you have created the label. To solve this
problem, the function that creates a GLabel comes in two forms. The first takes the
string for the label along with the x and y coordinates of the origin. The second
leaves out the origin point, setting the origin to the default value of (0, 0).
Suppose, for example, that you want to center the string "hello, world" in the
graphics window. To do so, you first need to create the GLabel, then change its
font so the label has the appearance you want, and finally determine the dimensions
of the label to calculate the correct initial position. You can then supply those
coordinates in the add method, which takes optional x and y parameters to set the
132 Simple Graphics
location of the object when you add it to the GWindow. The following program
implements this strategy:
function CenteredHelloWorld() {
let gw = GWindow(GWINDOW_WIDTH, GWINDOW_HEIGHT);
let msg = GLabel("hello, world");
[Link]("36px 'Sans-Serif'");
let x = ([Link]() - [Link]()) / 2;
let y = ([Link]() + [Link]()) / 2;
[Link](msg, x, y);
}
The calculations necessary to center the GLabel occur in the declarations of the
variables x and y, which specify the origin point for the centered label. To compute
the x coordinate of the label, you need to shift the origin left by half the width of the
label from the center of the window. Centering the label in the vertical dimension is
a bit trickier. You can get pretty close by defining the y coordinate to be half the
font ascent below the centerline. These declarations also introduce the fact that the
GWindow object implements the getWidth and getHeight methods, so you can
use these method calls to determine the width and height of the window.
If you’re a stickler for aesthetic detail, you may find that using getAscent to
center a GLabel vertically doesn’t produce the optimal result. Most labels that you
display on the canvas will appear to be a few pixels too low. The reason is that
getAscent returns the maximum ascent of the font and not the distance that the
text of this particular GLabel happens to rise above the baseline. If you want things
to look perfect, you may have to adjust the vertical centering by a pixel or two.
The most important methods in the GRect, GOval, GLine, and GLabel classes
are summarized in Figure 4-8. Other classes and methods will be introduced in later
chapters as they become relevant.
4.4 The graphics window 133
which appears at the beginning of every program that uses the graphics library.
This statement creates the graphics window and installs it in the web page so that it
is visible to the user. It also serves to implement the conceptual framework for
displaying graphical objects. The conceptual framework implemented by a library
package is called its model. The model gives you a sense of how you should think
about working with that package.
One of the most important roles of a model is to establish what analogies and
metaphors are appropriate for the package. Many real-world metaphors are possible
for computer graphics, just as there are many different ways to create visual art.
One possible metaphor is that of painting, in which the artist selects a paintbrush
and a color and then draws images by moving the brush across a screen that
represents a virtual canvas.
The fact that the graphics window uses the collage model has implications for
the way you describe the process of creating a design. If you were using the
metaphor of painting, you might talk about making a brush stroke in a particular
position or filling an area with paint. With the collage model, the key operations are
adding and removing objects, along with repositioning them on the background
canvas.
Collages also have the property that some objects can be positioned on top of
other objects, obscuring whatever is behind them. Removing those objects reveals
whatever used to be underneath. In this book, the back-to-front ordering of objects
in the collage is called the stacking order, although you will sometimes see it
referred to as z-ordering in more formal writing. The name z-ordering comes from
the fact that the stacking order occurs along the axis that comes out of the
two-dimensional plane formed by the x and y axes. In mathematics, the axis coming
out of the plane is called the z-axis.
The methods exported by the GWindow class appear in Figure 4-9. For now,
your most important methods are add, getWidth, and getHeight. The other
methods will be described in more detail when they are needed for an application.
4.5 Creating graphical applications 135
This program, which appears in Figure 4-10, displays three graphical objects:
1. A GOval representing the balloon itself, outlined in black and filled in red
2. A GLine representing the string attached to the balloon. In the code, the word
cord is used instead of string to avoid any possible confusion with the message,
which is a JavaScript string.
3. A GLabel displaying the string "CS is fun!" drawn in white.
4.5 Creating graphical applications 137
The objects themselves are not hard to create. What typically takes the most time
when you are creating this kind of display is figuring out how to specify the sizes of
each object and how to position them in the window so that everything fits together
in the way you want it to appear.
The simplest strategy for specifying the sizes and other properties of graphical
objects is to define them as constants, as shown in the [Link] example.
The constants indicate that the graphics window is 500 pixels wide and 300 pixels
high, that the balloon itself is 140 pixels wide and 160 pixels tall, that the message it
displays is the string "CS is fun!", and that the string tied to the base of the
balloon is 100 pixels long.
Your primary task in writing the program is to figure out exactly how to position
the graphical objects given the values of these constants. The entire figure—the
balloon together with its string—is centered in the graphics window, which means
that you have to figure out the coordinate locations for each of the objects relative to
the center of the window. The coordinates of the center are easily computed using
the following declarations, which will show up repeatedly in other examples:
let cx = [Link]() / 2;
let cy = [Link]() / 2;
The upper left corner of the oval representing the balloon is then shifted left
from cx by half the width of the balloon and shifted upward from cy by half the
total height, which is BALLOON_HEIGHT + CORD_LENGTH. The coordinates of the
upper left corner of the oval can therefore be computed as follows:
The remaining coordinates can be computed similarly. The y-coordinate of the top
of the cord, for example, can be computed using the following expression:
Although there are other reasonable choices, one possible strategy is to subdivide
the problem into functions that draw the house frame, the door, and each of the
windows. These functions would then have responsibility for drawing the parts of
the picture shown in the left margin. You can then draw the entire house by making
one call to drawFrame, one call to drawDoor, and two calls to drawWindow.
What you still need to do is figure out how each of these functions knows where
to position its part of the picture and what the sizes of each of the components
should be. As in the [Link] program, some of the values can be specified
using constants. Some, however, have to be passed as parameters to these
functions. At a minimum, the drawWindow function needs to know the x and y
coordinates of the window so that it can draw a window in two different places.
A complete program to draw the house appears in Figure 4-11 on the next two
pages. Each of the functions takes three parameters: the graphics window gw and
the coordinates x and y, which specify the location on the window at which that part
of the entire picture should appear. For consistency with JavaScript’s graphics
model, these coordinate values specify the upper left corner of that component of
4.5 Creating graphical applications 139
[Link]
140 Simple Graphics
the picture. For graphical objects that don’t have an upper left corner, the usual
strategy is to have the coordinates refer—as they do for the GOval class—to the
upper left corner of the rectangle that encloses the object, which is called its
bounding box. Thus, the coordinates for the house as a whole indicate the upper
left corner of the dashed rectangle in the following diagram:
4.5 Creating graphical applications 141
It is worth taking a look at the code for [Link] to make sure you
understand how the expressions ensure that the circles are centered.
142 Simple Graphics
When you work with two-dimensional graphical designs, you often need nested
loops to arrange graphical objects in both the horizontal and vertical directions. The
[Link] program in Figure 4-13, for example, draws a checkerboard
that looks like this:
4.5 Creating graphical applications 143
Once again, it is worth taking some time to go through the code in Figure 4-13,
paying particular attention to the following details:
• The program is designed so that you can easily change the dimensions of the
checkerboard by changing the values of the constants N_ROWS and N_COLUMNS.
• The checkerboard is arranged so that it is centered in the graphics window. The
variables x0 and y0 are used to hold the coordinates of the upper left corner of
the centered board.
• The decision to fill a square is made by checking whether the sum of its row
number and column number is even or odd. For white squares, this sum is even;
for black squares, this sum is odd. Note, however, that you don’t need to include
an if statement in the code to test this condition. All you need to do is call the
setFilled method with the appropriate Boolean value.
Summary
This chapter introduced the Portable Graphics Library, which allows you to create
simple pictures on the screen using lines, rectangles, ovals, and labels. Along the
way, you had a chance to practice using objects in JavaScript.
• The graphical programs in this book use the Portable Graphics Library, which is
a collection of graphical tools designed for use in introductory courses. That
library is supplied as a single JavaScript file called [Link].
Summary 145
The receiver is the object to which the message is sent, name indicates the name
of the method that responds to the message, and arguments is a list of values that
convey any additional information carried by the message.
• Functions that create new objects are called factory methods and conventionally
have names that begin with an uppercase letter.
• The first line in any JavaScript program that uses the Portable Graphics Library
creates a GWindow object using the following declaration:
let gw = GWindow(GWINDOW_WIDTH, GWINDOW_HEIGHT);
Review questions
1. What is the name of the JavaScript library used in this chapter to implement
programs that produce graphical output?
2. In your own words, define the terms class, object, and method.
3. What is a reference?
7. What is the first line in every graphical program that appears in this book?
8. What are the four classes of graphical objects introduced in this chapter?
10. What is the purpose of the setFilled and setFillColor methods in the
GRect and GOval classes?
12. Define the following terms in the context of the GLabel class: baseline, origin,
height, ascent, and descent.
13. Explain the purpose of the two following lines in the CenteredHelloWorld
function:
let x = ([Link]() - [Link]()) / 2;
let y = ([Link]() + [Link]()) / 2;
Why is there a minus sign in the calculation of the x coordinate and a plus sign
in the calculation of the y coordinate?
14. When you center a GLabel vertically using the getAscent method, why does
the resulting text often appear to be a few pixels too low?
16. What is meant by the term stacking order? What other term is often used for
the same purpose?
Exercises 147
Exercises
1. Use your program editor to create the program [Link] and
its associated [Link] file exactly as they appear in Figures 4-1 and 4-2.
Open the [Link] file in your browser to show that you can get graphical
programs working.
The size of the board should be specified as a constant, and the diagram should
be centered in the window, both horizontally and vertically.
3. Draw a simplified version of Figure 4-7, which illustrates the geometry of the
GLabel class. In your implementation, you should display the two strings
("The quick brown fox" and "jumped over the lazy dog") in red using a
sans-serif font that is large enough to make the guidelines easy to see. Then for
each of the strings, you should draw a gray line along the baseline, the line that
marks the font ascent, and the line that marks the font descent. Finally, you
should draw a small filled circle indicating the baseline origin of the first string.
The graphics window will then look like this:
This output is a little more honest than Figure 4-7 about the font ascent, which
appears slightly above the top of the uppercase characters.
148 Simple Graphics
4. Use the graphics library to draw a rainbow that looks something like this:
Starting at the top, the seven bands in the rainbow are red, orange, yellow,
green, blue, indigo, and violet, respectively; cyan makes a lovely color for the
sky. Remember that this chapter defines only the GRect, GOval, GLine, and
GLabel classes and does not include a graphical object that represents an arc.
It will help to think outside the box, in a more literal sense than usual.
5. If you think that the output produced by [Link] in Figure 4-11 seems
a bit mundane, you might instead want to draw a diagram of the House of
Usher, which Edgar Allan Poe describes as follows:
With the first glimpse of the building, a sense of insufferable gloom
pervaded my spirit. . . . I looked upon the scene before me—upon
the mere house, and the simple landscape features of the domain—
upon the bleak walls—upon the vacant eye-like windows . . . upon a
few white trunks of decayed trees—with an utter depression of soul.
From Poe’s description, you might draw a house that looks something like this:
Exercises 149
The figure on the left is the house with its “vacant eye-like windows” and the
three figures on the right are a stylized rendition of the “few white trunks of
decayed trees.”
6. Write a program that displays a pyramid on the graphics window. The pyramid
consists of bricks in horizontal rows, arranged so that the number of bricks in
each row decreases by one as you move upward, as follows:
The pyramid should be centered in the window both horizontally and vertically
and should use constants to define the dimensions of each brick and the height
of the pyramid.
The other change in this program is that the color of the dark squares has been
changed from black to gray so that the black checkers are not lost against the
background.
150 Simple Graphics
9. Rewrite the [Link] program from Figure 4-14 so that the number and radii
of the circles are controlled by the following constants:
const N_CIRCLES = 7;
const OUTER_RADIUS = 75;
const INNER_RADIUS = 10;
Given those values, the program should generate the following display:
10. Classical optical illusions offer a rich source of interesting graphical exercises.
One of the simplest examples is the Müller-Lyer illusion, named after the
German sociologist Franz Karl Müller-Lyer, who first described the effect in
1889. In one of its more common forms, the Müller-Lyer illusion asks the
viewer which of the two horizontal lines is longer in the following figure:
Most people are convinced that the bottom line is longer, but the two lines are
in fact the same length.
11. Another illusion that shows how context affects the perception of relative size
is the Ebbinghaus illusion, which was discovered by the German psychologist
Hermann Ebbinghaus and published in a 1901 book by the British psychologist
Exercises 151
Edward Tichener. This illusion, which appears in Figure 4-15, makes it seem
as if the central circle on the left is smaller than the circle on the right, even
though the two are the same size. Write a program to produce this illusion.
12. Write a program to produce the Zöllner illusion, which was discovered by the
German astrophysicist Johann Karl Friedrich Zöllner in 1860. In this illusion,
the diagonal lines that run in opposite directions on every other line make it
difficult to see that the horizontal lines are actually parallel:
13. An even more exotic illusion is the kindergarten illusion (also called the café
wall illusion), which was first described by the American psychologist Arthur
Henry Pierce in 1898. In this illusion, shifting the squares slightly on each row
of a checkerboard pattern makes the horizontal lines of the checkerboard appear
slanted instead of straight. Starting with the [Link] program in
Figure 4-13, make the changes necessary to produce the following image:
152 Simple Graphics
14. The scintillating grid illusion shown in Figure 4-16 was popularized by Elke
Lingelbach in the 1990s and is based on an earlier illusion published by
Ludimar Hermann in 1870. In this illusion, the viewer sees black dots inside
the white circles at the intersections of the grid. Write a program that replicates
this illusion.
15. Our visual sense is powerfully affected by our assumptions about an image. In
1911, the Italian psychologist Mario Ponzo showed that people expect objects
viewed at a distance in a perspective drawing to appear smaller. If an object
appears to violate the rules of perspective, our minds compensate by changing
our perception of its size.
Exercises 153
In one of its more popular forms, the Ponzo illusion illustrates this principle
by superimposing two horizontal lines onto a stylized image of a railroad track
receding into the distance. Since our experience assures us that the rails are
equally far apart all the way down the track, a line that crosses it must be larger
than one that falls entirely inside it, as illustrated in the following example:
16. Back in the early 1990s—long before JavaScript existed—Julie Zelenski and
Katie Capps Parlante developed a lovely graphics assignment that we used in
Stanford’s introductory course for many years. The goal of the assignment was
to draw a sampler quilt, which is composed of several different block types that
illustrate a variety of quilting styles.
For this exercise, your job is to use the graphics library to create the sampler
quilt shown in Figure 4-18. This quilt is composed of a repeating pattern of the
following four blocks, three of which are examples of previous work:
The only new block is the fourth one, which is a classic quilting pattern called a
log cabin block. This block is composed of rectangles that spiral inward
toward a square in the center. The width of each rectangle and the width of the
central square are all the same, which means that the dimensions are
determined by the block size and the number of frames in the spiral.
CHAPTER 5
Functions
Our module structure is based on the decomposition
criteria known as information hiding. According to this
principle, system details that are likely to change
independently should be the secrets of separate modules.
— David Parnas, Paul Clements, and David Weiss,
“The modular structure of complex systems,” 1984
David Parnas is Professor of Software Engineering emeritus at the University of Limerick in Ireland, where
he directed the Software Quality Research Laboratory, and has also taught at universities in Germany,
Canada, and the United States. His most influential contribution to software engineering is his
groundbreaking 1972 paper entitled “On the criteria to be used in decomposing systems into modules,”
which provided much of the foundation for the strategy of decomposition described in this chapter.
Professor Parnas also attracted considerable public attention in 1985 when he resigned from a Department
of Defense panel investigating the software requirements of the proposed Strategic Defense Initiative—
more commonly known as “Star Wars”—on the grounds that the requirements of the system were
impossible to achieve. For his courageous stand in bringing these problems to light, Parnas received the
1987 Norbert Wiener Award from Computer Professionals for Social Responsibility.
156 Functions
This chapter examines in more detail the concept of a function, which was initially
presented in Chapter 1 and then revisited in the context of JavaScript in Chapter 2.
A function is a set of statements that have been collected together and given a name.
Because functions allow the programmer to invoke the entire set of operations using
a single name, programs become much shorter and much simpler. Without
functions, programs would become unmanageable as they increased in size and
sophistication.
When you are first learning about programming, the best approach is usually to
alternate between these two perspectives. Taking the holistic view helps sharpen
your intuition about the programming process and enables you to stand back from a
program and say, “I understand what this function does.” Taking the reductionistic
view allows you to say, “I understand how this function works.” Both perspectives
are essential. You need to understand how functions work so that you can code
them correctly. At the same time, you must be able to take a step backward and
look at functions holistically, so that you also understand why they are important
and how to use them effectively.
These syntactic patterns are illustrated in the definition of the max function from
Chapter 3, which looks like this:
function max(x, y) {
if (x > y) {
return x;
} else {
return y;
}
}
This function has the name max and takes two parameters, x and y. The statements
in the body decide which of these two values is larger and then return that value.
Functions, however, are often called simply for their effect and need not return
an explicit value. For example, the Karel functions in Chapter 1 and the JavaScript
functions that implement complete programs don’t include a return statement.
Some languages distinguish a function that returns a value from one that doesn’t by
calling the latter a procedure. JavaScript uses the term function for both types.
This terminology is technically accurate because JavaScript functions always return
a value, which is the special value undefined if no return statement appears.
Parameter passing
The most important thing to remember about the process of calling a function is that
the values of the arguments are copied to the parameter variables in the order in
which they appear. The first argument is assigned to the first parameter variable,
the second argument to the second parameter variable, and so on. The names of the
parameters have nothing to do with the order in which their values are assigned.
There may well be a variable named x in both the calling function and in the
parameter list for the function being called. That reuse of the same name, however,
is merely a coincidence. Local variable names and parameter names are visible
only inside the function in which their declarations appear.
Unlike most modern languages, JavaScript does not check whether the number
of arguments in the call matches the number of parameters specified for the
158 Functions
function. If there are too many arguments, the extra ones are not assigned to any of
the parameters and are therefore essentially ignored. If there are too few, any
parameters that don’t have a corresponding argument are set to undefined.
Optional parameters
In early versions of the language, one of the primary uses of JavaScript’s rule for
missing arguments was to allow a function to take optional parameters. To test
whether the caller supplied an argument, the function could check the corresponding
parameter variable to see if it is undefined and, if so, substitute a default value.
Modern versions of JavaScript allow a function to specify default values by
including an equal sign and the value in the parameter list. For example, the
following function displays n consecutive integers, beginning with the value start
if two arguments are supplied and with 1 if the second argument is missing:
The following console session illustrates the operation of count, both when it is
given a second argument and when it is not:
Predicate functions
As you have seen in the earlier chapters, functions can return values of different
data types. For example, the monthName on page 90 returns a string, the function
fact on page 95 returns a number, and the function createFilledCircle on
page 144 returns a GOval. Although functions in JavaScript can return values of
any type, functions that return Boolean values deserve special attention because
they play such an important role in programming. Functions that return Boolean
values are called predicate functions.
As noted earlier in the chapter, there are only two Boolean values: true and
false. Thus a predicate function—no matter how many arguments it takes or how
complicated its internal processing may be—must eventually return one of these
5.1 A quick review of functions 159
function isEven(n) {
return n % 2 === 0;
}
A number is even if there is no remainder when you divide that number by two. If n
is even, the expression n % 2 === 0 has the value true, which is returned as the
value of isEven. If n is odd, the function returns false. Because isEven returns
a Boolean result, you can use it directly in a conditional context. For example, the
following for loop uses isEven to display all the even numbers between 1 and
100, inclusive:
The for loop runs through each number, and the if statement asks the simple
question “is this number even?” If it is, the program calls [Link] to display
the number; if not, nothing happens.
If you were writing a program that works with dates, it would be useful to have a
predicate function isLeapYear to determine whether a given year qualifies as a
leap year. Although one tends to think of leap years as occurring once every four
years, astronomical realities are not quite so tidy. Because it takes about a quarter
of a day more than 365 days for the earth to complete its orbit, adding an extra day
once every four years helps keep the calendar in sync with the sun, but it is still off
by a slight amount. To ensure that the beginning of the year does not slowly drift
through the seasons, the rule used for leap years is in fact more complicated. Leap
years come every four years, except for years ending in 00, which are leap years
only if they are divisible by 400. Thus, 1900 was not a leap year even though 1900
is divisible by 4. The year 2000, on the other hand, was a leap year because 2000 is
divisible by 400. For any leap year, one of the following conditions must hold:
function isLeapYear(year) {
return ((year % 4 === 0) && (year % 100 !== 0)) ||
(year % 400 === 0);
}
160 Functions
Once the function is defined, you can test for leap years like this:
if (isLeapYear(year)) . . .
5.2 Libraries
Writing a program to solve a large or difficult problem inevitably forces you to
manage at least some amount of complexity. There are algorithms to design,
special cases to consider, user requirements to meet, and innumerable details to get
right. To make programming manageable, you must reduce the complexity of the
programming process as much as possible. Functions reduce some of the
complexity; libraries offer a similar reduction in programming complexity but at a
higher level of detail. A function gives its caller access to a set of steps that
implements a single operation. A library provides a collection of tools that share a
common model. That model and its conceptual foundation constitute a
programming abstraction.
Once you have created the [Link] library, you can then use it in much the
same way that you use any other library. All you need to do is add the line
<script src="[Link]"></script>
to the [Link] file. Your JavaScript program will then have access to the
constants for the month names and the functions monthName and isLeapYear. In
computer science terminology, the [Link] library exports these constants and
functions, which are collectively called entries.
[Link]
162 Functions
Knowing how to call the isLeapYear function and knowing how to implement
it are both important skills. It is useful to keep in mind, however, that those two
skills—calling a function and implementing one—are to a large extent independent.
Successful programmers often use functions that they wouldn’t have a clue how to
write. Conversely, programmers who implement a library function can never
anticipate all the potential uses for that function.
Both functions and libraries offer a tool for hiding lower-level implementation
details so that clients need not worry about them. In computer science, this
technique is called information hiding. The fundamental idea, championed by
David Parnas in the early 1970s, is that the complexity of programming systems is
best managed by making sure that details are visible only at those levels of the
program at which they are relevant. For example, only the programmers who
implement isLeapYear need to know the details of its operation. Clients who
merely use isLeapYear can remain blissfully unaware of the underlying details.
When you design an interface for a library, you should try to protect the client
from as many of the complicating details of the implementation as possible. In
doing so, it is perhaps best to think of an interface not as a communication channel
between the client and the implementation, but instead as a wall that divides them.
Like the wall that divided the lovers Pyramus and Thisbe in Greek mythology,
the wall representing an interface contains an opening or chink that allows the two
5.3 A library to support randomness 163
The starting point for the [Link] library is the [Link] function,
which was included in Figure 2-3 in the list of functions available in the Math class.
Calling [Link] returns a number in the range beginning at 0 and extending
up to but not including the value 1. Every now and then, there is an application for
which you need random values in precisely this range. More often than not,
however, you would like to generate random values in a more general way. For
example, if you are working on a game program that involves rolling a die, you
would like to be able to produce a random integer between 1 and 6, inclusive. To
simulate flipping a coin, it would be useful to have a function that produced a
Boolean value so that true and false occurred with equal probability.
As you can see from the comments in Figure 5-2, the randomInteger function
takes two integers and returns an integer in the inclusive range that extends from the
first argument to the second. If you wanted to simulate a die roll, you would call
randomInteger(1, 6)
The body of the randomInteger function fits on a single line, but it takes some
thought to understand what that line does. For any values of the parameters low
and high, the randomInteger function returns the following expression:
It is probably easiest to examine this expression from the inside out. The function
call to [Link] returns a random number that can be as small as 0 but is
always strictly less than 1. In mathematics, a range of real numbers that can be
equal to one endpoint but not the other is called a half-open interval. On a number
5.3 A library to support randomness 165
[Link]
166 Functions
line, a half-open interval is marked using an open circle to show that the endpoint is
excluded, like this:
This text follows the standard conventions of mathematics by using square brackets
to indicate closed ends of intervals and parentheses to indicate open ends. Thus, the
notation [0, 1) indicates the half-open interval corresponding to this diagram.
The next step is to multiply the random value in the [0, 1) interval by the number
of possible outcomes, which is given by the expression
(high - low + 1)
Having to add 1 after subtracting low from high might at first seem confusing, but
this situation is analogous to the fencepost problem introduced in Chapter 1.
Subtracting low from high gives the distance between these points, which therefore
corresponds to the length of the fence. The number of possible outcomes is the
number of integers covered by the range, keeping in mind that defining the range to
be inclusive means that there is an integer—corresponding to a fencepost—at each
end. In the die roll example, the length of the range is 5, but the number of possible
outcomes is 6. Multiplying the result of [Link] by 6 produces a real
number in the [0, 6) range, as follows:
The code for randomInteger then uses [Link] to convert the real number to
an integer by rounding it down to the next smallest whole number.
The last remaining step is to add the value of low so that the set of possible
return values for randomInteger starts at the correct point, as illustrated on the
following number line in which only the solid dots represent possible values:
The implementation of randomReal follows much the same strategy as the code
for randomInteger but is simpler because it can leave out both the call to
[Link] and the adjustment of the range to avoid the fencepost problem, which
does not apply when real numbers are involved. The code is therefore simply
The function randomChance is used to simulate random events that occur with
some fixed probability. In accord with mathematical convention, a probability is
5.3 A library to support randomness 167
represented as a number between 0 and 1, where 0 means that the event never
occurs and 1 means that it always does. Calling randomChance(p) returns true
with probability p, where the parameter p has a default value of 0.5. Thus, calling
randomChance(0.75) returns true 75% of the time; calling randomChance()
returns true 50% of the time. You can use randomChance to simulate flipping a
coin, as illustrated by the following function, which returns "heads" or "tails"
with equal probability:
function flipCoin() {
return (randomChance() ? "heads" : "tails";
}
[Link]
5.3 A library to support randomness 169
1. The calling function computes values for each argument using the bindings of
local variables in its own context. Because the arguments are expressions, this
computation can involve operators and other functions; the calling function
evaluates these expressions before execution of the new function begins.
2. The system creates new space for all the local variables required by the new
function, including any parameters. These variables are allocated together in a
block, which is called a stack frame.
3. The value of each argument is copied into the corresponding parameter
variable. For functions with more than one argument, these copies occur in
order; the first argument is copied into the first parameter, and so forth. If there
are more arguments than parameters, the extra argument values play no role in
the initialization of the parameters. If there are more parameters than
arguments, those parameters that don’t have a corresponding argument are set
to the value undefined or a default value, if one is specified.
4. The statements in the function body are executed until the program encounters
a return statement or there are no more statements to execute.
5. The value of the return expression, if any, is evaluated and returned as the
value of the function.
6. The stack frame created for this function call is discarded. In the process, all
local variables disappear.
7. The calling program continues, with the returned value substituted in place of
the call. The point to which the function returns is called the return address.
Although this process may seem to make at least some sense, you probably need
to work through an example or two before you understand it fully. Reading through
the example in the next section will give you some insight into the process, but it
will be even more helpful to take one of your own programs and walk through it at
the same level of detail. And while you can trace through a program on paper or a
whiteboard, it may be best to get yourself a supply of 3 × 5 index cards and then use
5.4 The mechanics of function calls 171
a card to represent each stack frame. The advantage of the index-card model is that
you can create a stack of index cards that closely models the operation of the
computer. Calling a function adds a card; returning from the function removes it.
C(n, k) =
172 Functions
where the exclamation point indicates the factorial function, which you saw in
Chapter 3. The code to compute the combinations function in JavaScript appears in
Figure 5-6.
As you can see from Figure 5-6, the [Link] file contains two
functions. The combinations function computes the value of C(n, k), and the
now-familiar fact function computes factorials. A console session showing just
one call to the combinations function might look like this:
5.4 The mechanics of function calls 173
As always, the first step is to evaluate the arguments in the current context. In
this example, the arguments are the numbers 6 and 2, so the evaluation process
simply keeps track of these two values.
The second step is to create a frame for the combinations function that
contains space for the variables that are stored as part of that frame, which are the
parameters and any variables that appear in declarations within the function. The
combinations function has two parameters and no local variables, so the frame
only requires enough space for the parameter variables n and k. After the
JavaScript interpreter creates the frame, it copies the argument values into these
variables in order. Thus, the parameter variable n is initialized to 6, and the
parameter variable k is initialized to 2.
In the diagrams in this book, each stack frame appears as a rectangle surrounded
by a double line. Each stack-frame diagram shows the code for the function along
with a pointing-hand icon that makes it easy to keep track of the current execution
point. The frame also contains labeled boxes for each of the local variables. The
stack frame for the combinations function therefore looks like this after the
parameters have been initialized but before execution of the function begins:
To compute the value of the combinations function, the program must make
three calls to the function fact. In JavaScript, function calls are evaluated from left
to right, so the first call is the one to fact(n), as follows:
To evaluate this function, the system must create yet another stack frame, this
time for the function fact with an argument value of 6. The frame for fact has
174 Functions
both parameters and local variables. The parameter n is initialized to the value of
the calling argument and therefore has the value 6. The two local variables, i and
result, have not yet been initialized and therefore contain the value undefined,
which is indicated in stack diagrams as an empty box. The new frame for fact gets
stacked on top of the old one, which allows the JavaScript interpreter to remember
the values in the earlier stack frame, even though they are not currently visible. The
situation after creating the new frame and initializing the parameters looks like this:
The system then executes the statements in the function fact. In this instance,
the body of the for loop is executed six times. On each cycle, the value of result
is multiplied by the loop index i, which means that it will eventually hold the value
720 (1 × 2 × 3 × 4 × 5 × 6 or 6!). When the program reaches the return statement, the
stack frame looks like this:
Returning from a function involves copying the value of the return expression
(in this case the local variable result), to the point at which the call occurred. The
frame for fact is then discarded, which leads to the following configuration:
The next step in the process is to make a second call to fact, this time with the
argument k. In the calling frame, k has the value 2. That value is then used to
initialize the parameter n in the new stack frame, as follows:
5.4 The mechanics of function calls 175
The computation of fact(2) is easier to perform in one’s head than the earlier
call to fact(6). This time around, the value of result will be 2, which is then
returned to the calling frame, like this:
The code for combinations makes one more call to fact, this time with the
argument n - k. Evaluating this call therefore creates a new stack frame with n
equal to 4:
The value of fact(4) is 1 × 2 × 3 × 4, or 24. When this call returns, the system is
able to fill in the last of the missing values in the calculation, as follows:
The computer then divides 720 by the product of 2 and 24 to get the answer 15.
This value is returned to the JavaScript interpreter running in the JavaScript console
window. The interpreter prints that value on the console, like this:
176 Functions
function fact(n) {
let result = 1;
for (let i = 1; i <= n; i++) {
result *= i;
}
return result;
}
This implementation uses a for loop to cycle through the integers between 1 and n.
Strategies based on looping are said to be iterative. Functions like fact, however,
can also be implemented using a distinctly different approach that requires no loops
at all. This strategy is called recursion, which is the process of solving a problem
by breaking it down into simpler problems of the same form.
n ! = n × (n – 1)!
Thus, 4! is 4 × 3!, 3! is 3 × 2!, and so on. To make sure that this process stops at
some point, mathematicians define 0! to be 1. Thus, the conventional mathematical
definition of the factorial function looks like this:
1 if n = 0
n! =
n × (n – 1)! otherwise
function fact(n) {
if (n === 0) {
return 1;
} else {
return n * fact(n - 1);
}
}
If you trace through the logic the computer uses to evaluate any function call,
however, you discover that no magic is involved. When the computer evaluates a
call to the recursive fact function, it goes through the same process it uses to
evaluate any other function call.
To visualize the process, suppose that you have executed the statement
The computer now begins to evaluate the body of the function, starting with the
if statement. Because n is not equal to 0, control proceeds to the else clause,
where the program must evaluate and return the value of the expression
n * fact(n - 1)
Evaluating this expression requires computing the value of fact(n - 1), which
introduces a recursive call. When that call returns, all the program has to do is
multiply the result by n. It is therefore possible to diagram the current state of the
computation as follows:
The next step in the computation is to evaluate the call to fact(n - 1),
beginning with the argument expression. Because the current value of n is 4, the
argument expression n - 1 has the value 3. The computer then creates a new frame
for fact in which the formal parameter is initialized to this value. Thus, the next
frame looks like this:
5.5 Recursive functions 179
There are now two frames labeled fact. In the most recent one, the computer is
just starting to calculate fact(3). This new frame hides the previous frame for
fact(4), which will not reappear until the fact(3) computation is complete.
Computing fact(3) again begins by testing the value of n. Since n is still not
0, the else clause instructs the computer to evaluate fact(n - 1). As before, this
process requires the creation of a new stack frame evaluating fact(2). Following
the same logic, the program must then call fact(1), which in turn calls fact(0),
creating a total of three new stack frames, as follows:
At this point, however, the situation changes. Because the value of n is 0, the
function can return its result immediately by executing the statement
return 1;
This statement returns the value 1 to the calling frame, which resumes its position
on top of the stack, as shown:
180 Functions
From this point, the computation proceeds back through each of the recursive
calls, completing the calculation of the return value at each level. In this frame, for
example, the call to fact(n - 1) can be replaced by the value 1, as shown in the
diagram for the stack frame. In this stack frame, n has the value 1, so the result of
this call is simply 1. This result gets propagated back to its caller, as shown in the
following diagram:
At this stage, the program returns 3 × 2 to the preceding level, so that the frame for
the initial call to fact looks like this:
The final step in the process consists of calculating 4 × 6 and returning the value 24
to the initial call.
5.5 Recursive functions 181
n * fact(n - 1)
Replacing n with its value and then evaluating n - 1 makes it clear that the result is
4 * fact(3)
• Each pair of fertile rabbits produces a new pair of offspring each month.
• Rabbits become fertile in their second month of life.
• Old rabbits never die.
182 Functions
If a pair of newborn rabbits is introduced in January, how many pairs of rabbits are
there at the end of the year?
You can solve Fibonacci’s problem simply by keeping a count of the rabbits at
each month during the year. At the beginning of January, there are no rabbits, since
the first pair is introduced sometime in that month, which leaves one pair of rabbits
st
on February 1 . Since the initial pair of rabbits is newborn, they are not yet fertile
st
in February, which means that the only rabbits on March 1 are the original pair of
rabbits. In March, however, the original pair is now of reproductive age, which
means that a new pair of rabbits is born. The new pair increases the colony’s
st
population—counting by pairs—to two on April 1 . In April, the original pair goes
right on reproducing, but the rabbits born in March are as yet too young. Thus,
there are three pairs of rabbits at the beginning of May. From here on, with more
rabbits becoming fertile each month, the rabbit population begins to explode.
You can simplify the computation of further terms in this sequence by making an
important observation. Because in this problem pairs of rabbits never die, all the
rabbits that were around in the previous month are still around. Moreover, every
pair of fertile rabbits has produced a new pair. The number of fertile rabbit pairs
capable of reproduction is simply the number of rabbits that were alive in the month
before the previous one. The net effect is that each new term in the sequence must
simply be the sum of the preceding two. Thus, the next several terms in the
Fibonacci sequence look like this:
t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12
0 1 1 2 3 5 8 13 21 34 55 89 144
The number of rabbit pairs at the end of the year is therefore 144.
From a programming perspective, it helps to express the rule for generating new
terms in the following more mathematical form:
tn = tn-1 + tn-2
5.5 Recursive functions 183
The recurrence relation alone is not sufficient to define the Fibonacci sequence.
Although the formula makes it easy to calculate new terms in the sequence, the
process has to start somewhere. In order to apply the formula, you need to have at
least two terms already available, which means that the first two terms in the
sequence—t0 and t1—must be defined explicitly. The complete specification of the
terms in the Fibonacci sequence is therefore
n if n is 0 or 1
tn =
tn-1 + tn-2 otherwise
function fib(n) {
if (n === 0 || n === 1) {
return n;
} else {
return fib(n - 1) + fib(n - 2);
}
}
At this point, the computer calculates the result of fib(4), adds that to the result of
calling fib(3), and returns the sum as the value of fib(5).
But how does the computer evaluate fib(4) and fib(3)? The answer, of
course, is that it uses precisely the same strategy it did to calculate fib(5). The
184 Functions
essence of recursion is to break problems down into simpler ones that can be solved
by calls to exactly the same function. Those calls get broken down into simpler
ones, which in turn get broken down into even simpler ones, until at last the simple
cases are reached.
On the other hand, it is best to regard this entire mechanism as irrelevant detail.
Instead, just remember the recursive leap of faith. Your job at this level is to
understand how the call to fib(5) works. In the course of walking though the
execution of that function, you have managed to transform the problem into
computing the sum of fib(4) and fib(3). Because the argument values are
smaller, each of these calls represents a simpler case. Applying the recursive leap
of faith, you can assume that the program correctly computes each of these values,
without going through all the steps yourself. For the purposes of validating the
recursive strategy, you can just look the answers up in the table: fib(4) is 3 and
fib(3) is 2. The result of calling fib(5) is therefore 3 + 2, or 5, which is indeed
the correct answer. Case closed. You don’t need to see all the details, which are
best left to the computer.
As is often the case when using recursion, the key to finding a more efficient
solution lies in adopting a more general approach. The Fibonacci sequence is not
the only sequence whose terms are defined by the recurrence relation
tn = tn-1 + tn-2
5.5 Recursive functions 185
Depending on how you choose the first two terms, you can generate many different
sequences. The traditional Fibonacci sequence
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, . . .
comes from defining t0 = 0 and t1 = 1. If, for example, you defined t0 = 3 and t1 = 7,
you would get this sequence instead:
3, 7, 10, 17, 27, 44, 71, 115, 186, 301, 487, 788, 1275, . . .
Both sequences use the same recurrence relation, which specifies that each new
term is the sum of the preceding two. The only way the sequences differ is in the
choice of the first two terms. As a general class, sequences that follow this pattern
are called additive sequences.
If you had such a function, it would be easy to implement fib using it. All you
would need to do is supply the correct values of the first two terms, as follows:
function fib(n) {
return additiveSequence(n, 0, 1);
}
The body of fib consists of a single line of code that does nothing but call another
function, passing along a few extra arguments. Functions of this sort, which simply
return the result of another function, often after transforming the arguments in some
way, are called wrapper functions. Wrapper functions are common in recursive
programming. In most cases, a wrapper function is used, as it is here, to supply
additional arguments to a subsidiary function that solves a more general problem.
But what if you are asked to find a term further down in the sequence? The
recurisve insight you need is that the n th term in any additive sequence is simply the
n–1st term in the additive sequence that begins one step further along. This insight
makes it possible to implement the function additiveSequence as follows:
If you trace through the steps in the calculation of fib(5) using this technique,
you will discover that the calculation involves none of the redundant computation
that made the earlier recursive formulation so inefficient. The steps lead directly to
the solution, as shown in the following diagram:
fib(5)
= additiveSequence(5, 0, 1)
= additiveSequence(4, 1, 1)
= additiveSequence(3, 1, 2)
= additiveSequence(2, 2, 3)
= additiveSequence(1, 3, 5)
= 5
Summary 187
Summary
In this chapter, you learned about functions, which enable you to refer to an entire
set of operations with a single name. More importantly, by allowing the
programmer to ignore the internal details and concentrate only on the effect of a
function as a whole, functions provide a critical tool for reducing the conceptual
complexity of programs.
Review questions
1. Define the following terms as they apply to functions: call, argument, return.
3. Can there be more than one return statement in the body of a function?
6. What is an interface?
10. How would you use the randomInteger function to generate a randomly
chosen integer between 1 and 100?
11. By going through the code by hand, determine whether the randomInteger
function works with negative arguments. What are the possible results of
calling the function randomInteger(-5, 5)?
12. If you ran the [Link] program shown in Figure 5-4, you would
expect to see 10 circles on the graphics window because N_CIRCLES has the
value 10. In fact, you sometimes see fewer circles. Why might this be?
13. Describe the rules by which JavaScript assigns argument values to parameters.
14. Variables declared within a function are called local variables. What is the
significance of the word local in this context?
17. Describe the difference between the strategies of iteration and recursion.
18. What is meant by the phrase recursive leap of faith? Why is this concept
important for you as a programmer?
19. In the section entitled “Tracing the recursive process,” the text goes through a
long analysis of what happens internally when fact(4) is called. Using this
section as a model, trace the execution of fib(3), sketching out each stack
frame created in the process.
21. How many times is fib(1) called if you compute fib(10) using the recursive
implementation on page 183?
22. What is a wrapper function? Why are wrapper functions often useful in writing
recursive functions?
Exercises
1. In contrast to most languages, JavaScript has few built-in facilities to support
the creation of formatted tables, such as those in which numbers line up nicely
in columns. To create this kind of formatted table, it is useful to create a library
190 Functions
3. Heads. . . .
Heads. . . .
Heads. . . .
A weaker man might be moved to re-examine his faith, if in
nothing else at least in the law of probability.
—Tom Stoppard, Rosencrantz and Guildenstern Are Dead, 1967
Write a function consecutiveHeads(numberNeeded) that simulates tossing
a coin repeatedly until the specified number of heads appear consecutively. At
that point, your program should display a line on the console that indicates how
many coin tosses were needed to complete the process. The following console
log shows one possible execution of the program:
4. I shall never believe that God plays dice with the world.
—Albert Einstein, 1947
simulates what happens over time to a sample that contains 10,000 atoms of
some radioactive material, where each atom has a 50 percent chance of
decaying in a year. Your function should produce a trace on the console
showing how many atoms remain at the end of each year until all of the atoms
have decayed. For example, the output of your function might look like this:
As the numbers indicate, roughly half the atoms in the sample decay each year.
In physics, the conventional way to express this observation is to say that the
sample has a half-life of one year.
≅ = =
< 1
If you perform this simulation many times and compute what fraction of the
darts falls inside the circle, the result will be an approximation of π/4.
Write a program that simulates throwing 10,000 darts and then uses the
results to display an approximate value of π. Don’t worry if your answer is
correct only in the first few digits. The strategy used in this problem is not
particularly accurate, even though it occasionally proves useful as an
approximation technique. In mathematics, this technique is called Monte Carlo
integration, after the capital city of Monaco, famous for its casinos.
6. The combinations function C(n, k) determines the number of ways you can
choose k values from a set of n elements, ignoring the order of the elements. If
the order of the value matters—so that, in the case of the coin example,
choosing a penny and then a dime is seen as distinct from choosing a dime and
then a penny—you need to use a different function, which computes the
number of permutations, which are all the ways of ordering k elements taken
from a collection of size n. This function is denoted as P(n, k), and has the
following mathematical formulation:
P(n, k) =
Exercises 193
even though the answer is the much more manageable 2652 (52 × 51).
7. The values of the combinations function C(n, k) described in this chapter are
often displayed using a triangular arrangement that begins
and then continues for as many rows as desired. This figure is called Pascal’s
Triangle after its inventor, the seventeenth-century French mathematician
Blaise Pascal. Pascal’s Triangle has the interesting property that every interior
entry is the sum of the two entries above it.
The interesting challenge in this assignment is aligning the output, for which
the various functions you wrote for exercise 1 will come in handy.
8. The fact that every entry in Pascal’s Triangle is the sum of the two entries
above it makes it possible to calculate C(n, k) recursively. Use this insight to
write a recursive implementation of the combinations function without using
any loops or calls to fact.
10. Rewrite the fib function so that it operates iteratively rather than recursively.
11. Rewrite the digitSum function from page 93 so that it operates recursively
instead of iteratively. To do so, you need to identify both the simple cases and
the necessary recursive insight.
12. Rewrite the gcd function that uses Euclid’s algorithm shown on page 107 so
that it computes the greatest common divisor recursively using the following
rules:
Adele Goldberg received her Ph.D. in Information Science from the University of Chicago and took a
research position at the Xerox Palo Alto Research Center (PARC), which introduced the graphical user
interface—an idea that has since become central to modern computing. Together with others in the
Learning Research Group at PARC, Goldberg designed and implemented the programming language
Smalltalk, which took the ideas of object-oriented programming developed in Scandinavia and integrated
them into a programming environment designed to support constructivist learning in which students build
knowledge from their experiences. Drawing on the state-of-the-art technology invented at PARC,
Smalltalk was among the first programming environments designed for use with graphical displays. Along
with her colleagues Alan Kay and Dan Ingalls, Goldberg received the Software Systems Award from the
Association for Computing Machinery, the leading professional society for computer science, in 1987.
196 Writing Interactive Programs
So far, the only direct interactions you have had with JavaScript programs have
taken place in the context of the JavaScript console. When you type a function call
into the console window, the JavaScript interpreter then calls that function and
displays the result. That style of interaction is termed synchronous, because user
actions are synchronized with the program operation. A graphical user interface
(often shortened to the acronym GUI, which is pronounced like gooey), by contrast,
is asynchronous, in that it allows the user to intercede at any point, typically by
using the mouse or the keyboard to trigger an action. Actions that occur
asynchronously with respect to the program operation, such as clicking the mouse
or typing on the keyboard, are generically referred to as events. Interactive
programs that operate by responding to these events are said to be event-driven.
The primary goal of this chapter is to teach you how to write simple event-driven
programs.
Historically, the development of the graphical user interface has been closely
associated with the object-oriented paradigm, which is itself commonly abbreviated
as OOP. There are at least two reasons that the GUI and OOP have worked well
together (beyond the fact that they have both become popular three-letter buzzwords
in the computing industry). First, graphical displays are characterized by having
many independent objects that form a hierarchical relationship that fits easily into
the object-oriented paradigm. Second, it is easy to think of events as messages,
which are a central foundation of the object-oriented model. Clicking the mouse,
for example, sends a message to the application, which then responds in an
appropriate way.
In JavaScript, functions are values that are simultaneously part of both the
algorithmic structure and the data structure of a program. Given a functional value,
you can assign it to a variable, pass it as a parameter, or return it as a result. When a
programming language allows functions to behave just like any other data value,
computer scientists say that the language supports first-class functions.
6.1 First-class functions 197
function f(x) {
return x * x - 5;
}
JavaScript allows you to achieve a similar result using the following declaration:
This declaration introduces a new variable named f and initializes that variable to a
2
function that takes an argument x and returns the value x – 5. The syntax matches
that of any other JavaScript declaration and therefore takes a semicolon at the end.
The domain of the function data type is the vast spectrum of functions that you
might want to define in JavaScript. The operation that is particular to the function
data type is application, which is the process of calling that function with a list of
arguments. No matter which way you define the function f, you call it in the same
way, so that f(3) produces the value 4.
Although these two syntactic forms for function declaration are similar, there are
important differences. Declaring a function as a variable separates the name of the
function from its value. Although the functional value is stored in a variable named
f, the function itself is anonymous in the sense that it has no name. JavaScript
debuggers provide less information if you use the second form. Perhaps more
importantly, JavaScript defines every inner function declared using the first style
before it executes any of the statements of the enclosing block. This process is
called hoisting. As a result, the definition of inner functions can come at any point
within the body and need not interrupt its flow. In this book, inner functions are
usually defined at the end of the function or block of statements that contains them.
This first parameter is a function that takes a number and returns a result. The
effect of this function is to count from min to max, generating a line of output that
shows the value of the function at each of those values. For example, if f is defined
2
as shown in the preceding section to be f (x) = x – 5, calling
The function argument, however, can be any function, even one that comes from
a library. For example, calling
printFunctionTable([Link], 2, 9);
If you then go on to click the mouse in other positions, dots will appear there as
well. You could, for example, draw a picture of the constellation Ursa Major,
which is more commonly known as the Big Dipper. All you would have to do is
click the mouse once in the position of each star, as follows:
The code in Figure 6-1 is extremely short, with just a single top-level function
and a few constant definitions. Even so, the program is different enough from the
ones that you’ve seen so far that it makes sense to go through it in detail.
The first statement in the program simply creates the graphics window, precisely
as you always have. The next statement establishes the link between the graphics
window and the behavior specified by the clickAction function, which is defined
at the end of the function. By executing the line
[Link]("click", clickAction);
the program tells the graphics window that it wants to respond to mouse clicks.
Moreover, the response to that mouse click is specified by clickAction, which is
called automatically whenever a click occurs.
The body of DrawDots ends with the definition of the clickAction function,
which is defined everywhere inside DrawDots because of JavaScript’s policy of
hoisting functions described earlier in the chapter. The code in Figure 6-1 never
calls clickAction explicitly. The call, when it happens, comes from the code that
implements the graphics library. Functions that the program does not call directly
but that instead occur in response to some event are referred to as callback
functions. The name reflects the relationship between the client program and the
libraries it uses. As a client, your program calls addEventListener to register
interest in a particular event. As part of that process, you provide the library with a
function that it can call when the event occurs. It is, in a way, analogous to
providing a callback number. When the library implementation needs to call you
back, you’ve given it the means to do so.
6.3 Controlling properties of objects 201
Now that you have a sense of how callback functions work in general, you are in
a better position to understand the clickAction function, which looks like this:
function clickAction(e) {
let dot = GOval([Link]() - DOT_SIZE / 2,
[Link]() - DOT_SIZE / 2,
DOT_SIZE, DOT_SIZE);
[Link](true);
[Link](dot);
}
The function takes a parameter e, which provides the function with data about the
details of the event. In this case, e is a mouse event, which keeps track of the
location of the mouse along with other data. Callback functions that respond to
mouse events can determine the location of the mouse by invoking the methods
[Link]() and [Link](). Each of these methods returns a coordinate in pixels
measured relative to the origin in the upper left corner of the window.
The body of the clickAction function creates a GOval of the size specified by
DOT_SIZE, sets it to be filled, and then adds it to the window so that its center
appears as the current mouse position. The variable gw, which is a local variable
inside DrawDots, is accessible to the clickAction code because the definition of
that function appears inside the DrawDots body.
The classes in the graphics library export a richer set of methods than you have
had a chance to use so far. Figure 6-2 lists the complete set of methods supported
by every graphical object and a few that apply only to GRect, GOval, and GLine.
Each of the method descriptions consists of a single line that offers an overview of
what the method does. For more details, you can consult the web documentation for
the graphics library.
Instead of going through each of these methods in detail, this chapter presents
several programming examples that introduce new methods only as they are needed.
As a result, you have a chance to learn about each of the new methods in the context
of an application that makes use of it.
202 Writing Interactive Programs
[Link]
6.4 Responding to mouse events 203
The sections that follow offer several examples that illustrate conventional styles
of using the mouse to create and reposition objects in the graphics window.
Suppose, for example, that you press the mouse button somewhere on the screen
and then drag the mouse rightward an inch, holding the button down. What you’d
like to see is the following picture:
204 Writing Interactive Programs
If you then move the mouse downward without releasing the button, the
displayed line will track the mouse, so that you might see the following picture:
As you drag the mouse, the application repeatedly updates the line, making it
appear to stretch as the mouse moves. Because the effect is precisely what you
would expect if you joined the starting point and the mouse cursor with a stretchy
elastic line, this technique is called rubber-banding.
When you release the mouse, the line stays where it is. If you then press the
mouse button again on that same point, you can go ahead and draw an additional
line segment by dragging the mouse to the end point of the new line, as follows:
The code for this application, which appears in Figure 6-4, is remarkably short,
despite the fact that the program performs what seems like a sophisticated task. As
in the DrawDots program, the creation of the GWindow object at the beginning of
the program follows the standard convention. The program then declares a variable
called line that will keep track of the current line. When the program starts, there
is no line, and the program records that fact by setting its initial value to the special
6.4 Responding to mouse events 205
value null, which is used in JavaScript to indicate an object reference that doesn’t
yet exist. The value of line is set in the code for the "mousedown" event and
updated in the code for the "drag" event. The fact that one function sets this value
and another function updates it means that the variable line must be declared in the
DrawLines function so that both of the event-handling functions have access to it.
[Link]
6.4 Responding to mouse events 207
display. This program begins by adding a blue rectangle and a red oval to the
window, just as in the [Link] program from Chapter 4. The rest of
the main program represents the code pattern for dragging objects.
function mousedownAction(e) {
lastX = [Link]();
lastY = [Link]();
objectBeingDragged = [Link](lastX, lastY);
}
The first two statements simply record the x and y coordinates of the mouse in the
variables lastX and lastY. As you can see from the program, these variables are
declared as local variables in the enclosing DragObjects function, because the
program needs these values in the callback function that responds when the user
drags the mouse.
function dragAction(e) {
if (objectBeingDragged !== null) {
[Link]([Link]() - lastX,
[Link]() - lastY);
lastX = [Link]();
lastY = [Link]();
}
}
208 Writing Interactive Programs
The if statement checks to see whether there is an object to drag. If the value of
objectBeingDragged is null, there is nothing to drag, so the rest of the function
can just be skipped. If there is an object, you need to move it by some distance in
each direction. That distance does not depend on the current coordinates of the
mouse but rather on how far it has moved from where it was when you last noted its
position. Thus, the arguments to the move method are—for both the x and y
components—the location where the mouse is now minus where the mouse was at
the time of the last event. Those coordinates are stored in the variables lastX and
lastY. Once you have moved the object, you must then update the values of these
variables to ensure that they are correct for the next call to mouseDragged.
The only other feature in the [Link] program is that the application
also registers its interest in "click" events, which trigger a call to the following
function:
function clickAction(e) {
if (objectBeingDragged !== null) {
[Link]();
}
}
The point of adding this function is to allow the user to change the stacking order,
which, as noted in Chapter 2, is the order in which objects are layered on the screen.
setTimeout(function, delay)
creates a one-shot timer that calls function after delay milliseconds. The function
setInterval(function, delay)
creates an interval timer that calls function repeatedly every delay milliseconds. In
each case, the function returns a numeric value that allows subsequent code to
identify the timer. If you store this numeric value in a variable, you can then call
clearTimeout or clearInterval (depending on the type of timer) to stop the
timer process. Thus, executing
creates an interval timer and stores its identifying number in the variable timer.
The interval timer then begins generating calls to the function step once every 20
milliseconds, or every fiftieth of a second. The name step is chosen here to
suggest that each call represents a single step in the animation, which is called a
time step. The step function receives no arguments, so any information it needs
must be communicated through the local variables of the function in which step is
defined.
Timers that initiate events every 20 milliseconds allow you to change the state of
the graphics window quickly enough so that the changes seem smooth to the human
eye. You can therefore move an object on the screen by creating an interval timer
that executes its callback function every 20 milliseconds and then having the
callback function make an incremental change to the position of that object. When
the object reaches the desired final location, your program can then stop the timer
by calling
clearInterval(timer);
function step() {
[Link](dx, dy);
stepCount++;
if (stepCount === N_STEPS) clearInterval(timer);
}
The first line adjusts the position of the square by the values dx and dy. The second
line increments the value of stepCount. The third line checks to see whether
stepCount has reached the limit and, if so, stops the timer. The step function has
access to the variables square, dx, dy, stepCount, and timer because these are
local variables in AnimatedSquare.
It is, of course, tempting to start this program by building on the earlier example.
That strategy would suggest adopting the following pseudocode structure inside the
main program:
to completion before any events occur. After that, events completely determine
how the application proceeds. JavaScript’s event model requires a different
approach, in which the step function that implements the animation has to keep
track of the size of the current circle and determine when it has reached full size.
When it has, the step function—and not the main program, which has stopped
running by this point—must create the next circle. The step function therefore has
the following pseudocode form:
function step() {
if (the current circle is still growing) {
Increase the size of the current circle.
} else if (there are more circles to create) {
Create another circle.
} else {
clearInterval(timer);
}
}
The code for the [Link] program that uses this structure appears
in Figure 6-7. The code for the createNewCircle function is largely the same as
the code for createRandomCircle in Figure 5-4. The only differences are that
The code for the step function follows the pseudocode outline shown earlier on
this page. The only new feature is the call to the setBounds method, which resets
the location and size of the current circle so that it grows by one pixel in each time
step.
[Link]
214 Writing Interactive Programs
The first four parameters specify the location and size of the rectangle that encloses
the arc and therefore have precisely the same interpretation as those parameters in
calls to GRect or GOval. The next two parameters specify the start angle, which is
the angle at which the arc begins, and the sweep angle, which is the number of
degrees through which the arc extends. In keeping with mathematical convention,
angles in the graphics library are measured in degrees counterclockwise from the +x
axis, as follows:
four sample runs in Figure 6-9 show the effect of the code that appears below each
diagram. The code fragments create and display arcs using different values for
start and sweep. Each of the arcs has a radius of r pixels and is centered at the
point (cx, cy). The last two examples show that the values of start and sweep
can be negative, in which case the angles extend in the clockwise direction.
The GArc class implements the methods shown in Figure 6-10. As you can see,
these methods include setFilled and setFillColor, just as GRect and GOval
do. It is not immediately apparent, however, exactly what filling an arc means. In
the interpretation of arc-filling used in the Portable Graphics Library, the unfilled
version of a GArc is not simply the boundary of its filled counterpart. If you display
an unfilled GArc, only the arc itself is shown. If you call setFilled(true) on
that arc, the graphics library connects the end points of the arc to the center from
216 Writing Interactive Programs
which the arc was drawn and then fills the interior of that region. The following
sample run illustrates the difference by showing both unfilled and filled versions of
the same 60-degree arc:
The important lesson to take from this example is that the geometric boundary of
a GArc changes if you set it to be filled. A filled arc is a wedge-shaped region that
has a well-defined interior. An unfilled arc is simply a section taken from the
boundary of an ellipse. If you want to display the outline of the wedge that calling
setFilled would generate, the simplest strategy is to call setFilled(true) and
then use setFillColor("White") to set the interior of the region to white.
The GPolygon class is easy to use if you keep the following points in mind:
• Unlike the functions that create the other shapes, the GPolygon function class
does not create the entire figure. What happens instead is that calling GPolygon
creates an empty polygon. Once you have created an empty polygon, you then
add vertices to it by calling various other methods described later in this section.
• The origin of a GPolygon is not defined to be its upper left corner. Many
polygons, after all, don’t have an upper left corner. What happens instead is that
you—as the programmer who is creating the specific polygon—choose a
reference point that defines the location of the polygon as a whole. You then
specify the coordinates for each vertex in terms of where they lie in relation to
the reference point. This approach makes it easier to move the polygon as a unit.
6.6 Expanding the graphics library 217
You can then add the diamond at the center of the graphics window by executing
the following statement:
When you use the addVertex method to construct a polygon, the coordinates of
each vertex are expressed relative to the reference point. In some cases, it is easier
to specify the coordinates of each vertex in terms of the preceding one. To enable
this approach, the GPolygon class offers an addEdge method, which is similar to
addVertex except that the parameters specify the displacement from the previous
vertex to the current one. You can therefore draw the same diamond by making the
following sequence of calls:
Note that the first vertex must still be added using addVertex, but that subsequent
ones can be defined by specifying the edge displacements.
As you work with the GPolygon class, you will discover that some polygons are
easier to define with successive calls to addVertex, while others are easier to
define using addEdge. For many polygonal figures, however, it is even more
convenient to define the edges using polar coordinates. The GPolygon class
supports this style through the method addPolarEdge. This method is identical to
addEdge except that its arguments are the length of the edge and its direction,
expressed in degrees counterclockwise from the +x axis.
The addPolarEdge method makes it easy to create figures in which you know
the angles of the edges but would need trigonometry to calculate the vertices. The
following function, for example, uses addPolarEdge to create a regular hexagon in
which the length of each edge is determined by the parameter side:
function createHexagon(side) {
let hex = GPolygon();
[Link](-side, 0);
let angle = 60;
for (let i = 0; i < 6; i++) {
[Link](side, angle);
angle -= 60;
}
return hex;
}
As always, the first vertex is added using addVertex. Here, the initial vertex is the
one at the left edge of the hexagon. The first edge then extends from that point at an
angle of 60 degrees. Each subsequent edge has the same length, but sets off at an
angle 60 degrees to the right of the preceding one. When all six edges have been
added, the final edge ends up at the original vertex, thereby closing the polygon.
Figure 6-11 lists the methods that apply to the GPolygon class. As with the
other bounded figures, GPolygon implements setFilled and setFillColor.
Although the star is more complicated mathematically than the earlier examples,
the most difficult part is determining the coordinates of the starting point at the left
edge of the star. Calculating the x coordinate is easy because the starting point is
simply half the width of the star to the left of its center. Calculating the distance in
the y direction requires a bit of trigonometry, which can be illustrated as follows:
Each of the points around the periphery of a five-pointed star forms an angle that is
a tenth of a complete circle, which is 36 degrees. If you draw a line that bisects that
angle—leaving 18 degrees on either side—that line will hit the geometric center of
the star, forming the right triangle shown in the diagram. The value of dy is
therefore equal to dx multiplied by the tangent of 18 degrees, as shown in the code.
The other tricky calculation is that of the edge length, which is illustrated in the
following diagram:
To determine the value of edge, you need to subtract the dotted portion of the
horizontal line from its entire length, which is given by dx. The length of the dotted
portion is easily computed using trigonometry as dy multiplied by the tangent of 36
degrees. Once you have computed these values, the rest of the createStar
function follows much the same pattern as the implementation of createHexagon.
[Link]
222 Writing Interactive Programs
sets the variable box so that it holds a new GCompound object that looks like this:
Like the GPolygon class, the GCompound class defines its own coordinate
system in which all coordinate values are expressed relative to a reference point.
This design has two advantages. First, separating the process of defining the shape
and setting its coordinates means that you can define a GCompound without having
to know exactly where it will appear. That property is particularly useful if the
location of an object in the graphics window depends on its size. Second, there are
often more appropriate choices to use as a reference point than the conventional
upper left corner. The createCrossedBox function, for example, returns a
GCompound in which the reference point is at the center, which is often a more
convenient choice. The code in Figure 6-13 can then place the crossed box at the
center of the window using the following code:
let cx = [Link]() / 2;
let cy = [Link]() / 2;
[Link](box, cx, cy);
Executing these statements creates the following image on the graphics window:
The rest of the RotateCrossedBox program in Figure 6-13 then uses a timer to
rotate the box through a full revolution in one-degree increments. The statement
[Link](1);
in the callback function rotates the GCompound one degree counterclockwise around
its reference point, which in this case is the center. After 360 steps, the callback
function calls clearInterval to stop the timer, leaving the crossed box back in its
original orientation.
Summary 223
Summary
In this chapter, you learned how to create interactive programs. The important
points introduced in this chapter include:
sweep angle that indicates how far the arc extends. Filled arcs appear as wedges
in which the endpoints of the arc are connected to the center.
• The GPolygon class makes it possible to display an arbitrary polygon. The
GPolygon function itself creates an empty polygon; you create the actual
polygon by calling some combination of the methods addVertex, addEdge,
and addPolarEdge.
• The GCompound class represents a graphical object that contains other graphical
objects. Creating a GCompound allows the collection to be treated as a unit.
• Both the GPolygon and GCompound classes use an internal coordinate system
relative to the object itself. This strategy makes it possible to create the object
without knowing where it will appear in the window.
Review questions
1. In the context of JavaScript, what is an event?
3. What reasons are offered in this chapter for the close association of graphical
user interfaces and object-oriented programming?
8. What are the two methods used in this chapter to get more specific information
about a mouse event?
10. How does a callback function typically share information with the function that
defines it?
12. What value does the getElementAt method return if no object exists at the
specified location?
Exercises 225
13. How does the getElementAt method decide which object to return if more
than one object covers the specified location?
14. Describe in your own words the strategy for implementing animation in
JavaScript.
16. What are the two library functions that create timers? How do they differ?
18. Describe the significance of the start and sweep parameters in the call to the
GArc function.
19. What does it mean if the sweep argument to the GArc constructor is negative?
20. Describe the arcs produced by each of the following calls to GArc, where cx
and cy are the coordinates of the center of the window and r has the value 100:
21. How does the GArc class interpret the notion of a filled arc?
22. Describe the differences between the methods addVertex, addEdge, and
addPolarEdge in the GPolygon class.
23. Which of the three methods listed in the preceding question is conventionally
used to add the first vertex to a GPolygon?
24. In your own words, describe the purpose of the GCompound class.
25. What advantages does the text cite for having the GPolygon and GCompound
classes define their own reference point?
Exercises
1. Drawing on the printFunctionTable function for inspiration, implement a
function
function plot(gw, f, xMin, xMax, yMin, yMax)
that plots the function f on the graphics window by creating small GLine
segments and adding them to the graphics window. The parameters xMin,
226 Writing Interactive Programs
xMax, yMin, and yMax specify a translation between the values passed to and
returned by the function and the window coordinates. The left edge of the
window, for example, should correspond to the value xMin in the domain of the
function.
For example, calling
plot(gw, [Link], -2 * [Link], 2 * [Link], -1, 1);
should generate a plot of the trigonometric sine function for values of x ranging
from −2π to +2π and displayed so that the vertical space in the window runs
from −1 at the bottom to +1 at the top (note that this interpretation requires you
to flip JavaScript’s coordinate system so that it matches the traditional
Cartesian model in which y values increase as you move upward). After you
make this call, the graphics window should look like this:
Similarly, calling
plot(gw, [Link], 0, 4, 0, 2);
should plot the [Link] function on a graph that extends from 0 to 4 along
the x-axis and from 0 to 2 along the y-axis, like this:
2. Modify the DrawDots program so that clicking the mouse draws a small ×
every time you click the mouse. The ×, which consists of two GLine objects,
should be positioned so that the intersection appears at the point where the
mouse was clicked.
Exercises 227
The rectangle grows as you drag the mouse. When you release the mouse
button, the rectangle is complete and stays where it is. You can then go back
and add more rectangles in the same way.
Although the code for this exercise is quite short, there is one important
consideration that you will need to take into account. In the example above, the
initial mouse click is in the upper left corner of the rectangle. Your program,
however, has to work just as well if you drag the mouse in some direction other
than right and down. For example, you should also be able to draw a rectangle
by dragging to the left, as shown in the following illustration:
4. Use the GOval, GLine, and GRect classes to create a cartoon drawing of a face
that looks like this:
228 Writing Interactive Programs
Once you have this picture, add a callback function for the "mousemove" event
so that the pupils in the eyes follow the cursor position. For example, if you
move the cursor to the lower right side of the screen, the pupils should shift so
that they appear to be looking at that point, as follows:
Although it doesn’t matter much when the cursor is outside the face, it is
important to compute the position of the pupil independently for each eye. If
you move the mouse between the eyes, for example, the pupils should point in
opposite directions so that the face appears cross-eyed.
5. Write a program that draws a filled black square in the center of the canvas.
Once you have that part of the program working, animate your program so that
the color of the square changes once a second to a new color chosen randomly
by calling the randomColor function in the [Link] library. Your
program should run for a minute and then stop.
8. Write a program that draws a picture of a pumpkin pie divided into equal
wedge-shaped pieces where the number of pieces is indicated by the constant
N_PIECES. Each wedge should be a separate GArc, filled in orange and
outlined in black. The following screen image, for example, shows the diagram
when N_PIECES is 6.
Once you have this display, add event processing to your application so that
clicking on any of the wedges removes that wedge from the display. For
example, if you click on the wedge in the upper right, the screen image should
look like this:
9. The title character in the PacMan series of games is easy to draw in Java using
a filled GArc. As a first step, write a program that adds a PacMan figure at the
left edge of the window, as follows:
Once you have this part working, add the code to make the PacMan figure
move rightward until it reaches the right edge of the graphics window. As
230 Writing Interactive Programs
PacMan moves, your program should change the start and sweep angles so that
the mouth appears to open and close as shown in the following image sequence:
10. The PacMan shape appears in several examples of an optical illusion called a
subjective contour, popularized in an article by the Italian psychologist
Gaetano Kanizsa in the April 1976 issue of Scientific American. The following
screen image shows an example of a subjective contour in which the rectangle
framed by the cutaway circles appears brighter than the background:
Although the simplest way to produce this picture is to draw a white rectangle
on top of four complete circles, a skeptic might claim that the color of the
rectangle is brighter than its background. Make it impossible to defend this
claim by drawing this figure using only the four filled arcs.
11. Another illusion that uses filled arcs is the Wudnt illusion, first described by
Wilhelm Max Wundt in 1898.
Exercises 231
In this illusion, the lower curve looks longer than the upper curve, although the
two are in fact the same size. Write a program that draws these segments using
the graphics library. To do so, you need to draw a filled arc, overlay it with a
smaller arc filled in white, and then complete the border with an unfilled arc.
12. Write a program that draws the following optical illusion on the graphics
window:
The illusion arises from the fact that it is possible to see the white surfaces as
either the tops or the bottoms of cubes stacked to form a pyramid.
In writing this exercise, you should create a function that returns one of these
cubes as a GCompound and then assemble the pyramid from those compounds.
13. In J. K. Rowling’s Harry Potter and the Deathly Hallows, those who believe in
the legend named in the title recognize one another through a symbol that
combines three elements—a triangle representing the cloak of invisibility, a
circle representing the stone of resurrection, and a line representing the elder
wand—superimposed as follows:
232 Writing Interactive Programs
14. Write a program to play the classic arcade game of Breakout, which was
developed in 1976 by Steve Wozniak, who would later become one of the
founders of Apple. In Breakout, your goal is to clear a collection of bricks by
hitting each of them with a bouncing ball.
As you can see from the middle diagram, the ball is about to collide with
one of the bricks on the bottom row. When that happens, the ball bounces just
as it does on any other collision, but the brick disappears (which you can
accomplish simply by removing it from the graphics window).
The play continues in this way until one of the following conditions occurs:
• The ball hits the lower wall, which means that you must have missed it
with the paddle. In this case, the turn ends and the next ball is served,
assuming that you have not already exhausted your allotment of three
turns. If you have, the game ends in a loss.
• The last brick is eliminated. In this case, the game ends immediately, and
you can retire victorious.
After all the bricks in a particular column have been cleared, a path will
open to the top wall, as shown in the rightmost diagram in Figure 6-14. When
this delightful situation occurs, the ball will often bounce back and forth several
times between the top wall and the upper line of bricks without the user ever
having to worry about hitting the ball with the paddle. This condition is called
“breaking out.” It is important to note that, even though breaking out is a very
exciting part of the player’s experience, you don’t have to do anything special
in your program to make it happen. The game operates the same as always:
balls bounce off walls, collide with bricks, and obey the laws of physics.
The only part of the implementation that requires some explanation is the
problem of checking to see whether the ball has collided with a brick or the
paddle. The getElementAt method can determine whether there is an object
at a particular position, but it doesn’t work well to check the coordinates of the
ball’s center because the ball is larger than a single point. In this program, the
simplest strategy is to check the four corner points on the square in which the
ball is inscribed. A collision occurs if any of those points are inside a brick.
15. In New York’s Times Square, you can get the news of the day by watching
headlines on large display screens that show a single line of text. The headline
initially begins to appear at the right edge of the screen and then moves quickly
from right to left. Your job in this exercise is to write a program that simulates
this type of headline display by moving a GLabel across the screen.
234 Writing Interactive Programs
Suppose, for example, that you want to use your program to display the
famous Chicago Tribune headline from when the paper incorrectly called the
result of the 1948 presidential election:
Your program should create a GLabel containing the headline and then
position it so that the entire text of the label is clipped beyond the right edge of
the screen. Your program should then implement a timer-based graphical
animation that moves the GLabel a few pixels to the left on each time step.
After a few time steps, the display will show the first letter of the headline, as
follows:
The headline continues to scroll across the screen, so that a few seconds later
the entire first word is visible:
As the label continues to scroll, letters will disappear off the left edge of the
screen as new letters appear on the right. Your program should continue to
scroll letters toward the left until the entire GLabel disappears from view.
CHAPTER 7
Strings
The work [of conducting the census should] be done so
far as possible by mechanical means. In order to
accomplish this the records must be put in such shape that
a machine could read them. This is most readily done by
punching holes in cards.
— Herman Hollerith, An Electric Tabulating
System, 1889
The idea of encoding text in machine-readable form dates back to the nineteenth century and the work of
the American inventor Herman Hollerith. After studying engineering at City College of New York and the
Columbia School of Mines, Hollerith spent a couple of years working as a statistician for the U. S. Census
Bureau before accepting a teaching position at MIT. While at the Census Bureau, Hollerith had become
convinced that the data produced by the census could be counted more quickly and accurately by machine.
In the late 1880s, he designed and built a tabulating machine that was used to conduct the 1890 census in
record time. The company he founded to commercialize his invention, originally called the Tabulating
Machine Company, changed its name in 1924 to International Business Machines (IBM). Hollerith’s
card-based tabulating system pioneered the technique of textual encoding described in this chapter—a
contribution that was reflected in the fact that early versions of the FORTRAN language used the letter H
(for Hollerith) to indicate text data.
236 Strings
Although you have been using strings ever since Chapter 2, you have only scratched
the surface of what you can do with string data. This chapter introduces the features
available in the JavaScript String class, which provides a convenient abstraction
for working with strings of characters. Understanding the various methods
available in this library will make it much easier to write interesting applications.
Before considering the details of the String class, however, it helps to take a step
back and look at how computers store data in the first place.
The interpretation of the values for each bit depends on how you choose to view
the underlying information. If you think of the bits that form the internal circuitry
of the machine as tiny light switches, you might label those states as off and on. If
you think of each bit as a logical value, you might instead use the Boolean labels
false and true. However, because the word bit comes from a contraction of the term
binary digit, it is more common to label those states as 0 and 1, which are the digits
of the binary number system on which computer arithmetic is based.
Binary notation
The idea of writing numbers in binary notation predates the development of the
electronic computer by more than 250 years. The German mathematician Gottfried
Wilhelm von Leibniz (1646–1716) offered a detailed account of the binary system
in a paper published by the French Royal Academy of Science in 1703. In that
paper, Leibniz writes:
neighbor on the right. That rule makes it easy to translate a number written in
binary back to its decimal equivalent: all you need to do is add the place values of
each digit in the number. For example, if Leibniz were to use binary notation to
represent the year of his birth, he would write the number like this:
The following diagram shows that this value indeed corresponds to the value 1646:
For the most part, numeric representations in this book use decimal notation for
readability. If the base is not clear from the context, the text follows the usual
strategy of using a subscript to denote the base. For example, the equivalence of the
binary value 11001101110 and the decimal value 1646 can be made explicit by
writing the numbers like this:
110011011102 = 164610
To get a sense of how computers can store integers internally, consider the byte
containing the following binary digits:
238 Strings
That sequence of bits represents the number forty-two, which you can verify—just
as Leibniz would have done—by calculating the contribution for each of the
individual bits, as follows:
8
Bytes can store integers between 0 and 255, which is 2 − 1. Numbers outside this
range must be stored in larger units that use more bits of memory.
Hexadecimal notation
Although the bit diagrams make it clear how computers store integer values
internally, these diagrams also demonstrate the fact that writing numbers in binary
form is terribly inconvenient. Binary numbers are cumbersome, mostly because
they tend to be so long. Decimal representations are intuitive and familiar but make
it harder to understand how the number translates into bits.
A = 10
B = 11
C = 12
D = 13
E = 14
F = 15
What makes hexadecimal notation useful is the fact that you can easily convert
between hexadecimal values and the underlying binary representation. All you need
to do is combine the bits into groups of four. For example, the number forty-two
can be converted from binary to hexadecimal like this:
7.1 Binary representation 239
The first four bits represent the number 2, and the next four represent the number
10. Converting each of these to the corresponding hexadecimal digit gives 2A as
the hexadecimal form. You can then verify that this number still has the value 42
by adding up the digit values, as follows:
For the most part, numeric representations in this book use decimal notation for
readability. As noted earlier, the text follows the convention of using a subscript to
denote the base if it is not clear from context. Thus, the three most common
representations for the number forty-two—decimal, binary, and hexadecimal—look
like this:
The most important thing to remember, however, is that the number itself is
always the same; the numeric base affects only the representation. Forty-two has an
intrinsic meaning that is independent of the base, which is perhaps easiest to see in
the representation an elementary school student might use:
The number of tick marks in this representation is forty-two. The fact that a number
is written in binary, decimal, or any other base is a property of the representation,
not of the number itself. Numbers do not have bases; representations do.
Once you have enumerated a set of values, you can represent those values in
memory by using the appropriate numeric code. For example, the numeric value 12
corresponds to the month of December. Internally, that value is stored as an integer
expressed—as you saw in the preceding sections—as a sequence of binary digits.
There is no indication in the hardware as to whether that value represents the integer
12 or the numeric representation for the month of December. The meaning of a
particular value depends on how it is used. If the program uses the value
arithmetically, it is interpreted as the integer 12. If it instead uses that value to
select from a list of month names, that value indicates December. In either case, the
number stored inside the computer is exactly the same.
The strategy of using numbers to represent nonnumeric data is one of the most
important ideas in the history of computation. One of the clearest and earliest
expositions of that idea comes from Ada Lovelace, daughter of the poet Lord Byron
and his wife Anna Isabella Byron. In the 1840s, Lady Lovelace collaborated with
the English mathematician and inventor Charles Babbage on the design of his
Analytical Engine, a calculating machine that anticipated several essential features
of modern computers, including the ability to solve different tasks by changing its
programming. Indeed, much of what we know about the Analytical Engine—which
sadly was never completed—comes from Lovelace’s translation of a detailed
description of Babbage’s work by the Italian engineer Luigi Menabrea. Her
translation, entitled Sketch of the Analytical Engine Invented by Charles Babbage,
Ada Lovelace
Esq., was published in 1843, along with her explanatory notes that were almost
three times as long as the original paper. Lovelace recognized that the algebraic
patterns for which the Analytical Engine was designed could be extended to include
concepts beyond simple numbers. Her notes envision a world of possibilities for the
Analytical Engine that someday “might compose elaborate and scientific pieces of
music of any degree of complexity or extent.”
In an interview for a film about Ada Lovelace’s life and work, Doron Swade,
who led the effort to rebuild Babbage’s earlier Difference Engine for the Science
Museum in London, offers the following description of Ada’s contribution:
Charles Babbage Ada saw something that, in some sense, Babbage failed to see. In Babbage’s
world, his engines were bound by number. . . . What Lovelace saw—what
Ada Byron saw—was that number could represent entities other than quantity.
So, once you had a machine for manipulating numbers, if those numbers
represented other things—letters, musical notes—then the machine could
manipulate symbols of which number was one instance.”
Representing characters
The primitive elements of string data are individual characters. Like the months of
the year, characters can be represented inside the computer by assigning each
character a numeric code. You could, for example, assign successive integers to
7.1 Binary representation 241
represent each of the letters in the alphabet, using 0 for the letter A, 1 for letter B,
and so on. In 1605, the English philosopher and scientist Francis Bacon did
precisely that when he devised a technique for encoding messages that is now
known as Bacon’s cipher. What is, however, even more astonishing is that Bacon
based his cipher on the binary representation of these numbers, almost a century
before Leibniz published his paper on binary arithmetic. Bacon’s cipher, however,
was not used in practice and had little or no influence on the later development of
computation.
The first binary encoding scheme for characters used extensively in practice was Francis Bacon
the Baudot code, which was invented in 1870 by the French engineer Émile Baudot,
one of the pioneers of the telegraph. In Baudot’s scheme, each of the 26 letters was
assigned a numeric code. The encoding also included a few special characters to
represent the space character, the two characters that telegraph printers used to
designate the end of a line, and transitions to an alternate character set used for
digits and punctuation. The letters of the alphabet did not appear in order, but were
instead chosen so that the most common letters, such as E and T, would require
pressing just one of the five keys on the input device that Baudot designed.
The fact that the letters do not appear consecutively in the Baudot code does not
make the encoding scheme any less effective. The only essential characteristic of
an encoding system is that the sender and receiver agree on how to convert letters to Émile Baudot
numeric codes. The need for a common encoding shared by senders and receivers
increases the importance of standardization. As long as all telegraph operators used
the same code, they were able to communicate with one another.
In its original design, ASCII contained 128 characters, which is enough to store
the uppercase and lowercase letters of the Latin alphabet, the standard decimal
digits, a variety of punctuation symbols, and a set of nonprinting characters called
control characters. The characters in the original ASCII set appear in Figure 7-1.
The gray boxes in the table correspond to control characters that have lost their
significance over time. The few remaining control characters recognized by
JavaScript are indicated using a backslash (\) followed by a letter that defines that
242 Strings
The characters in Figure 7-1 are arranged according to their internal values,
which are expressed in hexadecimal. The character A, for example, appears in the
row labeled 4x and the column labeled 1, so its internal representation is 4116, which
is the decimal number 65. There is no need to learn these values, although certain
patterns are important. This text, for example, relies on the following properties:
Unfortunately, even that expanded standard has become too small for modern
computing. The current version of Unicode includes 1,114,112 characters, ranging
from ancient alphabets to emoji, the small digital images used to express emotions
or ideas in text messages. Because these symbols were added to Unicode after
JavaScript was formalized, it requires extra work to include them in strings. Using
those characters, moreover, requires the use of a concept called a code point, which
was added only in version 6 of the JavaScript standard, which puts it beyond the
scope of this text. Detailed information about code points is readily available on the
web.
7.2 String operations 243
The relational operators compare strings using lexicographic order, which is similar
to traditional alphabetical order but which uses the underlying Unicode values of
each character to make the comparison. Lexicographic order means that case is
significant, so "a" is not equal to "A". In lexicographic order, "a" is greater than
"A" because the Unicode value for a lowercase a (6116 or 97) is greater than the
Unicode value for an uppercase A (4116 or 65).
All other string operations require calling methods on a string value using the
receiver syntax you learned in Chapter 4 for working with graphical objects:
[Link](arguments)
Figure 7-2 on the next page lists the most common methods that JavaScript defines
as part of its String class. These methods are explored in detail in the sections that
follow.
StringClass
7.2 String operations 245
the expression [Link] has the value 26. Similarly, if you create a
variable str using the declaration
the expression [Link] has the value 0. The string containing no characters at
all, which comes up frequently in programming, is called the empty string.
The position number written underneath each character is called its index.
In JavaScript, you select a character from a string by calling the charAt method.
For example, the expression
[Link](0)
[Link]([Link] - 1)
JavaScript allows you to retrieve the underlying Unicode value of the character
at any index position by calling charCodeAt with the desired index. For example,
[Link](0)
returns the Unicode value of the character "A" at index position 0, which you can
determine from Figure 7-1 has the value 4116, or 65. To convert from character
codes to strings, you need to use the function [Link], which, like
the various Math functions, is associated with a class rather than an object. Calling
[Link](90), for example, returns the one-character string "Z".
want to select and the index of the character that immediately follows the desired
substring. For example, the method call
[Link](1, 4)
The JavaScript String class exports two other methods, substr and slice, that
select substrings but use different conventions for defining which characters should
appear. To minimize confusion, this text uses only the substring method.
[Link](pattern);
where pattern is the content you’re looking for. When called, the indexOf method
searches through str looking for the first occurrence of the pattern. If the search
value is found, indexOf returns the index position at which the match begins. If
the character does not appear before the end of the string, indexOf returns −1.
The indexOf method takes an optional second argument that indicates the index
position at which to start the search. The effect of both styles of the indexOf
method is illustrated by the following examples, which assume that the variable str
contains the string "hello, world":
[Link]("o") → 4
[Link]("o", 5) → 8
[Link]("o", 9) → −1
The JavaScript String class also includes a lastIndexOf method that works
like indexOf, except that it searches backward for a match.
7.2 String operations 247
Case conversion
The methods toLowerCase and toUpperCase convert any alphabetic characters
in the receiver string to the specified case, leaving any other characters unchanged.
For example, if str contains "hello, world", calling [Link]()
returns "HELLO, WORLD". Similarly, calling [Link]() returns
"abcdefghijklmnopqrstuvwxyz".
str = [Link]();
[Link]("y") || [Link]("Y")
is true if the string variable answer begins "y" or "Y". The endsWith method is
symmetric and returns true if the string ends with the specified suffix. The trim
method returns a copy of the string after removing all whitespace characters
(“invisible” characters such as spaces or tabs) from both ends of the string.
Some JavaScript programmers avoid these methods because they didn’t exist in
early versions of JavaScript. If you need to ensure that your code runs on as many
browsers as possible, you can implement these tools as predicate functions. For
example, you can implement the function startsWith like this:
performs the multiplication first to get 64, converts that number to a string, and then
concatenates that string to the end of the characters "When I'm " to produce the
string "When I'm 64". At times, however, it is useful to exercise more control over
the format of the numeric string.
One tool for formatting numbers is the toString method, which applies to any
numeric value. With no argument, toString performs the same conversion as the
concatenation operator and produces a string of digits. When used with numbers,
toString allows you to specify the base as a parameter. For example, if n
contains the value 42, calling [Link](16) returns the string "2a". If you
prefer upper case for hexadecimal digits, you can call toUpperCase on the result.
To offer some control over numeric formatting, JavaScript defines the methods
toFixed, which specifies exactly how many digits should appear after the decimal
point in the converted string, and toPrecision, which specifies the number of
significant digits. For example, calling [Link](4) produces the string
"3.1416", which has four digits after the decimal point. Calling
[Link](4) returns "3.142", which has four significant digits.
JavaScript includes two built-in functions that convert numeric strings into the
numeric values signified by those strings. The parseInt function converts a string
of digits into the corresponding numeric value. By default, parseInt interprets the
number as a decimal value, but the function takes an optional second argument
specifying the base. For example, calling parseInt("2A", 16) returns the value
42. The parseFloat function performs a similar conversion for a string
representing a floating-point value, so that parseFloat("3.14159") returns the
numeric value 3.14159.
A better strategy for converting strings to numbers is to call the built-in Number
function, which translates its argument to a number. The Number function signals
7.3 Classifying characters 249
failure by returning NaN, which is short for not a number. In JavaScript, you check
whether a value is NaN by calling the built-in function isNaN. You cannot use the
=== operator because NaN is traditionally defined so that it is not equal to itself.
Although this definition may initially seem odd, two computations that produce NaN
might in fact be generating different values.
function isDigit(ch) {
return [Link] === 1 && ch >= "0" && ch <= "9";
}
The Boolean expression in the return statement first checks that the parameter ch
is a single-character string and, if so, checks to make sure that it appears in the
inclusive range between "0" and "9" in lexicographic order. Similarly, you can
check whether a character is an uppercase letter using the following function:
function isUpperCase(ch) {
return [Link] === 1 && ch >= "A" && ch <= "Z";
}
isLetter(ch)
function isEnglishVowel(ch) {
return [Link] === 1 &&
"AEIOUaeiou".indexOf(ch) !== -1;
}
250 Strings
[Link]
7.4 Common string patterns 251
On each loop cycle, the expression [Link](i) refers to the character in the
string at index position i. You can, for example, count the number of spaces in a
string using the following function:
function countSpaces(str) {
let nSpaces = 0;
for (let i = 0; i < [Link]; i++) {
if ([Link](i) === " ") nSpaces++;
}
return nSpaces;
}
For some applications, it is useful to iterate through a string in the opposite
direction. This style of iteration uses the following for loop:
Here, the index i begins at the last index position and then decreases by one on each
cycle, down to and including the index position 0.
Assuming that you understand the syntax and semantics of the for statement,
you could work out these iteration patterns from first principles each time you need
them in an application. Doing so, however, would slow you down enormously.
These patterns are worth memorizing so that you don’t have to waste any time
thinking about them. Whenever you recognize that you need to cycle through the
characters in a string, some part of your nervous system between your brain and
your fingers should be able to translate that idea effortlessly into the following line:
For example, the nCopies function returns a string consisting of n copies of str:
The nCopies function is useful if, for example, you need to generate some kind of
section separator in console output. One strategy for accomplishing this goal would
be to use the following statement to print a line of 72 hyphens:
[Link](nCopies(72, "-"));
function reverse(str) {
let result = "";
for (let i = [Link] - 1; i >= 0; i--) {
result += [Link](i);
}
return result;
}
You can also implement reverse by running the loop in the forward direction and
concatenating each new character to the front of the result string, as follows:
As with most programming problems, there is more than one strategy for solving
this problem. The following code illustrates one strategy:
function isPalindrome(str) {
let n = [Link];
for (let i = 0; i < n / 2; i++) {
if ([Link](i) !== [Link](n - i - 1)) {
return false;
}
}
return true;
}
This implementation uses a for loop to run through each index position in the first
half of the string, checking whether the character in that position matches the one in
the symmetric position relative to the end of the string.
If, however, you make use of the functions you already have, you can code
isPalindrome in a much simpler form, as follows:
function isPalindrome(str) {
return str === reverse(str);
}
students to figure out why the code includes the selection expression
[Link](n - i - 1) or why it is appropriate to use the < operator in the for
loop test, as opposed to <=. By contrast, the line
Generating acronyms
An acronym is a new word formed by combining, in order, the initial letters of a
series of words. For example, NATO is an acronym formed from the first letters in
North Atlantic Treaty Organization. The goal of this section is to write a function
called acronym that takes a string and returns its acronym. For example, calling
When you first look at the problem, it might seem that the obvious approach is to
start with the first character and then search for spaces in a while loop. Each time
the function finds a space, it can concatenate the next character onto the end of the
string variable used to hold the result. When no more spaces appear in the string,
the acronym is complete. This strategy can be translated into a JavaScript
implementation as follows:
function acronym(str) {
let result = [Link](0);
let sp = [Link](" ");
while (sp !== -1) {
result += [Link](sp + 1);
sp = [Link](" ", sp + 1);
}
return result;
}
7.5 String applications 255
Although this implementation works for some strings, it fails for others. For
example, it produces the correct algorithm only if each pair of words is separated by
exactly one space. If some of the words are separated using hyphens—as in
"self-contained underwater breathing apparatus", which produces the
acronym "scuba"—this implementation will fail to return the correct result.
Worse still, the function will generate an error condition if the word ends with a
space, because the call to [Link](sp + 1) will try to select the character after
the end of the string, which doesn’t exist.
function acronym(str) {
let result = "";
let inWord = false;
for (let i = 0; i < [Link]; i++) {
let ch = [Link](i);
if (isLetter(ch)) {
if (!inWord) result += ch;
inWord = true;
} else {
inWord = false;
}
}
return result;
}
This implementation uses the standard idiom to go through the string character by
character, looking at each one. It determines the word boundaries by using the
Boolean variable inWord, which is true if the process is scanning letters and
false if it is scanning nonletters. New letters get added to the acronym only if the
code sees a letter when inWord was previously false.
1. If the word contains no vowels, no translation is done, which means that the Pig
Latin word is the same as the original.
256 Strings
2. If the word begins with a vowel, the Pig Latin translation consists of the
original word followed by the suffix way.
3. If the word begins with a consonant, the Pig Latin translation is formed by
extracting the string of consonants up to the first vowel, moving that collection
of consonants to the end of the word, and then adding the suffix ay.
As an example, suppose that the English word is scram. Because the word begins
with a consonant, you divide it into two parts: one consisting of the letters before
the first vowel and one consisting of that vowel and the remaining letters:
You then interchange these two parts and add ay at the end, as follows:
Thus the Pig Latin word for scram is amscray. For a word that begins with a vowel,
such as apple, you simply add way to the end, which leaves you with appleway.
The code for [Link] appears in Figure 7-4. The file exports two
functions for clients to use. The wordToPigLatin function converts a word to its
Pig Latin equivalent. The toPigLatin function takes a line of text and converts
the entire line to Pig Latin by divides the line into words and then converting each
word. Characters that are not part of a word are copied directly to the output line so
that punctuation and spacing remain unaffected. The following console log gives a
few examples of the functions toPigLatin and wordToPigLatin:
[Link]
258 Strings
One of the first encryption systems whose details survive is the Polybius square,
developed by the Greek historian Polybius in the second century BCE. In this
system, the letters of the alphabet are arranged to form a 5×5 grid in which each
letter is represented by its row and column number. Suppose, for instance, that you
want to transmit following English version of Pheidippides’s message to Sparta:
THE ATHENIANS BESEECH YOU TO HASTEN TO THEIR AID
Polybius square
This message can be transmitted as a series of numeric pairs, as follows:
44 23 15 11 44 23 15 33 24 11 33 43 12 15 43 15 15 13 23 54
34 45 44 34 23 11 43 44 15 33 44 34 44 23 15 24 42 11 24 14
7.5 String applications 259
The advantage of the Polybius square is not so much that it allows for secret
messages, but that it simplifies the problem of transmission. Each letter in the
message can be represented by holding between one and five torches in each hand,
which allows a message to be communicated visually over a great distance. By
reducing the alphabet to an easily transmittable code, the Polybius square
anticipates such later developments as Morse code and semaphore, not to mention
modern digital encodings such as ASCII or Unicode.
In De Vita Caesarum, written sometime around 110 CE, the Roman historian
Suetonius describes an encryption system used by Julius Caesar, as follows:
If he had anything confidential to say, he wrote it in cipher, that
is, by so changing the order of the letters of the alphabet, that
not a word could be made out. If anyone wishes to decipher
these, and get at their meaning, he must substitute the fourth
letter of the alphabet, namely D, for A, and so with the others.
Even though it may seem like ancient history, there are still situations in which
reading data or commands from the console offers a useful style of human-computer
interaction. If nothing else, it is often easiest to test your functions by writing
simple console-based based programs that let you see the results of making
particular calls. In fact, that’s exactly what you’ve been doing when you use the
JavaScript console. You type in expressions and get to see the results.
The AddIntegerList function begins by giving instructions to the user and then
asks the user to enter integers on the console, one per line. When the user enters a
blank line after the last question mark, the program displays the sum.
The code for [Link] appears in Figure 7-6. The first new feature
used in the program is the requestInput method in the console object. The first
argument is a prompt, which is used to notify the user that some input is expected.
The second argument is the function to be called when that input appears. The
callback function takes one argument, which is the input line. If that string is
empty, processLine displays the sum of the values entered; otherwise,
processLine converts the string to an integer and adds it to the running total.
When you include the [Link] library in your [Link] file, the
browser ordinarily creates a new region at the end of the <body> section to hold the
console output and then expands that region as the program runs. For programs that
accept input data, it improves the user experience if the web page creates an explicit
262 Strings
region for the console so that its size doesn’t keep changing. You can set a fixed
height for the console by including the HTML tag
inside the <body> section, where h indicates the height of the console in pixels.
Summary
In this chapter, you have learned how to use the String class, which makes it
possible to write string-processing functions without worrying about the details of
the underlying representation. The important points in this chapter include:
• Sequences of bits are combined inside the hardware to form larger structures,
including bytes, which are eight bits long, and words, which are large enough to
contain a standard integer.
• Computer scientists tend to record the values of bit sequences in hexadecimal
(base 16), which allows binary values to be represented in a more compact form.
• Numbers don’t have bases; representations do.
• Nonnumeric data values are represented by numbering the elements in the
domain and then using those numbers as codes for the original values.
• Characters are represented internally using a coding scheme called Unicode,
which assigns numeric values to characters from a wide range of languages.
• The String class represents a type that is conceptually a sequence of characters.
The character positions in a string are assigned index numbers that start at 0 and
extend up to one less than the length of the string.
• The most common methods exported by the String class appear in Figure 7-2
on page 244. Because String is a class, the methods use the receiver syntax
instead of a more traditional functional form.
• The standard pattern for iterating through the characters in a string is
for (let i = 0; i < [Link]; i++) {
. . . body of loop that manipulates [Link](i) . . .
}
Review questions
1. Define the following terms: bit, byte, and word.
5. What JavaScript functions allow you to convert between numbers and their
corresponding string representations?
9. By consulting Figure 7-1, determine the Unicode values of the characters "$",
"@", "0", and "x".
10. True or false: In JavaScript, you can determine the length of the string stored in
the variable str by calling length(str).
11. True or false: The index positions in a string begin at 0 and extend up to the
length of the string minus 1.
12. How do you extract the character at position k in a string? How would you
determine the Unicode value of that character?
13. What are the arguments to the substring method? What happens if you omit
the second argument?
15. What value does indexOf return if the pattern string does not appear?
17. Suppose that you have declared and initialized the variable s as follows:
let s = "hello, world";
Given that declaration, what value is produced by each of the following calls:
a. s + "!" f. [Link]("h", "j")
b. [Link] g. [Link](0, 3)
c. [Link](5) h. [Link](7)
d. [Link]("l") i. [Link](3, 5)
e. [Link]("l", 5) j. [Link](3, 3)
18. What is the pattern for iterating through each character in a string?
19. How does the pattern in question 18 change if you want to iterate through the
characters backward, starting with the last character and ending with the first?
Exercises
1. In exercise 18 from Chapter 3, you wrote a program to find perfect numbers.
Rewrite that program so that it also displays the binary form of these numbers.
As you can see if you run this program, the first few perfect numbers follow an
interesting pattern when you write them out in binary. Euclid discovered this
pattern more than 2000 years ago, and the 18th-century Swiss mathematician
Leonhard Euler proved that all even perfect numbers follow this pattern.
2. As noted in the discussion of the built-in methods in the String class, the
startsWith and endsWith methods are not implemented in older browsers.
Using the startsWith function on page 247 as a model, implement the
function endsWith(str, suffix) that checks whether str ends with the
specified suffix.
6. In many word games, letters are scored according to their point values, which
are inversely proportional to their frequency in English words. In Scrabble™,
the points are allocated as follows:
Points Letters
1 A, E, I, L, N, O, R, S, T, U
2 D, G
3 B, C, M, P
4 F, H, V, W, Y
5 K
8 J, X
10 Q, Z
266 Strings
For example, the word "FARM" is worth 9 points in Scrabble: 4 for the F, 1
each for the A and the R, and 3 for the M. Write a function scrabbleScore
that takes a word and returns its score in Scrabble, not counting any of the other
bonuses that occur in the game. You should ignore any characters other than
uppercase letters in computing the score.
is a sentence palindrome, because if you look only at the letters and ignore any
case distinctions, it reads identically backward and forward.
10. In English, the notion of an ongoing action is expressed using the present
progressive tense, which involves the addition of an ing suffix to the verb. For
example, the sentence I think conveys a sense that one is capable of thinking;
by contrast, the sentence I am thinking conveys the impression that one is
currently doing so. The ing form of the verb is called the present participle.
Exercises 267
11. As in most languages, English includes two types of numbers. The cardinal
numbers (such as one, two, three, and four) are used in counting; the ordinal
numbers (such as first, second, third, and fourth) are used to indicate a position
in a sequence. In text, ordinals are usually indicated by writing the digits in the
number, followed by the last two letters of the English word that names the
corresponding ordinal. Thus, the ordinal numbers first, second, third, and
fourth often appear in print as 1st, 2nd, 3rd, and 4th. The ordinals for 11, 12,
and 13, however, are 11th, 12th, and 13th. Devise a rule that determines what
suffix should be added to each number, and then use this rule to write a
function createOrdinalForm(n) that returns the ordinal form of the number
n as a string.
12. The waste of time in spelling imaginary sounds and their history
(or etymology as it is called) is monstrous in English . . .
—George Bernard Shaw, 1941
In the early part of the 20th century, there was considerable interest in both
England and the United States in simplifying the rules used for spelling English
words, which has always been a difficult proposition. One suggestion
advanced as part of this movement was to eliminate all doubled letters, so that
bookkeeper would be written as bokeper and committee would become comite.
268 Strings
13. When large numbers are written on paper, it is traditional—at least in the
United States—to use commas to separate the digits into groups of three. For
example, the number one million is usually written as 1,000,000. Implement a
function
function addCommas(digits)
that takes a string of decimal digits representing a number and returns the string
formed by inserting commas at every third position, starting on the right. Your
implementation of the addCommas function should be able to reproduce the
following console log:
14. As written, the PigLatin program in Figure 7-4 behaves oddly if you enter a
string that includes words beginning with an uppercase letter. For example, if
you were to capitalize the first word in the sentence and the name of the Pig
Latin language, you would see the following output:
Rewrite the wordToPigLatin function so that any word that begins with a
capital letter in the English line still begins with a capital letter in Pig Latin.
Thus, after you make the necessary changes in the program, the output should
look like this:
15. Most people in English-speaking countries have played the Pig Latin game at
some point in their lives. There are other invented “languages” in which words
are created using some simple transformation of English. One such language is
Exercises 269
called Obenglobish, in which words are created by adding the letters ob before
the vowels (a, e, i, o, and u) in an English word. For example, under this rule,
the word english gets the letters ob added before the e and the i to form
obenglobish, which is how the language got its name.
In official Obenglobish, the o b characters are added only before vowels that
are pronounced, which means that a word like game would become gobame
rather than gobamobe because the final e is silent. While it is impossible to
implement this rule perfectly, you can do a pretty good job by adopting the rule
that the ob should be added before every vowel in the English word except
Write a function obenglobish that takes an English word and returns its
Obenglobish equivalent, using the translation rule given above. Your function
should allow you to generate the following sample run:
The genetic code for all living organisms is carried in its DNA—a molecule
with the remarkable capacity to replicate its own structure. The DNA molecule
itself consists of a long strand of chemical bases wound together with a similar
strand in a double helix. DNA’s ability to replicate comes from the fact that its
four constituent bases—adenosine, cytosine, guanine, and thymine—combine
with each other only in the following ways:
• Cytosine on one strand links only with guanine on the other, and vice versa.
• Adenosine links only with thymine, and vice versa.
Biologists abbreviate the names of the bases by writing only the initial letter: A,
C, G, or T.
270 Strings
Inside the cell, a DNA strand acts as a template to which other DNA strands
can attach themselves. As an example, suppose that you have the following
DNA strand, in which the position of each base has been numbered as it would
be in a JavaScript string:
Your mission in this exercise is to determine where a shorter DNA strand can
attach itself to the longer one. If, for example, you were trying to find a match
for the strand
the rules for DNA dictate that this strand can bind to the longer one only at
position 1:
Write a function
that returns the first position at which the DNA strand s1 can attach to the
strand s2. As in the indexOf method, the optional start parameter indicates
the index position at which the search should start. If there is no match,
findDNAMatch should return −1.
17. Although Caesar ciphers are simple, they are also extremely easy to break. A
somewhat more secure scheme allows each letter in the message to be
represented consistently by some other letter, but not one chosen by shifting the
character a fixed distance in the alphabet. This kind of coding scheme is called
a letter-substitution cipher.
the key (which is unimaginatively generated by typing the letter keys on the
keyboard in order), that key then corresponds to the following mapping:
Write a function encrypt that takes a string and a 26-character key and
returns the string after applying a letter-substitution cipher with that key. For
example, your function should be able to produce the following sample run:
18. Write a predicate function isKeyLegal, which takes a string and returns true
if that string would be a legal key in a letter-substitution cipher. A key is legal
only if it meets the following two conditions:
These conditions automatically rule out the possibility that the key contains
invalid characters or duplicated letters. After all, if all 26 uppercase letters
appear and the string is exactly 26 characters long, there isn’t room for
anything else.
19. Letter-substitution ciphers require the sender and receiver to use different keys:
one to encrypt the message and one to decrypt it when it reaches its destination.
Your task in this exercise is to write a function invertKey that takes an
encryption key and returns the corresponding decryption key. In cryptography,
that operation is called inverting the encryption key.
The translation table shows that A maps into Q, B maps into W, C maps into E,
and so on. To turn the encryption process around, you have to read the
272 Strings
translation table from bottom to top, looking to see what letter in the original
text would have produced each letter in the encrypted version. For example, if
you look for the letter A in the bottom line of the key, you discover that the
corresponding letter in the original must have been K. Similarly, the only way
to get a B in the encrypted message is to start with an X in the original one. The
first two entries in the inverted translation table therefore look like this:
If you continue this process by finding each letter of the alphabet on the bottom
of the original translation table and then looking to see what letter appears on
top, you will eventually complete the inverted table, as follows:
The inverted key is simply the 26-character string on the bottom row, which in
this case is "KXVMCNOPHQRSZYIJADLEGWBUFT".
20. Write a program that reads in a list of integers on the console until the user
enters a blank line. When the sentinel appears, your program should display
the largest value in the list.
21. For a slightly more interesting challenge, write a program that finds both the
largest and the second-largest integer in a list, prior to the entry of the blank
line that ends the input. A sample run of this program might look like this:
The input values in this example are the number of pages in the British
hardcover editions of J. K. Rowling’s Harry Potter series. The output therefore
tells us that the longest book (Harry Potter and the Order of the Phoenix) has
766 pages and the second-longest book (Harry Potter and the Goblet of Fire)
weighs in at a mere 636 pages.
CHAPTER 8
Arrays
I’m not rich because I invented VisiCalc, but I feel that I’ve
made a change in the world. That’s a satisfaction money
can’t buy.
— Dan Bricklin, November 1985, as quoted
in Robert Slater, Portraits in Silicon
In modern computing, one of the most visible applications of the array structure described in this chapter is
the electronic spreadsheet, which uses a two-dimensional array to store tabular data. The first electronic
spreadsheet was VisiCalc, which was released in 1979 by Software Arts, Incorporated, a small startup
company founded by MIT graduates Dan Bricklin and Bob Frankston. VisiCalc proved to be a popular
application, leading many larger firms to develop competing products, including Lotus 1 2 3 and, more
recently, Microsoft Excel.
274 Arrays
Up to now, the programs in this book have worked with individual data items. The
real power of computing, however, comes from the ability to work with collections
of data. This chapter introduces the idea of an array, which is an ordered collection
of values. Arrays are important in programming largely because such collections
occur quite often in the real world. Whenever you want to represent a set of values
that it makes sense to think about as forming a sequence, arrays are likely to play a
role in the solution.
Like every other data type in JavaScript, arrays can be stored in variables, passed
as arguments to a function, and returned from functions as a result. And like every
other data type, arrays in JavaScript support a set of operations appropriate to the
type. For arrays, that set of operations allows you to manipulate both the contents
and the ordering of elements. These operations are outlined in the sections that
follow.
After you make this definition, the value of the constant COINS is an array that
corresponds to the following box diagram:
The small numbers underneath the boxes in this diagram represent the position of
that value in the array, which is called its index. When you use JavaScript’s array
notation, the index numbers always begin with 0 and run up to one less than the
number of elements. Thus, in an array with six elements, the index numbers are
0, 1, 2, 3, 4, and 5, as the preceding diagram shows.
8.1 Introduction to arrays 275
Every JavaScript array has a field called length that contains the number of
elements. The expression
[Link]
The elements of an array need not be numbers but can instead be any JavaScript
value. For example, the following variable declaration defines hogwarts as an
array containing the names of the four houses at the Hogwarts School of Witchcraft
and Wizardry from J. K. Rowling’s Harry Potter novels:
let hogwarts = [
"Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin"
];
Array selection
To refer to a specific element within an array, you specify both the array name and
the index corresponding to the position of that element within the array. The
process of identifying a particular element within an array is called selection, and is
indicated in JavaScript by writing the name of the array and following it with the
index written in square brackets. For example, given the array definitions from the
preceding section, the expression COINS[2] is 10, because that is the value at index
2 in the COINS array. Similarly, hogwarts[0] has the value "Gryffindor". If
you select an index position that falls outside the limits of an array, JavaScript
returns the value undefined.
The result of a selection expression is assignable, in the sense that you can use a
selection expression on the left side of an assignment statement. For example, if
some future leaders of Hogwarts decided that they might need to honor a more
worthy wizard, evaluating the expression
hogwarts[3] = "Dumbledore";
Arrays are often used in connection with for loops that step through every index
position in the array. The usual pattern for doing so is analogous to the pattern for
iterating through the characters in a string presented in Chapter 7:
A simple example of the use of for loops with arrays is the function
function listArray(array) {
for (let i = 0; i < [Link]; i++) {
[Link](array[i]);
}
}
This function simply lists the elements of array, one per line, on the console. For
example, after defining this function, you could generate the following console
session:
function sumArray(array) {
let sum = 0;
for (let i = 0; i < [Link]; i++) {
sum += array[i];
}
return sum;
}
returns the sum of the elements in the array. Calling sumArray([ 1, 2, 3, 4 ]),
for example, returns 10 (1 + 2 + 3 + 4). Similarly, if COINS is defined as shown on
page 274, calling sumArray(COINS) returns 191 (1 + 5 + 10 + 25 + 50 + 100).
8.1 Introduction to arrays 277
It is worth spending a few minutes examining the code for reverseArray. The
overall strategy is to use a for loop to go through the index positions in the first
half of the array, marking that position with the variable lh, where the variable
name is chosen to suggest the idea of a position on the left side of the array that you
might point to with your left hand. The first statement in the loop body calculates
the index position of the corresponding element at the right side of the array and
stores that index in the variable rh. The last three lines of the loop body
278 Arrays
interchange these two array elements using a temporary variable to ensure that no
values have been lost after the first assignment.
It is interesting to ask whether this rule is applied when the console script calls
reverseArray. Is the hogwarts array copied into the parameter variable array?
If so, why doesn’t the reverseArray function reverse the elements of the copy,
leaving the original value of hogwarts unchanged?
The key to answering this question lies in understanding that the value of a
JavaScript array is not the sequence of elements itself but is instead a reference to
those elements, which indicates where the elements are stored in the computer’s
memory. When you pass an array as an argument to a function, JavaScript copies
the reference but does not copy the actual element values. The effect of this
strategy is that a function and its caller have access to the same elements.
The idea that arrays—and indeed all JavaScript objects, as you will learn in
Chapter 9—are passed as references is so important that it is worth going through an
example in more detail. Suppose that you have defined the following function:
function testReverseArray() {
let numbers = [ 1, 2, 3, 4, 5 ];
reverseArray(numbers);
[Link](numbers);
}
Calling this function creates a new stack frame and declares the variable numbers.
After you initialize numbers to an array value, the stack frame looks like this:
8.1 Introduction to arrays 279
The important thing to note is that the elements of the array are stored outside the
frame. The reference stored in numbers indicates where the array is stored.
When the program calls reverseArray, the new stack frame looks like this:
The elements of the array are still in the same place in the computer’s memory as
they were in the frame for testReverseArray. When reverseArray returns,
the values in the numbers array will be reversed, as follows:
such an element, JavaScript returns the value undefined. If you assign a new
value to an element outside the defined bounds, JavaScript creates that element,
even if doing so leaves undefined elements interspersed among the defined ones.
let array = [ 1, 2, 3 ];
which creates a three-element array. Given that array has values only in index
positions 0, 1, and 2, it seems reasonable for array[7] to be undefined. But
what happens if you assign a value to that element, as in
array[7] = 8;
Although writing code that leaves unfilled holes in the middle of an array makes
programs more difficult to understand, the fact that JavaScript creates new elements
as you assign a value to each array index offers a convenient mechanism to create
an array in the context of a for loop. For example, the function
because they are similar to methods in JavaScript’s String class. For example,
both arrays and strings have a length field that indicates the number of values, and
the indexOf and lastIndexOf methods are the same for both classes. Other
methods perform similar functions with slightly different names. The concat
method for arrays corresponds to the + operator for strings, and the slice method
is the array counterpart of substring.
The primary difference between the methods available for strings and those
available for arrays lies in how those methods are allowed to manipulate the values
to which they are applied. None of the methods in the String class change the
contents of the original string but instead return a result by creating an entirely new
string value. For example, if str is a string variable, calling [Link]()
does not change the value of str. What happens instead is that the call returns a
new string in which all characters have been converted to upper case. By contrast,
most of the methods in Figure 8-2 have the effect of changing the contents of the
array. These methods are detailed in the sections that follow.
The push method takes any number of arguments and adds each argument to the
end in turn. For example, suppose that you have used the following declaration to
create an array of four strings:
At this point, calling [Link]("five") would add the string "five" to the
end of the array, like this:
The pop method removes the last element in the array and returns it to the caller.
Thus, if you called [Link]() in this configuration, the pop method would
remove and return the value "five", restoring the array to its previous state:
The unshift and shift methods have the same effect as push and pop except
that the changes occur at the beginning of the array. For example, if you called
[Link]("zero"), the array would change as follows:
Note that the unshift and shift methods change the index numbers of the
remaining array elements as the initial elements are added and removed.
The splice method is used both to remove and add elements at an arbitrary
index position in the array. The general form of the call is
[Link](index, count, . . .)
where index is the array index at which the deletions and insertions occur, count is
the number of elements to delete, and any remaining arguments are inserted in its
place. For example, given the most recent contents of the numbers array, calling
[Link](1, 1, "by") would change the array as follows:
284 Arrays
Although splice is by far the most flexible method available for adding and
removing array elements, it is by no means the most important to learn. In my
experience, the most frequently used method is push, which makes it possible to
expand an array one element at a time in a fashion similar to the pattern for growing
a string by concatenation. For example, the push method makes it possible to
rewrite the createArray function from page 280 like this:
The push and pop methods allow the JavaScript array class to implement a
remarkably useful data structure called a stack, which is a list in which values are
added and removed only at the end. This restriction implies that the last item added
to a stack is always the first item that gets removed. Stacks are important in
computer science partly because nested function calls behave in a stack-oriented
fashion. Thus, the function called most recently is the first function that returns.
A common (but possibly apocryphal) explanation for the words stack, push, and
pop is that the stack model derives from the way plates are stored in a cafeteria.
Suppose, for example, that you are in a cafeteria where customers pick up their own
plates from a spring-loaded column that makes it possible to take only the top plate,
as illustrated in the following diagram:
When a dishwasher adds a new plate, it goes on the top of the stack, pushing the
others down slightly as the spring is compressed, as follows:
8.2 Array operations 285
Customers can take plates only from the top of the stack. When they do, the
remaining plates pop back up. The last plate added to the stack is the first one a
customer takes.
Similarly, the push and shift methods work together to implement a useful
data structure that computer scientists call a queue, which is analogous to a waiting
line. The push method adds new elements to the end of the line; the shift method
removes them from the front. You will learn more about stacks and queues if you
continue your study of computer science beyond what is covered in this book.
reverseArray(array);
To achieve the same result using the reverse method, clients would instead write
[Link]();
The sort method rearranges the elements of an array so that they appear in
ascending order. If you call sort with no argument, JavaScript sorts the array
according to the how the elements would appear when printed. This interpretation
makes sense for arrays of strings but produces counterintuitive results if the array
contains numeric values. For example, if you define array as
would sort the array numerically. It doesn’t. What JavaScript does is sort the array
lexicographically using the string representation of the numbers, as follows:
286 Arrays
Fortunately, JavaScript allows you to control how sort reorders the elements by
passing it a comparison function. A comparison function takes two arguments and
returns a number whose sign indicates the ordering relationship between those
values. That return value must be negative if the first value should precede the
second, positive if the first value should follow the second, and zero if the values
should be sorted to the same position. For example, you can sort array numerically
by calling [Link](sortNumerically), where sortNumerically is
Similarly, if lines is an array of strings, you can sort the array alphabetically
without regard to case by supplying the following comparison function:
Although the sort method always rearranges the elements of the array so that
they appear in ascending order, it is easy to sort an array in descending order by
using a different comparison function. The most general solution is to define the
following function, which takes one comparison function and returns a new one that
calls the original function with the arguments reversed:
function reverseComparison(cmp) {
return function(v1, v2) { return cmp(v2, v1); };
}
You can use reverseComparison to invert the sense of any comparison function.
For example, you can sort list in reverse without regard to case like this:
[Link](reverseComparison(sortIgnoringCase));
The join method in the array class reverses the process. If, for example, you were
to call join("-") on an array containing these three elements, you would get the
string "16-Jul-1969".
You do have to take some care in using split if the separator pattern can
appear at the beginning or end of the string. In such cases, the array that split
returns will contain empty strings because its argument represents a separator,
which always stands between two substrings. For example, if the variable dir
contains "/usr/bin/" (a directory that several popular operating systems use to
store command-line applications), calling [Link]("/") returns the following
four-element array:
Calling [Link]("\n") would return an array with five elements, the last of
which is the empty string corresponding to the characters after the final newline.
What you almost certainly want instead is to divide the title into its four component
lines using the following function:
function splitLines(text) {
let lines = [Link]("\n");
if ([Link] > 0 &&
lines[[Link] - 1] === "") {
[Link]();
}
return lines;
}
288 Arrays
function TestSumArray() {
[Link]("Enter a list of numbers.");
readNumberArray(" ? ", processArray);
function processArray(array) {
[Link]("The sum is " + sumArray(array));
}
}
a subscript, reflecting the fact that arrays are used to hold data that would typically
be written with subscripts in mathematics.
The exercises for Chapter 7 ask you to write a program that implements a
letter-substitution cipher, which encrypts a message by replacing each letter in the
input text with an encoded version of that letter determined through the use of a
secret key. Although the task of implementing a letter-substitution cipher is an
interesting problem in its own right, an even more interesting computational
problem is figuring out how you might break a letter-substitution cipher if you did
not have access to the key.
My first step was to ascertain the predominant letters, as well as the least
frequent. Counting all, I constructed a table thus:
...
Now, in English, the letter which most frequently occurs is e. Afterwards,
the succession runs thus: a o i d h n r s t u y c f g l m w b k p q x z.
E however predominates so remarkably that an individual sentence of
any length is rarely seen, in which it is not the prevailing character. Edgar Allan Poe
Given that computerized analyses of English text were not available in his day, Poe
can perhaps be excused for making a few mistakes. Poe was, however, entirely
correct in his claim that the first step in discovering the hidden meaning of a
cryptogram is to construct a table showing how often each letter appears. A
program that does just that appears in Figure 8-4 on the next page.
290 Arrays
[Link]
8.3 Using arrays for tabulation 291
The following sample run illustrates the operation of the test program using the
first page of Dr. Seuss’s One Fish, Two Fish, Red Fish, Blue Fish as its input:
The output shows that the file contains four copies of the letters F, I, S, and H (one
for each of the four appearances of fish), three E’s, two O’s, and a smattering of
letters that each appear exactly once. Note that letters that never appear in the input
are not shown in the output.
and the element at the end of the array corresponds to the number of Z’s. If you call
the array letterCounts, you can initialize it by writing
This declaration uses the createArray function defined on page 280 to allocate
space for an array with 26 elements, as shown in this diagram:
Each time a letter appears in the input, you need to increment the corresponding
element in letterCounts. Finding the element to increment is simply a matter of
converting the character into an integer in the range 0 to 25 by converting the
character to uppercase and then subtracting the Unicode value of "A", which is
stored in LETTER_BASE. If the input character is stored in the variable ch, the code
necessary to increment the appropriate element in letterCounts looks like this:
letterCounts[[Link](0) - LETTER_BASE]++;
The use of files in JavaScript is further complicated by the fact that reading the
contents of a file might take some time, depending on the size of the file. Most
languages therefore include library functions that read data from a file and wait for
that process to complete before proceeding. That model, however, is not
appropriate for JavaScript, which doesn’t support the concept of suspending
execution until an operation is complete. If you need to read a file in JavaScript,
8.4 Reading text from files 293
you call a library function to start the operation, passing in a callback function that
is invoked when the read operation is complete.
To make it possible to work with files, the code supplied with this book includes
a library called [Link] that supports reading a text file within the
security constraints that JavaScript imposes. To use the library, you simply call
[Link], passing in a function that is called with the
complete text of the file once the read operation is complete. For example, you can
use the following function to count letter frequencies in a file chosen by the user:
function CountLetterFrequenciesInFile() {
[Link](countLetterFrequencies);
}
This function displays a button inviting the user to choose a file. Pressing that
button brings up a dialog that allows the user to browse the directory hierarchy to
find the desired file. When the user selects a file, the browser reads the contents of
the file as a string and then passes that string to countLetterFrequencies. As
noted in the preceding section, the parameter to countLetterFrequencies can
be either a string or an array of strings, so it works perfectly well to pass the entire
contents of the file to countLetterFrequencies.
If you sort this output by letter frequency in descending order, you discover that
the 12 most common letters in Middlemarch are
E T A O I N H S R D L U
The only difference between this frequency table and the statistical results for
modern English presented on page 289 is that the H and the S are reversed. In
general, the more text you analyze, the closer the frequencies will come to those
calculated for modern English.
Players take turns placing the letters X and O in the empty squares, trying to line up
three identical symbols horizontally, vertically, or diagonally.
Given this declaration, you can refer to the characters in the individual squares by
supplying two indices, one specifying the row number and another specifying the
8.6 Image processing 295
column number. In this representation, each number varies over the range 0 to 2,
and the individual positions on the board have the following designations:
Displaying an image requires two steps. The first step is to create or download
an image file in one of the standard formats. The name of the image file should end
with an extension that identifies the encoding format, which is typically .png. That
file, moreover, must be stored in the same directory as the [Link] file. The
second step is to create a GImage object and add it to the graphics window, just as
you would with any other graphical object. For example, if you have an image file
called [Link], you can display that image in the upper left corner of the
graphics window using the following line:
[Link](GImage("[Link]"));
wanted to center the image in the graphics window. Like the other classes that
represent graphical objects, GImage implements getWidth and getHeight, which
suggests that you could center the image using the following code:
The problem with this code segment is that JavaScript implements reading an
image as an asynchronous operation. Calling the GImage function starts the process
of reading the image but does not wait for that process to complete. The
implementation of the GImage class knows how to update the image on the graphics
window when the image is fully loaded, but it is impossible to get any information
about the image until that process is complete. In particular, you can’t determine
the size of the image by calling getWidth and getHeight. Since you need this
information to center the image, you have to make sure that the image is fully
loaded before you can determine where to place it in the window.
Like the GWindow class, the GImage class implements the addEventListener
method, which takes the name of an event and a callback function that is called
when the event occurs. For GImage, the relevant event is the "load" event, which
is triggered when the image is fully loaded. The addEventListener method
makes it possible to center an image using the following code:
You also need to use a callback function if you need to scale an image so that it
has the desired size. The code for the [Link] program in Figure 8-5
illustrates the use of scaling to display an image so that it fills the available space in
the window. The image, which shows the earth as seen by the Apollo 17 astronauts
on their way to the moon in December 1972, is stored in an image file named
[Link]. The [Link] program reads that image file into a
GImage object and the callback function then adds that object to the window.
The [Link] program introduces one new method from the graphics
library that is particularly useful for images. The scale method changes the image
8.6 Image processing 297
size by the specified scale factor. If the variable image contains a GImage object,
calling
[Link](0.5);
resizes the object to make it half as big in each dimension. Similarly, calling
[Link](2);
[Link]([Link]() / [Link]());
scales the image so that it fills the entire width of the window. The program,
however, cannot perform this calculation without knowing the width of the image,
298 Arrays
which is available only in the callback function. Loading the [Link] file for
the [Link] program produces the following display:
Even in cases in which your use of an image falls within the “fair use”
guidelines, it is important to give proper credit to the source. As a general rule,
whenever you find an image on the web that you would like to use, you should first
check to see whether that website explains its usage policy. Many of the best
sources for images on the web have explicit guidelines for using their images.
Some images are absolutely free, some are free for use with citation, some can be
used in certain contexts but not others, and some are completely restricted. For
example, the website for the National Aeronautics and Space Administration
([Link] has an extensive library of images about the exploration
8.6 Image processing 299
of space. As the website explains, you can use these images freely as long as you
include the citation “Courtesy NASA/JPL-Caltech” along with the image. The
[Link] program follows these guidelines and includes the requested
citation on the page with the image.
Representation of images
In JavaScript, an image is a rectangular array in which the image as a whole is a
sequence of rows, and each row is a sequence of individual pixel values. The value
of each element in the array indicates the color that should appear in the
corresponding pixel position on the screen. From Chapter 4, you know that you can
specify a color in JavaScript by indicating the intensity of each of the primary
colors. Each of those intensities ranges from 0 to 255 and therefore fits in an
eight-bit byte. The color as a whole is stored in a 32-bit integer that contains the
red, green, and blue intensity values along with a measure of the transparency of the
color, represented by the Greek letter alpha (α). For the opaque colors used in most
images, the value of α is always 255 in decimal, which is 11111111 in binary or FF
in hexadecimal.
As an example, the following diagram shows the four bytes that form the color
PINK, which JavaScript defines using the hexadecimal values FF, C0, and CB as the
red, green, and blue components. Translating those values to their binary form
gives you the following:
α red green blue
The fact that JavaScript packs all the information about a color into a 32-bit integer
means that you can store an image as a two-dimensional array of integers. Each
element of the entire array contains one row of the image. In keeping with
JavaScript’s coordinate system, the rows of an image are numbered from 0 starting
at the top. Each row is an array of integers representing the value of each pixel as
you move from left to right.
The height of the image is equal to the number of rows in the pixel array. The
width is the number of elements in any of the rows, each of which has the same
length in a rectangular image. Thus, you can initialize variables to hold the height
and width of the pixel array like this:
argument and then uses the getWidth and getHeight methods to center the image
in the window.
The GImage class includes several methods to simplify the task of manipulating
image data. These methods appear in Figure 8-7. As you can see from the first
section of the figure, the GImage class supports two factory methods, one for
reading data from a file and one to construct a GImage from a two-dimensional
array. Given an initialized image, the getPixelArray method returns the array of
pixels stored within the image. The GImage class also exports class methods for
retrieving the red, green, and blue components of a pixel from an integer and for
assembling red, green, and blue values into the corresponding integer form.
These new capabilities in the GImage class make it possible for you to write
programs to manipulate images, in much the same way that a commercial system
302 Arrays
like Adobe Photoshop™ does. The general strategy consists of the following three
steps, all of which must be performed after the image is fully loaded:
The following function definition uses this pattern to flip an image vertically:
function flipVertical(image) {
let array = [Link]();
[Link]();
return new GImage(array);
}
[Link]
304 Arrays
Summary
In this chapter, you have learned how to use arrays, which are the primary data
structure that JavaScript uses to represent ordered lists of data. The important
points introduced in this chapter include:
• Like most languages, JavaScript includes a built-in array type for storing an
ordered collection of elements. Each element in an array has an integer index
that indicates its position in the array. Index numbers begin with 0.
• JavaScript arrays are most often created by enclosing a list of the elements in
square brackets, separated by commas.
• The number of elements in a JavaScript array is stored in a field called length.
• You can select a particular element of an array by indicating the index of the
desired element in square brackets after the array name. This operation is called
selection.
• Arrays are often used together with for loops that allow you to cycle through
the elements of the array.
• Arrays in JavaScript are stored as references to the memory containing the
values of the array. An important implication of this design is that passing an
array as a parameter does not copy the elements. Instead, JavaScript copies the
reference value, which specifies the internal location of the array data. As a
result, if a function changes the values of any elements of an array passed as a
parameter, those changes will be visible to the caller.
• Arrays support a variety of operations implemented as methods. The most
important array methods are listed in Figure 8-2 on page 281.
• Although JavaScript offers little support for data files, the code supplied with
this book includes a [Link] library that exports methods that
allow the user to select a file.
• JavaScript supports arrays with any number of dimensions, which are
represented as arrays of arrays.
• Images are represented as two-dimensional arrays of integers, each of which
specifies the color of a pixel as a combination of its red, green, and blue color
components.
• The graphics library includes a class called GImage that supports images in a
way that gives clients access to the underlying pixel array.
Review questions 305
Review questions
1. Define the following terms as they apply to arrays: element, index, length, and
selection.
2. How would you create an array called dwarves containing the names of the 13
dwarves who arrived at Bilbo’s doorstep in J. R. R. Tolkien’s fantasy The
Hobbit? Their names, in the order in which they appeared, are Dwalin, Balin,
Kili, Fili, Dori, Nori, Ori, Oin, Gloin, Bifur, Bofur, Bombur, and Thorin.
4. True or false: Arrays violate the following rule for parameter passing as
expressed in the following sentence from Chapter 5: The value of each
argument is copied into the corresponding parameter variable.
5. The following diagram appears in the chapter to illustrate the effect of the five
array methods that add and remove elements from an array, but the method
names are missing from this version:
Supply the missing method names, ideally without looking back at the chapter.
6. Trace the execution of the following program and show the output for each call
to [Link]:
function ArrayMethodsReview() {
let array = [ 0, 1 ];
[Link](2);
[Link]("array = [" + array + "]");
[Link]([Link]());
[Link]("array = [" + array + "]");
[Link](1, 0, 0);
[Link]("array = [" + array + "]");
}
7. How does a comparison function used in conjunction with the sort method
indicate the result of the comparison?
11. The text uses the following declaration to initialize an empty Tic-Tac-Toe board:
let board = [ [ "", "", "" ],
[ "", "", "" ],
[ "", "", "" ] ];
Think carefully about how arrays are represented before offering your answer.
12. What class from the graphics library makes it possible to display images on the
graphics window?
15. Given a pixel array, how do you determine the width and height of the image?
Exercises
1. In statistics, a collection of data values is usually referred to as a distribution.
A primary purpose of statistical analysis is to find ways to compress the
complete set of data into summary statistics that express properties of the
distribution as a whole. The most common statistical measure is the mean
(traditionally denoted by the Greek letter µ), which is simply the traditional
average. Another common statistical measure is the standard deviation
(traditionally denoted as σ), which provides an indication of how much the
values in a distribution x1, x2, . . . , xn differ from the mean. If you are
computing the standard deviation of a complete distribution as opposed to a
sample, the standard deviation can be expressed as follows:
σ =
The uppercase sigma (Σ) indicates a summation of the quantity that follows,
which in this case is the square of the difference between the mean and each
individual data point.
Exercises 307
Create a library called [Link] that exports the functions mean and
stdev, each of which takes an array of numbers representing a distribution and
returns the corresponding statistical measure. Make sure that the comments are
sufficient for clients to understand how to use these functions.
3. Both the reverseArray function defined in this chapter and the reverse
method that is defined for all array objects change the values in the calling
array. An alternative approach, which was adopted for the reverse function
for strings in Chapter 7, is to return a new array leaving the original unchanged.
Use this strategy to implement a function createReversedArray(array)
that returns an array whose elements are the reverse of the original values
without changing the contents of array. Your function should allow you to
generate the following console log:
4. Write a program that checks whether the parentheses, brackets, and curly
braces in a string are properly matched. If, for example, you look at the string
{ s = 2 * (a[2] + 3); x = (1 + (2)); }
you will discover that all the bracketing operators are correctly nested, with
each open parenthesis matched by a close parenthesis, each open bracket
matched by a close bracket, and so on. On the other hand, the following strings
are all unbalanced for the reasons indicated:
The easiest way to solve this problem is to go through the string character
by character ignoring all characters except for the bracketing operators. If you
see one of the opening operators (a left parenthesis, left bracket, or left brace),
push that operator on a stack that keeps track of the unmatched operators.
When you see a closing operator (a right parenthesis, right bracket, or a right
brace), pop the stack and make sure that the operators match.
6. Use the split, join, and sort methods to write a function sortLetters
that rearranges the characters in a string so that they appear in lexicographic
order. For example, calling sortLetters("cabbage") should return the
string "aabbceg".
7. A histogram is a graph that displays a set of values by dividing the data into
separate ranges and then indicating how many data values fall into each range.
For example, given the set of exam scores
100, 95, 47, 88, 86, 92, 75, 89, 81, 70, 55, 80
The asterisks in the histogram indicate one score in the 40s, one in the 50s, five
in the 80s, and so forth. When you generate histograms on the console,
however, it is easier to display them sideways on the page, like this:
Exercises 309
Write a program called Histogram that allows the user to select a file
containing exam scores ranging from 0 to 100 and then displays a histogram of
those scores, divided into the ranges 0–9, 10–19, 20–29, and so forth, up to the
range containing only the value 100. Your function should match the format
shown in the sample run as closely as possible.
that tests to see whether square is a magic square. Your function should work
for matrices of any size. If you call isMagicSquare with an array with a
different number of rows and columns, your function should return false.
Write a function
Exercises 311
function countMines(mines)
that goes through the array of mines and returns a new array with the same
dimensions that indicates how many mines are in the neighborhood of each
location. If a location contains a mine, the corresponding entry in the matrix
returned by countMines should be −1. In Minesweeper, the neighborhood
consists of the eight adjacent locations as long as those locations are inside the
array. For example, the declaration
10. Over the last couple of decades, a logic puzzle called Sudoku has become
popular throughout the world. In Sudoku, you start with a 9 × 9 array of
integers in which some of the cells have been filled with a digit between 1
and 9 as shown on the left side of Figure 8-10. Your job in the puzzle is to fill
each of the empty spaces with a digit between 1 and 9 so that each digit
312 Arrays
appears exactly once in each row, each column, and each of the smaller 3 × 3
squares. The solution appears at the right side of Figure 8-10. Each Sudoku
puzzle is carefully constructed so that there is only one solution.
Although the algorithmic strategies you need to solve Sudoku puzzles lie
beyond the scope of this book, you can easily write a function that checks to
see whether a proposed solution follows the Sudoku rules against duplicating
values in a row, column, or outlined 3 × 3 square. Write a function
function checkSudokuSolution(puzzle)
that performs this check and returns true if the puzzle is a valid solution.
12. Write a method rotateLeft that takes a GImage and produces a new GImage
in which the original has been rotated 90 degrees to the left.
CHAPTER 9
Objects
I have always tried to identify and focus in on what is essential
and yields unquestionable benefits. For example, the inclusion
of a coherent and consistent scheme of data type declarations
in a programming language I consider essential.
— Niklaus Wirth, Turing Award Lecture, 1984
Swiss computer scientist Niklaus Wirth designed and engineered several early programming languages
including Euler, PL360, Algol-W, and Pascal, which became the standard language for introductory
computer science throughout the 1970s and 1980s. Although Grace Hopper’s COBOL language described
on page 79 included support for data records, Pascal was the first programming language to integrate the
record concept into the type system in a consistent way. In 1975, Wirth published an influential book
entitled Algorithms + Data Structures = Programs, which offers an eloquent defense of the idea that data
structures are as fundamental to programming as algorithms. Niklaus Wirth received the ACM Turing
Award in 1984.
314 Objects
When you learned about arrays in Chapter 8, you took your first steps toward
understanding an extremely important idea in computer programming: the use of
compound data structures to represent complex collections of information. When
you declare an array in the context of a program, you are able to combine an
arbitrarily large number of data values into a single structure that has integrity as a
whole. Then, if you need to do so, you can select particular elements of that array
and manipulate them individually. But you can also treat the array as a unit and
manipulate it all at once.
The ability to take individual values and organize them into coherent units is one
of the fundamental features of modern programming languages. Functions allow
you to unify many independent operations under a single name. Compound data
structures—of which arrays are only one example—offer the same facility in the
data domain. In each case, being able to combine the tiny pieces of a program into
a single, higher-level structure provides both conceptual simplification and a
significant increase in your power to express ideas in programming. The power of
unification is hardly a recent discovery; it has given rise to social movements and to
nations, as reflected in the labor anthem that proclaims “the union makes us strong”
and the motto “E Pluribus Unum”—“out of many, one”—on the Great Seal of the
United States.
Although arrays are a powerful tool when you need to model real-world data that
can be represented as a list of ordered elements, it is also important to be able to
combine unordered data values into a single unit. This chapter describes how
JavaScript supports such assembled collections of data values and how to use those
values effectively in programs.
Encapsulation has advantages for both the client and the implementer. On the
one hand, encapsulation benefits the client by hiding the complexity associated with
9.1 Objects in JavaScript 315
Objects as records
Objects in JavaScript are similar to arrays in that they allow multiple data values to
be considered as a unit. The fundamental difference lies in how the individual data
values are identified. In an array, each of the elements is identified by an index
number. In an object, each of the internal components, which are generally called
properties in JavaScript, is identified by a name.
Objects are useful whenever you have to model a collection of data that has
individual components but nonetheless represents an integrated whole. If, for
example, you are designing a payroll system for a company, each individual
employee has a variety of attributes such as name, job title, and salary, but it still
makes sense to think of all those components as a single entity that describes a
particular employee. At the rather small firm of Scrooge and Marley that appears in
A Christmas Carol by Charles Dickens, the data for the two employees might look
something like this:
Each of the two objects in this diagram is a combination of three distinct parts: a
name property indicating the name of the employee, a title property indicating
the job title, and a salary property indicating the annual compensation. Programs
can treat these employee objects at either of two levels. At the holistic level, an
employee acts as a single data value. You can assign it to a variable, pass it as a
parameter, or return it as a result. When you need to take a more reductionistic
view, you can select and manipulate the individual properties.
Creating objects
JavaScript makes it easier to create compound objects than most other modern
languages do. To create a compound object, you simply enclose a set of property
specifications inside curly braces. Each property specification consists of the
property name and its value separated by a colon, with the property specifications
316 Objects
themselves separated by commas. The following line, for example, declares the
variable clerk containing the information for Bob Cratchit:
let clerk =
{ name: "Bob Cratchit", title: "clerk", salary: 25 };
Like arrays, objects in JavaScript are treated as references to the actual value.
The value of clerk is therefore diagrammed most accurately like this:
Selecting properties
Given a JavaScript object, you can select individual properties using the dot
operator, which is written in the form
[Link]
where object specifies the object as a whole and name specifies the desired
property. Thus, given the declaration of clerk from the preceding section, you
could select the name property using the expression [Link], which in this
case has the string value "Bob Cratchit".
Fields are also assignable. For example, when the reformed Mr. Scrooge tells
Bob Cratchit, “I am about to raise your salary,” he could do so by writing
[Link] += 5;
which gives the underpaid clerk an extra five pounds a year. Moreover, because
objects are stored as references, any changes made to the properties of an object
passed as a parameter to a function will persist after that function returns. For
example, if Ebenezer Scrooge decided to be even more generous, he could define
the following function and then use it to double the salary of any employee:
function doubleSalary(employee) {
[Link] *= 2;
}
9.2 Using objects as maps 317
The rest of this book contains several examples of objects that use the JSON rules.
where object is the object from which the selection is made and name is a string
expression indicating the property name. For example, if the variable clerk is
defined as shown on the previous page, you can select the component containing the
clerk’s salary either by writing [Link] or by writing clerk["salary"].
Both forms select the property named "salary" from the object stored in clerk.
A simple way to implement this facility is to create a map whose keys are the
airport codes and whose values are the city names. If this map is stored in the
constant AIRPORT_CODES, all you need to do to find the city corresponding to the
three-letter airport code is evaluate the expression AIRPORT_CODES[code]. If
AIRPORT_CODES contains a property matching the three-letter code for the airport,
the expression will return the corresponding city name from the map. If there is no
property matching the code, the value of the expression is undefined.
Initializing a map
The interesting question is how to initialize AIRPORT_CODES. In most languages,
the traditional approach would be to read the data from a file containing the
three-letter code for each airport along with the name of the city served by that
airport. As noted in Chapter 8, however, the security restrictions imposed by
JavaScript make it difficult to work with data files. Given those restrictions, the
most straightforward approach to initializing a map like AIRPORT_CODES is to
include the JSON representation of the complete map in a JavaScript file and then
load it using a <script> tag, just as you would load a JavaScript library. For the
application that translates three-letter airport codes to city names, you could store
the data in the file [Link] shown in Figure 9-1 at the top of the next
page. The complete list of three-letter codes is long but still fits easily in a
JavaScript file.
The [Link] data file need not reside on the same server that runs
the application. Because web pages often use libraries from standard repositories
on the web, browsers allow the [Link] file to load scripts from any URL.
This file, for example, might be stored on the IATA website, which would then be
able to update the map whenever a new airport code was assigned. Web
applications that need this information could then load the [Link] file
directly from that website.
9.2 Using objects as maps 319
In this pattern, key is the name of a variable that holds the values of the keys and
map is the variable containing the map.
When you use a for loop to iterate through the elements of a map, it is
important to make no assumptions about the order in which those keys appear. You
9.3 Representing points 321
cannot assume, for example, that the keys will be delivered in alphabetical order. If
you need to ensure that the keys appear alphabetically, you need to store the keys in
an array and then call sort to put the elements into the desired order.
Combining the x and y coordinates into a single object makes it possible to work
with points as composite values, which means that you can manipulate them like
any other data value. You can assign a point to a variable, create an array of points,
pass a point as an argument to a function, and return a point as a result. This last
example—returning a point as the result of a function call—adds a new capability
that would otherwise be difficult to achieve. A JavaScript function is allowed to
322 Objects
return only a single value, so there is no way for a function to return the x and y
coordinates independently. A function can, however, return a point, which acts as a
single value. The caller can then extract its x and y coordinates.
let origin = { x: 0, y: 0 };
let lowerRight = { x: GWINDOW_WIDTH, y: GWINDOW_HEIGHT };
The first declaration defines the variable origin to be the point (0, 0) at the upper
left corner of the window; the second defines the variable lowerRight to be the
point in the lower right corner, assuming that the constants GWINDOW_WIDTH and
GWINDOW_HEIGHT are set up as in the earlier graphical examples. Once you have
these variables, you can create a GLine that runs diagonally across the window like
this:
function Point(x, y) {
return { x: x, y: y };
}
creates and returns an object whose conceptual type is Point, even though there is
no difference internally between objects created using the Point function and those
created using JavaScript’s standard format for objects. In keeping with the
convention for defining factory methods that serve as types, the name Point begins
with an uppercase letter, just as GRect and GOval do.
common that the ECMA 5.0 standard allows you to leave out the property names in
this case. Thus, it is now legal in JavaScript to write the Point method like this:
function Point(x, y) {
return { x, y };
}
Given this definition of Point, the declarations for the variables origin and
lowerRight can be rewritten like this:
To see how the notion of encapsulation might apply to the design of a Point
type, it may help to think about how the current implementation of Point differs
from that of the classes in the graphics library. In some ways, the strategy for
representing a Point is similar to that of representing a GRect. In much the same
way that the Point factory method creates a new object whose conceptual type is
Point, the GRect factory creates a new object whose conceptual type is GRect.
The most obvious difference lies in the way clients refer to component values
within the object. Given a Point object as it is currently defined, clients select the
x and y properties using the dot operator. Given a GRect object, clients obtain its
coordinates by calling the methods getX and getY. Although a GRect logically
has an x and a y position, it is not possible for the client to make direct references to
those internal properties.
To apply this strategy of encapsulation to the Point type, you need to learn how
to add methods to a JavaScript object. At a minimum, the Point type needs to
324 Objects
define the methods getX and getY so that clients can obtain the coordinates of the
point without referring directly to the x and y properties. Thus, instead of having
clients write
pt.x
[Link]()
Methods that return the value of a property are called getters. Methods that change
the value of a property—which are far less common—are called setters.
Adding methods to the Point type is not as hard as you might at first imagine.
The receiver syntax used for method calls looks very much like the operation of
selecting a property from an object and is in fact implemented in precisely that way.
The name getX is simply a property in a Point object, just as x is. The difference
is that the value of the getX property is a function that returns the value of the
internal x component. JavaScript functions are, after all, first-class objects, and it is
perfectly appropriate to store a function value in a property of an object. The only
complication in writing the getX function is that its implementation must have
some way of gaining access to the value of the x component of the point.
[Link]()
the implementation of getX can refer to the x property of the receiver as this.x.
This strategy allows you to define the following implementation of the factory
method Point, which creates an object containing properties named x and y along
with methods for obtaining the corresponding values using the receiver syntax:
function Point(x, y) {
return {
x: x,
y: y,
getX: function() { return this.x; },
getY: function() { return this.y; }
};
}
9.3 Representing points 325
The key to a better strategy lies in recognizing that any function defined inside
the factory method for Point already has access to the values of x and y because
they are part of the closure that contains the local variables of the factory method.
Thus, getX and getY can simply return these values without using the keyword
this at all, as illustrated in the following version of the factory method:
function Point(x, y) {
function getX() { return x; }
function getY() { return y; }
return { getX, getY };
}
The return statement at the end of the factory method returns an object with only
two public properties, which are the methods getX and getY. The object does not
include the definitions of the x and y properties, which are private to the closure.
All classes in this book use this closure-based approach, which is adapted from a
model recommended by Doug Crockford, who was introduced on page 39. This
strategy has two primary advantages. First, it hides the underlying representation
from the client. Second, this strategy eliminates the need for the keyword this,
which is a common source of confusion in JavaScript.
[Link](Point(2, 3))
JavaScript will automatically invoke the toString method of the Point object to
produce the string "(2, 3)", which is then displayed on the console.
326 Objects
Adding toString to the Point class gives rise to the final version of the
Point class, which appears in Figure 9-4 along with a simple test program. The
only statement other than function definitions inside the Point factory method is
which returns an object whose only accessible properties are the three exported
methods. The values of the variables remain safely encapsulated inside the closure.
9.3 Representing points 327
In the code shown in Figure 9-4, the definitions of the methods getX, getY, and
toString appear after the return statement. This ordering is possible because
JavaScript applies a process called hoisting to the inner functions as described in
Chapter 5. As a result, the definitions of these methods are available at every point
inside the factory method. The advantage of relying on hoisting is that doing so
ensures that the code that implements the factory method is not separated from the
method header by the definitions of the inner functions, which can easily run for
more than a page in a class that exports several public methods. Constants and
variables declared using the let keyword, however, are not hoisted in JavaScript
and must therefore be defined before they are used.
From here, you create a figure by winding a single piece of yarn through the pegs,
starting at peg 0 and then moving ahead DELTA spaces on each cycle. For example,
if DELTA is 11, the yarn goes from peg 0 to peg 11, then from peg 11 to peg 22, and
then (counting past the beginning) from peg 22 to peg 5, as follows:
328 Objects
[Link]
9.3 Representing points 329
The process continues until the yarn returns to peg 0, creating the following pattern:
To get a sense of why this distinction might be important, consider the arithmetic
problem of adding together the following fractions:
Basic arithmetic makes it clear that the mathematically precise answer is 1, but that
is not the answer you get if you use JavaScript’s floating-point arithmetic.
The problem becomes clear if you write the following JavaScript program:
function FractionSum() {
let sum = 1/2 + 1/3 + 1/6;
[Link]("1/2 + 1/3 + 1/6 = " + sum);
}
The problem is that the memory cells used to store numbers have a limited storage
capacity, which in turn restricts the precision they can offer. Within the limits of
JavaScript’s standard arithmetic, the sum of one-half plus one-third plus one-sixth is
closer to 0.9999999999999999 than it is to 1.0. By contrast, rational numbers are
not subject to this type of rounding error because no approximations are involved.
What’s more, rational numbers obey well-defined arithmetic rules, which are
summarized in Figure 9-7. Since JavaScript does not include rational numbers
among its predefined types, you have to implement Rational as a new class.
9.4 Rational numbers 331
From my own experience, I’ve often found the following approach helpful:
1. Think generally about how clients are likely to use the class. From the very
beginning of the process, it is essential to remember that library classes are
designed to meet the needs of clients and not for the convenience of the
implementer. In a professional context, the most effective way to ensure that a
new class meets those needs is to involve clients in the design process. At a
minimum, however, you need to put yourself in the client role as you sketch the
outlines of the class design.
2. Determine what information belongs in the private state of each object.
Although the private data contained in the closure of the factory method is
conceptually part of the implementation, it simplifies the later design phases if
you have an intuitive sense of what information objects of this class contain.
3. Determine the parameters needed for the factory method. Whenever a client
creates a new instance of your class, the first step in the process is making a call
to the factory method. As part of the design phase, you need to decide what
information the client will want to supply at the time the object is created,
which in turn determines what parameters the factory method will need. In the
case of the Point class, the client must supply the x and y coordinates.
4. Enumerate the operations that will become the public methods of the class. In
this phase, the goal is to define the names and parameters for the methods
exported by the class, thereby adding specificity to the general outline you
developed at the beginning of the process.
332 Objects
5. Code and test the implementation. Once you have completed the overall
design, you need to implement it. Writing the actual code is not only essential
to having a working program but also offers validation for the design. As you
write the implementation, it is sometimes necessary to revisit the interface
design if, for example, you discover that a particular feature is difficult to
implement at an acceptable level of efficiency. As the implementer, you also
have a responsibility to test your implementation to ensure that the class
delivers the functionality it advertises in the interface.
The sections that follow carry out these steps for the Rational class.
Since this example is a textbook scenario, however, it isn’t possible for you to
schedule meetings with prospective clients. The primary purpose of the example is
to illustrate the structure of class definitions in JavaScript. Given these limitations
and the need to manage the complexity of the example, it makes sense to limit the
design goals so that the Rational class implements only the arithmetic operations
defined in Figure 9-7.
Although the similar definition in the Point class did not require any further
manipulation of the parameters, it makes sense to think at this point whether there
are any additional options you would like to offer to the client or any constraints
that need to be imposed on the arguments. For example, it might be useful to allow
clients to create Rational objects from integers by passing a single argument to
the factory method, so that the call Rational(2) is treated automatically as if it
were a call to Rational(2, 1) signifying the integer 2. That feature is easy to
implement by specifying a default value of 1 for the second argument, as follows:
Similarly, it might be useful to ensure that the value passed for den is not 0, since
division by zero is not legal in a rational number. Although Chapter 11 describes
other strategies for reporting errors, it is consistent with JavaScript’s conventions
for arithmetic to return the constant NaN, signifying that the result is not a number.
The beginning of the Rational factory method might therefore look like this:
There are, however, additional constraints that you might want to impose on the
values of num and den stored within the closure. If the client is given unconstrained
choice for the numerator and denominator, there will be many different ways to
represent the same rational number. For example, the rational number one-third can
be written as a fraction in any of the following ways:
Since these fractions all represent the same rational number, it is inelegant to allow
arbitrary combinations of numerator and denominator values in a Rational object.
It simplifies the implementation if every rational number has a consistent, unique
representation.
• The denominator is always positive, which means that the sign of the value is
stored with the numerator. Thus, if the denominator supplied by the client is
negative, it is necessary to invert the sign of both the numerator and the
denominator.
• The rational number 0 is always represented as the fraction 0/1. Without this
rule, there would be many distinct representations for the rational number 0.
• The fraction is always expressed in lowest terms, which means that any common
factors are eliminated from the numerator and the denominator. In practice, the
easiest way to reduce a fraction to lowest terms is to divide both the numerator
334 Objects
and the denominator by their greatest common divisor. Fortunately, you already
know how to compute the greatest common divisor using Euclid’s algorithm,
which appears as the function gcd on page 107.
Implementing these rules requires the following code in the factory method,
which still needs the definitions of the exported methods and the return statement:
Although there are other methods that would make sense in a professionally
designed version of the Rational class, the only additional facilities defined in this
implementation are a toString method and the methods getNum and getDen,
which return the numerator and denominator components of the Rational object.
The reason for including these getter methods as part of the object will become clear
in the discussion of the implementation.
[Link]
336 Objects
[Link]
9.4 Rational numbers 337
the most complex part of the implementation is the factory method for which you
have already seen the necessary code, the contents of [Link] are reasonably
straightforward.
As is true for any JavaScript class that maintains its local state as part of a
closure, all the code in Figure 9-8 appears inside the factory method. Because the
definitions of the exported methods appear within the body of the factory method
for the Rational class, they have access to the parameter variables num and den as
well as to the other functions. The factory method looks like this in pseudocode:
The code for the arithmetic operators follows directly from their mathematical
definitions. The implementation of the add method, for example, looks like this:
function add(r) {
return Rational(num * [Link]() + [Link]() * den,
den * [Link]());
}
The arguments to Rational are simply the values required by the addition formula:
In the add method, the values a and b refer to the numerator and denominator of the
current Rational object, which are available in the variables num and den. The
values c and d refer to the corresponding components of the Rational object
passed as the variable r. Because these values are not part of the current closure,
the code needs to call the getNum and getDen methods to retrieve these values.
Those methods must therefore be exported as part of the class.
The return statement in the add method calls the Rational factory method
with the computed values of the numerator and denominator for the result. The
code in the factory method ensures that the result is properly reduced to lowest
terms and meets the other requirements imposed on the properties maintained inside
each Rational object.
338 Objects
function RationalSum() {
let a = Rational(1, 2);
let b = Rational(1, 3);
let c = Rational(1, 6);
let sum = [Link](b).add(c);
[Link]("1/2 + 1/3 + 1/6 = " + sum);
}
My favorite example of a linked list takes its inspiration from the following
passage in The Return of the King by J. R. R. Tolkien:
For answer Gandalf cried aloud to his horse. “On, Shadowfax! We must
hasten. Time is short. See! The beacons of Gondor are alight, calling for
aid. War is kindled. See, there is the fire on Amon Dîn, and flame on
Eilenach; and there they go speeding west: Nardol, Erelas, Min-Rimmon,
Calenhad, and the Halifirien on the borders of Rohan.”
9.5 Linking objects together 339
In adapting this scene for the concluding episode in his Lord of the Rings film
trilogy, Peter Jackson produced an evocative interpretation of this scene. After the
first beacon is lit in the towers of Minas Tirith, we see the signal pass from
mountaintop to mountaintop as the keepers of each signal tower, ever vigilant, light
their own fires when they see the triggering fire at the preceding station. The
message of Gondor’s danger thus passes quickly over the many leagues that
separate it from Rohan, as illustrated in Figure 9-9.
The code in Figure 9-11 on the next page implements the SignalTower class,
which exports two methods—getName and signal—each of which is described in
the comments that precede the method. The SignalTower implementation also
exports a class method called createChain that creates a list of SignalTower
objects from an array of names. The program in Figure 9-12 on page 341 simulates
the process of lighting the Beacons of Gondor by creating the linked list of towers
and then calling signal on the first tower in the list.
340 Objects
[Link]
9.5 Linking objects together 341
function signal() {
[Link]("Lighting " + name);
if (link !== null) [Link]();
}
The implementation uses [Link] to report the lighting of the current tower.
If this tower is linked to another one, it invokes the signal method on that tower.
Although this pattern represents a recursive invocation of signal, it is far easier to
think of the operation in terms of the object-oriented metaphor of sending messages.
When one tower receives a signal, it lights its signal fire, which sends a signal to the
next tower in the chain.
342 Objects
Summary
This chapter introduces the concept of an object, which is a data structure used to
represent a collection of values. Like arrays, objects combine multiple values into a
single unit. In an array, individual elements are selected using a numeric index; in
an object, individual properties are selected by name. JavaScript also uses objects
to implement encapsulation, which provides a barrier between data and behavior.
Review questions
1. What word does JavaScript use for the individual components of an object?
7. True or false: Iterating through the keys in a map always processes the keys in
alphabetical order.
10. What would happen if you changed the value of DELTA in [Link]
from 113 to 104? Would the picture be as striking? Why or why not?
12. How does JavaScript make it possible to hide the internal representation of a
class from its clients?
16. What restrictions does the factory method for the Rational class place on the
values of the num and den variables?
19. What is the conventional strategy for indicating the end of a linked list?
Exercises
1. Write a function printPayroll that takes an array of employees, each of
which is defined as a simple JavaScript object, and prints on the console a list
for each employee showing the name, title, and salary. For example, if
SCROOGE_AND_MARLEY has been initialized as a two-element array containing
344 Objects
the entries for Ebenezer Scrooge and Bob Cratchit shown in the chapter, your
function should be able to generate the following sample run:
2. The game of dominos is played using pieces that are usually black rectangles
with some number of white dots on each side. For example, the domino
is called the 4-1 domino, with four dots on its left side and one on its right.
As with the examples in the text, the data structures that maintain the number of
dots should be private to the class.
3. Define a Card class suitable for representing a standard playing card, which is
identified by two components: a rank and a suit. The rank is stored as an
integer between 1 and 13 in which an ace is a 1, a jack is an 11, a queen is a 12,
and a king is a 13. The suit is also represented internally as an integer, which
has will always be equal to one of the following four constants:
const CLUBS = 0;
const DIAMONDS = 1;
const HEARTS = 2;
const SPADES = 3;
Exercises 345
• A factory method that takes either of two forms. If Card is called with two
arguments, as in Card(10, DIAMONDS), it should create a card from those
components. If Card is called with one argument, it should interpret the
argument as a string composed of a rank (either a number or the first letter
of a symbolic name) and the first letter of the suit, as in "10D" or "QS".
• A toString method that converts the card to a string as described in the
outline of the factory method. The card Card(QUEEN, SPADES), for
example, should have the string representation "QS".
• The getter methods getRank and getSuit.
In addition to the Card class itself, the [Link] file should export the
constants that define the suits along with the names of the four ranks that are
usually named rather than numbered (ACE, JACK, QUEEN, KING). Your
implementation should make it possible to run the following test program:
function TestCardClass() {
for (let suit = CLUBS; suit <= SPADES; suit++) {
let str = "";
for (let rank = ACE; rank <= KING; rank++) {
if (rank > ACE) str += ", ";
str += Card(rank, suit);
}
[Link](str);
}
}
5. In May of 1844, Samuel F. B. Morse sent the message “What hath God
wrought!” by telegraph from Washington to Baltimore, heralding the beginning
of the age of electronic communication. In his 1998 book, The Victorian
Internet, British journalist Tom Standage goes so far as to argue quite plausibly
that the impact of the telegraph on the 19th-century world was in many ways
more profound than the impact of the Internet on the 20th.
Write a program that reads in lines from the user and translates each line
either to or from Morse code, depending on the first character of the line:
• If the line starts with a letter, you need to translate it to Morse code. Any
characters other than the 26 letters should simply be ignored.
Exercises 347
• If the line starts with a period (dot) or a hyphen (dash), it should be read as
a series of Morse code characters that you need to translate back to letters.
You may assume that each sequence of dots and dashes in the input string
will be separated by spaces, and you are free to ignore any other characters
that appear. Because there is no encoding for the space between words, the
characters of the translated message will be run together when your
program translates in this direction.
The program should end when the user enters a blank line. A sample run of
this program (taken from the messages between the Titanic and the Carpathia in
1912) might look like this:
8. Write a function midpoint that takes two values of type Point and returns a
new Point object whose coordinates define the midpoint of the line segment
specified by the two parameters. For example, if the variables upperLeft and
lowerRight are defined as
9. The Rational class presented in the text defines the methods add, sub, mul,
and div but offers no way to compare two rational values other than
348 Objects
subtracting one Rational value from another and looking at the sign of the
numerator. Add the methods equalTo, lessThan, and greaterThan to the
[Link] file. Each of these methods should compare the current
Rational object to a second one passed as an argument and return a Boolean
value indicating whether the specified relation holds between the two values.
10. There is no reason to restrict the process of passing a visual signal to a single
chain of towers as the [Link] implementation in Figure 9-11 does.
There could, after all, be several towers watching for the signal from Minas
Tirith, each of which would respond by lighting its own signal fire and then
propagating that signal to its links. The process of forwarding a signal to
subsequent towers can split into multiple paths at each tower, leading to a
branching structure that computer scientists call a tree.
Barbara Liskov earned her bachelor’s degree in mathematics from the University of California at Berkeley
in 1961. After being introduced to computers and programming through jobs at the MITRE Corporation
and Harvard University, Liskov returned to California, where she received her Ph.D. in Computer Science
from Stanford University in 1968. For most of her career, Liskov was Professor of Electrical Engineering
and Computer Science at the Massachusetts Institute of Technology, where she conducted pioneering work
on data abstraction in programming languages. The ideas that she championed about the importance of
encapsulation have since become commonplace. For her many contributions, Liskov received the ACM
Turing Award in 2009. On that occasion, MIT Provost L. Rafael Rife observed “every time you exchange
e-mail with a friend, check your bank statement online or run a Google search, you are riding the
momentum of her research.”
350 Designing Data Structures
Chapters 8 and 9 introduced you to the concepts of arrays and objects, both of
which make it possible to represent collections of data values. Those chapters,
however, concentrate on the low-level details of how arrays and objects are
implemented in JavaScript. This chapter focuses instead on how you can use arrays
and objects to implement data structures that are useful in applications, which
requires thinking about data structures in a more holistic way.
• Simplicity. Hiding the internal representation from the client means that there
are fewer details for the client to understand.
• Flexibility. Because a class is defined in terms of its public behavior, the
programmer who implements a class is free to change the internal representation.
As with any abstraction, it is appropriate to change the implementation as long
as the interface remains the same.
• Security. The interface boundary acts as a wall that protects the implementation
and the client from each other. If a client program has access to the
representation, it can change the values in the underlying data structure in
unexpected ways. Keeping the representation private prevents the client from
making such changes.
The two examples of ADTs from Chapter 9—the Point class from section 9.3
and the Rational class from section 9.4—are unusually simple in that the internal
state consists only of the parameters to the factory method. Moreover, once you
have created a Point or a Rational object, the internal state never changes. Such
classes are said to be immutable. Immutable classes have many advantages,
particularly in applications that use more than one processor.
In practice, many classes need to maintain internal state information that changes
over time. The variables that keep track of that state information must be declared
as local variables in the factory method so that they become part of its closure.
Several of the ADT examples introduced in this chapter illustrate this strategy.
10.2 Implementing a token scanner 351
There are several strategies for designing a TokenScanner class that offers the
necessary functionality. You could, for example, have the token scanner return an
array containing the entire list of tokens. That strategy, however, isn’t appropriate
for applications that work with very long strings, because the scanner has to create a
single array containing the entire list of tokens. A more space-efficient approach is
to have the scanner deliver its tokens one at a time. When you use this design, the
process of reading tokens from a scanner has the following pseudocode form:
Create the token scanner and initialize it to read from some string.
while (more tokens are available) {
Read the next token.
}
This pseudocode structure immediately suggests the sort of methods that the
TokenScanner class will need to provide for its clients. From this example, you
would expect TokenScanner to export the following methods:
• A TokenScanner factory method that allows clients to specify the input string
• A hasMoreTokens method that tests whether there are tokens left to process
• A nextToken method that scans and returns the next token
352 Designing Data Structures
These methods define the operational structure of a token scanner and do not
depend on the characteristics of the client application. Different applications,
however, define tokens in different ways, which means that the TokenScanner
class must give the client some control over what types of tokens are recognized.
calling nextToken repeatedly must return the following sequence of eight tokens:
Other applications, by contrast, are likely to define tokens in different ways. The
JavaScript interpreter inside your web browser, for example, uses a token scanner to
divide a program into tokens that make sense in the programming context, including
identifiers, constants, operators, and other symbols that define the syntactic
structure of the language. For example, if you give the token scanner the line
These two applications differ somewhat in the definition of a token. In the Pig
Latin translator, anything that’s not a sequence of letters and digits is returned as a
single-character token, including the spaces. By contrast, token scanners for
programming languages often ignore whitespace characters and treat floating-point
numbers as a single token.
you force clients to specify the rules for token formation, they need to learn how to
write those rules, which is similar in many respects to learning a new language.
Worse still, the rules for token formation are often difficult for clients to get right,
particularly if they need to recognize a complex pattern, such as the one that
compilers use to recognize numbers.
As always, one of the first decisions you have to make in writing the code is
what information needs to be maintained in the private state of the class. In this
implementation, the private state consists of the following:
• The parameter str, which contains the input string for the token scanner
• The variable cp, which keeps track of the current position in the string
• The variable ignoreWhitespaceFlag, which indicates if this option is enabled
The code in the factory method sets these variables to their initial values and then
returns an object containing the public methods exported by the class, as follows:
[Link]
10.2 Implementing a token scanner 355
It is worth going through the code in Figure 10-2 to make sure that you
understand it. In particular, it is worth spending a minute or two looking at the
implementation of nextToken. Two statements in this method use the expression
[Link](cp++) to retrieve the next character. This is first example you’ve
seen of an expression that uses the result of the increment operator in the context of
an enclosing expression. As described in Chapter 2, the expression cp++ adds one
356 Designing Data Structures
to the value of cp but then returns its previous value to the enclosing expression.
The character selected from str is therefore the one marked by the index cp before
that variable is incremented.
The TokenScanner class makes it easier to write many applications. You can,
for example, simplify [Link] by rewriting lineToPigLatin like this:
function toPigLatin(str) {
let result = "";
let scanner = TokenScanner(str);
while ([Link]()) {
let token = [Link]();
if (isLetter([Link](0))) {
token = wordToPigLatin(token);
}
result += token;
}
return result;
}
Although the new implementation of toPigLatin is not much shorter than the
original, the code is conceptually simpler. The original code had to operate at the
level of individual characters; the new version gets to work with complete words,
because the TokenScanner class takes care of the low-level details.
• The setInput method makes it possible to set a new input string without
changing the scanner parameters.
• The saveToken method makes it possible to put a token back into the token
stream where it can then be read again by a subsequent call to nextToken.
• The token scanner supports options for reading numbers, quoted strings, and
multicharacter operators and for ignoring JavaScript-style comments.
• The getTokenType method makes it possible to identify different types of
tokens, such as numbers, strings, operators, and identifiers.
10.2 Implementing a token scanner 357
[Link]
358 Designing Data Structures
The next several sections illustrate this idea by looking at several different
strategies for implementing a library package that maintains a list of the legal words
in English. Although such a package has conceptual similarities to a dictionary,
computer scientists commonly refer to a word list that lacks associated definitions
as a lexicon. A lexicon of English words has many useful applications, ranging
from checking the spelling in a document to playing word games. At a minimum,
an abstract data type implementing an English lexicon must support the following
operations:
const ENGLISH_WORDS = [
"a", "aa", "aah", "aahed", "aahing", "aahs",
. . . approximately 25,000 more lines . . .
"zymurgies", "zymurgy", "zyzzyva", "zyzzyvas"
];
10.3 Efficiency and representation 359
A file containing this array loads surprisingly quickly, even over a web connection.
Moreover, it is easy—although not necessarily efficient—to implement each of the
required operations. Clients can iterate through every English word as follows:
Similarly, you can use the built-in indexOf method to check whether the variable
str contains a valid English word like this:
Implementing the lexicon in this form, however, has two serious flaws. First,
using indexOf to check whether a word is in the lexicon is highly inefficient, as
you will see in the next few pages. Perhaps more importantly, giving clients direct
access to the ENGLISH_WORDS array and telling them what array operations to use
makes it impossible to eliminate the inefficiency. Exposing the underlying
representation limits the scope of possible changes because clients come to depend
on a particular design.
[Link](str)
Designing the method that supports processing each word in alphabetical order
requires a little more thought. The code pattern for cycling through each word
shown at the top of this page makes an unwarranted assumption about the
360 Designing Data Structures
operations that the EnglishLexicon class must support. The list of required
operations includes “processing every word in the lexicon in alphabetical order.”
There is, however, no requirement that the client be able to select the word at a
particular index. Some implementation strategies make it possible to process every
word in order but offer no efficient way to select, for example, the word at index k.
A useful strategy for allowing clients to process the words in order is to export a
method that calls a client-supplied callback function on each of the words. In
computer science, a method that applies an operation to every element of a data
structure is called a mapping function. The EnglishLexicon class implements
that strategy by exporting a method called map, which takes a function and then
calls that function on every word in alphabetical order. You can, for example,
display every word in the lexicon by calling
[Link](displayWord);
function displayWord(word) {
[Link](word);
}
Although the implementation in Figure 10-4 works, the strategy for looking up a
word in the lexicon is so inefficient that it would be impossible to use in most
applications. This claim, however, raises important questions. What does it mean
for an implementation to be inefficient? How can one measure the efficiency of an
implementation and its underlying algorithms?
This technique cannot be used to measure the elapsed time of an operation that
runs in less than a millisecond because the lack of precision in the [Link]
function makes it impossible to measure such small time intervals accurately. For
example, a single call to contains would almost certainly finish in less than a
millisecond, which means that elapsed would very likely have the value 0. To get
an accurate measurement, it is necessary to see how long it takes to complete many
calls to contains. For example, the following function goes through every word
in the lexicon and determines how long it takes to check them all:
function TestLexiconTiming() {
let english = EnglishLexicon();
let start = [Link]();
[Link](checkWord);
let elapsed = [Link]() - start;
[Link]("Time: " + elapsed + " milliseconds");
function checkWord(word) {
if () {
[Link]("contains failed for " + word);
}
}
}
It is important to look carefully at this number. Checking every word in the lexicon
takes almost 36 seconds—more than half a minute! Computers are supposed to be
fast. Why does looking up a few hundred thousand words take so long?
[Link]
364 Designing Data Structures
corresponding values are set to true, although any defined value would work just
as well. All the contains method has to do is convert the argument to lower case,
look up that key in the map, and check that the value is not undefined.
The map-based version of the EnglishLexicon class runs more than a hundred
times faster than the array-based version from Figure 10-4. At the same time, the
comparison is not entirely fair. Although contains runs dramatically faster when
the underlying implementation uses a map, the operation of cycling through the
words in alphabetical order takes substantially more time in the map-based
implementation than it does in its array-based counterpart. In version 5 of ECMA
JavaScript, which this text uses as its standard, the client can make no assumption
about the order in which keys are processed using the for-in statement. Although
more recent versions of JavaScript offer some control over the order, it is unwise to
rely on that behavior because programs that do so will fail to work with older
browsers. What you need to do instead is store the unordered keys in an array and
then use the sort method to arrange the keys in alphabetical order.
The implementation of the map function checks to see whether the local variable
sortedWordList has already been defined. If not, the code creates a sorted list of
words by running through all the keys in ENGLISH_WORD_MAP, adding each key to
the sortedWordList array, and then sorting that array. Subsequent calls to map
do not pay this cost. If you run TestLexiconTiming a second time with the same
lexicon, the time required to look up every word drops substantially, as illustrated in
the following console log:
The process of looking up every word in the lexicon is now more than 1000 times
faster with the map-based implementation than it is with the original array-based
model. Preparing the sorted list on the first call, however, still takes on the order of
a quarter of a second, even though that cost is incurred only once. While a quarter
of a second is hardly in the same league as the 36 seconds required to look up the
words in the array-based lexicon, it would be nice to avoid this cost, if possible.
10.3 Efficiency and representation 365
After converting the parameter word to lower case to ensure that it matches the
words in the lexicon, the binary-search implementation of contains begins by
setting the variables min and max to the first and last index positions in the array.
The first index, of course, is 0, and the last index is one less than the length of the
ENGLISH_WORDS array. If word is in the lexicon, it must lie somewhere in this
range of indices. The rest of the function consists of a loop that successively
narrows this range by comparing word against the entry in the middle of the index
range and using the result of that comparison to decide how to adjust the index
bounds. The loop continues until the word is found or until there are no elements
left in the range, which means that the word is not in the lexicon.
if ([Link]("lexicon")) . . .
The ENGLISH_WORDS array contains 127145 words, which means that the initial
values of min and max are 0 and 127144. On the first cycle of the loop, the code
computes the midpoint of the remaining range by averaging min and max. The code
then stores this position in the variable mid after calling [Link] to ensure that
the value is an integer. The word at index 63572 is the unfamiliar but nonetheless
legitimate word lightered. Since lexicon comes before lightered in lexicographic
order, contains can narrow the search to the range between min and mid - 1,
which therefore narrows the search range to the indices between 0 and 63571. The
process then continues until it either finds the specified word or there are no
elements left in the index range.
366 Designing Data Structures
[Link]
10.3 Efficiency and representation 367
As you can see from the trace output, contains is able to find the word lexicon
by making just 12 comparisons, even though the lexicon contains 127145 words.
Reducing the number of required comparisons speeds up the implementation of the
EnglishLexicon class considerably, as shown in the following console log for the
TestLexiconTiming program:
time is proportional to the number of words in the lexicon. Because of the direct
proportionality of lexicon size to running time, computer scientists say that this
implementation runs in linear time. In the implementation that uses binary search,
the running time is proportional to the number of times you can divide the lexicon
in half, which is in turn proportional to the logarithm of the lexicon size. This
algorithm is therefore said to run in logarithmic time, which grows much more
slowly than linear time. The internal implementation of maps in JavaScript uses a
technique called hashing, which is essentially an algorithm that tells the computer
where to look for each entry. As you will discover if you go on to a more advanced
course on data structures and algorithms, hashing can be made to run in constant
time, which means that its performance is independent of the lexicon size.
Divide-and-conquer algorithms
The binarySearch function in Figure 10-6 solves the problem of searching for a
key in a sorted array by dividing the problem in half on each cycle. Strategies that
depend on dividing a problem into smaller instances of that problem are often called
divide-and-conquer algorithms. Because divide-and-conquer algorithms involve
solving smaller instances in the same form as the original problem, such algorithms
are typically implemented recursively. You can, for example, easily implement the
binary search algorithm as a recursive function and will have a chance to do so in
exercise 12.
1 if n is 0
xn =
x × x n-1 otherwise
function raiseToPower(x, n) {
if (n === 0) {
return 1;
} else {
return x * raiseToPower(x, n - 1);
}
}
You can, however, adopt a recursive strategy similar to the one used in binary
search by taking advantage of the fact that
2
x n = (x n / 2 )
if n is even and
2
x n = (x (n – 1) / 2) × x
function raiseToPower(x, n) {
if (n === 0) {
return 1;
} else if (n % 2 === 0) {
return square(raiseToPower(x, n / 2));
} else {
return square(raiseToPower(x, (n - 1) / 2)) * x;
}
}
function square(x) {
return x * x;
}
Given the data in Figure 10-7, the important question to ask is how to represent
the electoral information in a way that preserves the relationships among the
individual data values. In doing so, it is useful to think carefully about those
relationships and to avoid jumping to conclusions based on the way in which the
information is presented to human readers. For example, the two-dimensional
structure of a printed table does not necessarily imply that the best representation is
a two-dimensional array, but may simply indicate that this representation is easiest
to display on the printed page.
As you design a data structure for the state-by-state electoral tallies—or any data
structure, for that matter—it is important to keep in mind that arrays and objects are
tools. Designing effective data structures requires you to think in a holistic way that
focuses on the abstract structures represented by those arrays and objects. Thinking
holistically makes it easier to recognize the relationships that define the overall
structure.
You have already seen examples of the following abstract data structures:
• Lists. A list is an abstract data structure in which the individual elements form a
logical sequence in which you can identify each element by its position.
• Records. A record is an abstract data structure in which the elements are part of
a logical whole but not in an ordered relationship. Typically, elements of a
record are identified by name.
• Maps. A map is an abstract data structure in which a set of keys is associated
with a corresponding set of values.
[Link]
372 Designing Data Structures
There are at least two reasons why a two-dimensional array is probably not the
best option for storing the voting data. First, elements of an array are usually of the
same type, even though JavaScript does not enforce that restriction. In the table of
election results, the rows have the same structure, but the columns do not. The first
column in each row is the number of electoral votes assigned to that state, while the
other columns list vote totals by party. This distinction suggests that each row is
best represented as an object in which one property is the number of electoral votes
and a second property is a map that links party names and vote totals.
If you adopt this strategy, you can represent the data from the 2016 presidential
election as shown in Figure 10-8. The entire structure is an array with one element
10.4 Representing real-world data 373
for each state and one for the District of Columbia. Each element in the array is an
object containing the election data for that state: the number of electoral votes and
the map from party names to vote totals. The JSON structure is flexible enough to
accommodate the missing data values represented by the blank cells in Figure 10-7,
but the code used to process the table must allow for the possibility that a field is
undefined. The [Link] file can load the entire data structure by including
the JSON file in a <script> tag, just as if it were a library.
Once you have defined the data structure, you can then write applications that
use that structure to generate whatever summary reports you need. For example, the
[Link] program in Figure 10-9 calculates the overall winner in both
electoral and popular votes. The data structure for the election is supplied as an
argument to the countVotes call in the [Link] file. Running the program in
Figure 10-9 using the [Link] data file produces the
following output on the console:
<body onload="CountVotes(PRESIDENTIAL_ELECTION_2012)">
[Link]
10.4 Representing real-world data 375
[Link]
376 Designing Data Structures
Summary
This chapter continues the discussion of JavaScript objects, focusing on abstract
data types that are useful in a variety of applications. Important points in this
chapter include the following:
• Classes that implement a set of operations without revealing the internal data
structures are called abstract data types. Abstract data types offer several
advantages over structures whose details are visible, including simplicity,
flexibility, and security.
• Separating behavior and representation in an abstract data type allows the
implementer to change that representation without adversely affecting clients.
• A sequence of characters that has integrity as a unit is called a token. This
chapter presents a simple implementation of a TokenScanner class that divides
a string into tokens consisting of consecutive letters. The libraries included with
this text include a JSTokenScanner class that offers clients considerably more
flexibility. The methods exported by the expanded JSTokenScanner class
appear in Figure 10-3.
• A lexicon is a list of words without associated definitions.
• The underlying representation of an abstract type and the algorithms used to
implement its methods can have a profound effect on efficiency. For example,
the original array-based implementation of the EnglishLexicon class runs
1000 times more slowly than the map-based version.
• The linear search algorithm operates by looking at each element of an array in
order until it finds the desired element.
• The binary search algorithm is more efficient than linear search but requires that
the elements of the array be in sorted order. The efficiency advantage of binary
search lies in the fact that you can discount half the remaining elements on each
cycle of the search loop.
• The class method [Link] returns the number of milliseconds that have
elapsed since January 1, 1970. By checking the time both before and after an
operation, you can determine how long that operation takes, assuming that the
operation takes long enough to fit within the precision of a millisecond clock.
• In designing the data structure for an application, it is usually better to think in
terms of abstract conceptual models—lists, records, and maps—rather than the
concrete structures—arrays and objects—used to represent those models.
• It is important to think carefully about structural relationships and avoid jumping
to premature conclusions arising from how information is presented.
Review questions 377
Review questions
1. What is an abstract data type?
6. What are the two public methods exported by the EnglishLexicon class?
8. What value is returned by the [Link] method? How can you use that value
to measure the elapsed time required to execute some segment of code?
10. Figure 10-10 shows an array called STATE_CODES containing the two-letter
abbreviations for the 50 states, arranged in alphabetical order. Show the steps
involved if you use the binary search algorithm to search for the code "OK".
How many comparisons are required to find this value?
11. How would you describe the binary search algorithm to someone who has little
familiarity with computers and programming?
12. Using the diagram at the bottom of page 369 as a model, draw a diagram
showing the recursive calls made in evaluating raiseToPower(3, 9).
378 Designing Data Structures
Exercises
1. Write a program that uses the TokenScanner class to display the longest word
that appears in a file chosen by the user. A word should be defined as any
consecutive string of letters and digits, just as it is in the TokenScanner class.
4. When playing Scrabble, one of the most useful things to memorize is the list of
all legal two-letter words. It is easy to use the English lexicon to create a list of
two-letter words, but there are at least two strategies for doing so. The most
natural one is to iterate through the words in the lexicon and print those whose
length is two. A somewhat less obvious strategy is to generate all 26 × 26
combinations of the letters and see which ones are words. Implement each of
these strategies and then use the elapsedTime function from the preceding
exercise to see which strategy is more efficient.
S-hook at the end. Some words, of course, allow an S tile to be added at the
beginning, but it turns out that there are 680 words—including, for example,
the words cold and hot—that allow an S-hook on both ends. Write a program
that uses the EnglishLexicon class to display a list of all such words.
6. If you manage to play all seven of your tiles in a single turn, you get a 50-point
bonus for what Scrabble players call a bingo. To help you find bingos, it would
be useful to have a function listAnagrams that takes a string of letters you
might have in your Scrabble rack and returns all the legal words that can be
formed by rearranging those letters in any order. Although the technique for
generating all rearrangements of a string requires concepts beyond the scope of
this book, you can achieve the same result by going through the English lexicon
and printing every word that contains the same set of letters. The following
console log shows a few examples:
If you have trouble figuring out how to implement listAnagrams, you might
look at exercise 6 from Chapter 8 for an idea.
8. When you convert English to Pig Latin, most words turn into something that
sounds vaguely Latinate but different from conventional English. There are,
however, a few words whose Pig Latin equivalents just happen to be English
words. For example, the Pig Latin translation of trash is ashtray, and the
translation for express is expressway. Use the [Link] program from
Chapter 7 together with the EnglishLexicon class to write a program that
displays a list of all such words.
380 Designing Data Structures
9. Write a program that displays a table showing the number of words that appear
in the EnglishLexicon class, sorted by the length of the word. The output
from your program should look like this:
10. Write a program to produce a trace of the binary search algorithm of the sort
shown on page 367. To give clients the option of generating such a trace, add
to the EnglishLexicon class a new exported method called setTraceMode,
which takes a Boolean value indicating whether tracing should occur. If the
client calls setTraceMode(true) on the lexicon object, calling contains
should generate a complete trace on the console.
11. The binary search algorithm is useful enough that it makes sense to export it to
clients. Define a class method
[Link](key, array, min, max)
that searches for an occurrence of key in array, which must be in sorted order.
The optional min and max parameters specify the range of indices included in
the search. If these parameters are missing, binarySearch should look at
every element in the entire array. The binarySearch method should return
the index at which key appears in array. If key appears more than once in the
array, the return value can be any of its index positions. Make sure that you
update the code for contains to call the binarySearch method.
12. Rewrite the binarySearch method from the preceding exercise so that it
operates recursively rather than iteratively.
Exercises 381
13. The fact that it is possible to compute x n in logarithmic time makes it possible
to compute the Fibonacci function fib(n) in logarithmic time, as promised in
Chapter 5. To do so, you need to rely on the fact that the Fibonacci function is
related to a value called the golden ratio, which makes its first appearance in
classical Greek mathematics. The golden ratio, usually designated by the
Greek letter phi (ϕ ), is defined to be the value that satisfies the equation
ϕ2 – ϕ – 1 = 0
Because this is a quadratic equation, it actually has two roots. If you apply the
quadratic formula, you will discover that these roots are
ϕ =
n
Moreover, because is always very small, the formula can be simplified to
14. Use the data from the [Link] to find all states in
which the winning candidate got less than 50 percent of the vote. In 2016,
seven of these states were won by Democrats and seven by Republicans.
15. Exercise 2 asks you to write a program that counts the number of words in a
sequence of lines. Extend that program by making the following changes:
16. Suppose that a bank has hired you as a programmer and given you the task of
automating the process of converting between different foreign currencies at
the prevailing rate of exchange. Every day, the bank receives a file called
[Link] containing the current exchange rates stored in JSON
format as shown in Figure 10-11. Each value in the currencies map is an
object that specifies the name of the currency and its current exchange rate
relative to the dollar. For example, the entry
Exercises 383
indicates that the three-letter code "GBP" has the name "Pound sterling"
and is currently trading at 1.23586 dollars to the pound.
where amount is the monetary value you want to convert, and XXX and YYY are
the three-letter codes for the old and new currency. Alternatively, the input line
may consist of a three-letter currency code, in which case the program should
report the full name of that currency. A sample run that illustrates both input
forms might look like this:
Design a data structure that encodes the information shown in Figure 10-12
and then create a JavaScript file that stores that information in JSON form.
Test your data structure by writing a console-based program that requests a
potion name from the user and then displays a list of its ingredients.
18. For certain applications, it is useful to be able to generate a series of names that
form a sequential pattern. For example, if you were writing a program to
number figures in a paper, having some mechanism to return the sequence of
strings "Figure 1", "Figure 2", "Figure 3", and so on, would be very
handy. However, you might also need to label points in a geometric diagram,
in which case you would want a similar but independent set of labels for points
such as "P0", "P1", "P2", and so forth.
If you think about this problem more generally, the ADT you need is a label
generator that allows the client to define arbitrary sequences of labels, each of
which consists of a prefix string ("Figure " or "P" for the examples in the
preceding paragraph) coupled with an integer used as a sequence number.
Because the client may want different sequences to be active simultaneously, it
makes sense to define the label generator as a LabelGenerator class. To
initialize a new generator, the client provides the prefix string and the initial
index as arguments to the LabelGenerator factory method. Once the
generator has been created, the client can return new labels in the sequence by
calling nextLabel on the LabelGenerator object.
19. When text is displayed on the printed page or a computer screen, it usually
must be adjusted to fit within fixed margins. Output that is too wide must be
broken up and displayed on several lines. If the text is composed of words,
divisions between the lines are made at the spaces that mark the word
boundaries. As long as an entire word fits on the current line without extending
past the right margin, it is placed on that line. If a word would extend past the
right margin, the current line is displayed, and the word that caused the
overflow is placed at the beginning of the next line. Subsequent words are then
added to the newly created line until that line fills up as well. This process is
called filling and can be repeated as long as there is any text to display or the
client specifies a line break.
20. For some applications, JavaScript’s limitation on how many significant digits
appear in an integer can be a limitation. For those applications, it is useful to
have access to a BigInteger class that is capable of storing integers with an
arbitrarily large number of digits. Several such packages exist on the web, all
of which use techniques whose complexity puts them beyond the scope of this
text. Nonetheless, you can get some idea of how such a package might work by
writing a simplified BigInteger class that supports only addition and
multiplication.
The challenge in this problem is to write the methods that perform the required
arithmetic operations. In doing so, you must go back to the rules you learned in
elementary school for doing arithmetic by hand.
Addition proceeds digit by digit from the right end of the number, just as
when you first learned to add multidigit numbers. To add two BigInteger
values, you need to select characters from the digits string and convert them
to numeric form. Once you have obtained the corresponding digits from each
number, you can add them, record the single-digit sum, and keep track of the
carry. You can then go on to the next digit, moving from right to left.
386 Designing Data Structures
Multiplication is a little trickier but also follows the rules you learned in
school. For each digit in the multiplier, you can form the product of that digit
and the multiplicand by repeated addition, since the loop will run at most nine
times. You then move on to the next digit and perform a similar calculation,
multiplying the result by 10 simply by concatenating a "0" to the end of the
string. You can then use the add method to sum these partial products.
Norwegian computer scientists Kristen Nygaard and Ole-Johan Dahl developed the central ideas of
object-oriented programming more than 50 years ago as part of their work on the programming language
SIMULA. Early versions of SIMULA appeared in the early 1960s, but the stable version of the language
that brought these concepts to the attention of the world appeared in 1967. The initial work on SIMULA
was carried out at the Norwegian Computing Center, a state-funded research laboratory in Norway focusing
on developing better software-engineering techniques. Both later joined the faculty at the University of
Oslo. Although their work took several decades to become established in the industry, interest in
object-oriented techniques has grown considerably in the last three decades, particularly after the release of
modern object-oriented languages like C++ and Java. For their contributions, Nygaard and Dahl received
both the 2001 Turing Award from the Association for Computing Machinery and the 2001 John von
Neumann Medal from the Institute of Electrical and Electronic Engineers.
388 Inheritance
This biological classification system is illustrated in Figure 11-1 at the top of the
Carl Linnaeus
next page, which shows the classification of the common black garden ant, whose
scientific name, Lasius niger, corresponds to its genus and species. This species of
ant, however, is also part of the family Formicidae, which is the classification that
identifies it as an ant. If you move upward in the hierarchy from there, you discover
that Lasius niger is also of the order Hymenoptera (which includes bees and wasps),
the class Insecta (which consists of the insects), and the phylum Arthropoda (which
also includes, for example, shellfish and spiders).
One of the properties that makes this system of biological classification useful is
that all living things belong to a category at every level in the hierarchy. Each
individual life form therefore belongs to several categories simultaneously and
inherits the properties that are characteristic of each one. The species Lasius niger,
for example, is an ant, an insect, an arthropod, and an animal—all at the same time.
Moreover, each individual ant shares the properties that it inherits from each of
those categories. One of the defining characteristics of the class Insecta is that
insects have six legs. All ants must therefore have six legs because ants are
members of that class.
The biological metaphor also helps to illustrate the distinction between classes
and objects. Although every common black garden ant has the same biological
classification, there are many individuals of the common-black-garden-ant variety.
In the language of object-oriented programming, Lasius niger is a class and each
individual ant is an object.
11.1 Class hierarchies 389
[Link]
11.2 Defining an employee hierarchy 391
contains the name of the class. The methods implemented by that class appear in
the lower portion of the box. The hierarchical relationships among the classes are
indicated using arrows with open arrowheads that point from one class to another
class at a higher level of the hierarchy. The class that appears lower in the hierarchy
is a subclass of the class to which it points, which is called its superclass.
In the UML diagram in Figure 11-2, the names of the classes GObject and
GFillableObject appear in italics. This notation is used to define an abstract
class, which is a class that is never used to create an object but instead acts as a
common superclass for concrete classes that appear beneath it in the hierarchy.
Because GObject is abstract, you never create a GObject but instead create one of
its concrete subclasses.
The root of this hierarchy is the Employee class, which defines the methods that
are common to all employees. The Employee class therefore exports methods like
getName and getJobTitle, which the other classes simply inherit. Conversely, it
is almost certainly necessary to write separate getPay methods for each of the
subclasses, because the computation is different in each case. The pay of an hourly
employee depends on the hourly rate and the number of hours worked. For a
commissioned employee, the pay is typically the sum of some base salary plus a
commission on the sales volume for which that employee is responsible. At the
same time, it is useful to note the fact that every employee has a getPay method,
even though its implementation differs for each of the subclasses. The UML
diagram therefore includes a getPay method at the level of the Employee class,
even though that method is defined at a lower level. The names of the Employee
class and the getPay method within it are set in italic type to indicate that these are
abstract entities that act as placeholders for the concrete definitions.
Figures 11-4 and 11-5 on the next two pages define a simple version of the
Employee class and its HourlyEmployee subclass. The factory method for
Employee takes two parameters, name and title, and creates an object that
contains these fields along with the methods that are common across all subclasses.
The factory method for HourlyEmployee takes these same two parameters and
uses them as arguments to Employee to set up the common parts of the structure.
The HourlyEmployee factory method also defines the local variables required to
compute an hourly employee’s pay and then adds a definition for getPay:
function getPay() {
return hoursWorked * hourlyRate;
}
11.2 Defining an employee hierarchy 393
[Link]
394 Inheritance
[Link]
11.3 Extending graphical classes 395
Because the getPay method is part of the specification for the Employee class
itself, the definition of Employee in Figure 11-4 also defines a getPay method,
which looks like this:
function getPay() {
throw Error("getPay not defined at this level");
}
The body of this function introduces a new JavaScript statement called throw,
which is used to signal an unexpected condition called an exception. Although a
complete treatment of exceptions is beyond the scope of this text, it is important for
you as a class designer to report errors in a consistent way. The built-in Error
class in JavaScript creates an exception value that JavaScript debuggers recognize
as an error condition. The factory method for Error takes a message string that is
displayed either in the debugger, if one is running, or on the system console.
Fortunately, this error condition will never occur as long as the client uses the
Employee class hierarchy correctly. Because Employee is an abstract class, clients
will not call its factory method but will instead create one of its concrete subclasses.
Each of those subclasses must replace this version of the getPay method with one
that calculates the employee’s pay appropriately. In object-oriented programming,
the process of providing a new definition for a method defined at a higher level is
called overriding.
You will have a chance to implement the other two subclasses of Employee in
exercise 1.
Figure 11-6 contains the same code as Figure 6-12 but defines the operation as
creating an instance of a new GObject subclass instead of a new graphical object.
function DrawOutlinedGoldStar() {
let gw = GWindow(GWINDOW_WIDTH, GWINDOW_HEIGHT);
let cx = [Link]() / 2;
let cy = [Link]() / 2;
let star = GStar(STAR_SIZE);
[Link](true);
[Link]("Gold");
[Link](star, cx, cy);
}
11.3 Extending graphical classes 397
Running this program produces the following output on the graphics window:
function HelloBox() {
let gw = GWindow(GWINDOW_WIDTH, GWINDOW_HEIGHT);
let cx = [Link]() / 2;
let cy = [Link]() / 2;
let box = GTextBox(BOX_WIDTH, BOX_HEIGHT, "Hello");
[Link](box, cx - [Link]() / 2,
cy - [Link]() / 2);
}
displays an 80 × 40 box containing the string "Hello" at the center of the window,
as follows:
The code for the GTextBox class itself appears in Figure 11-7.
398 Inheritance
[Link]
11.4 Decomposition and inheritance 399
In addition to the factory method that creates the GCompound along with the
GRect and GLabel objects it contains, the GTextBox exports three additional
methods—setLineColor, setFillColor, and setTextColor—that control the
colors of different aspects of the display. Each of these methods redirects the
client’s request to the graphical object that is responsible for displaying that feature.
The setLineColor and setFillColor methods pass those messages along to the
GRect object stored in the local variable frame, and the setTextColor method
sends the appropriate message to the GLabel stored in the variable label. Passing
an operation along to a private object stored inside a class is called forwarding.
single graphical object. The individual components inside a train can then be
objects of a class called TrainCar, which is also a subclass of GCompound. The
three different types of train cars then become subclasses of TrainCar.
If you look at the diagrams for these three cars, you will see that they share a
number of common features. The wheels are the same, as are the connectors that
link the cars together. In fact, the body of the car itself is the same except for the
color. Each type of car shares a common framework that looks like this:
Thus, if you color the interior of the car with the appropriate color, you can use it as
the foundation for any of the three car types. For the engine, you need to add a
smokestack, a cab, and a cowcatcher. For the boxcar, you need to add doors. For
the caboose, you need a cupola.
To make it possible to draw cars in any color, the simplest approach is to have
the TrainCar factory method take a color parameter that specifies the fill color of
the gray box shown in the most recent diagram. Individual subclasses can then
choose whether to make a specific decision about color, which might be that
engines are always black and cabooses are always red, or to pass that choice on to
the subclass. The Boxcar subclass, for example, can also take a color parameter
and then pass it along to the TrainCar factory method.
The fact that each car has two wheels suggests that defining a TrainWheel class
will simplify the TrainCar class by allowing the code for creating a wheel to be
shared. Putting all these ideas together gives rise to the class hierarchy shown in
Figure 11-8. Every class in the UML diagram is a GObject and can therefore be
displayed on the graphics window.
Given this design, you can assemble the three-car train shown at the beginning
of this section using the following code:
11.4 Decomposition and inheritance 401
The first line creates an empty train, and the remaining lines add an engine, a green
boxcar, and a caboose to the end of the train. To center the train at the base of the
window, you can take advantage of the fact that the Train class inherits the
getWidth method from GObject. You can therefore simply ask the train how
long it is and then subtract half its width from the coordinates of the center of the
window.
The Train object created in this snippet of code is a GCompound that contains
every graphical object that appears in the window. If you want the train to move, all
you have to do is animate the location of the GCompound, since all of its pieces will
move together with the top-level compound.
The code to create and animate this train appears in Figure 11-9 on the next three
pages. The implementation includes the class definitions for Train, TrainCar,
TrainWheel, and Boxcar. You will have the chance to implement the Engine
and Caboose subclasses in exercise 8.
402 Inheritance
[Link]
11.4 Decomposition and inheritance 403
[Link]
404 Inheritance
If you look at textbooks for object-oriented languages like Java, you will find no
shortage of examples in which inheritance is used without any obvious advantage.
Some authors, for example, define a Pizza class, which has PepperoniPizza and
MushroomPizza as subclasses. While those classes are arguably specializations of
the Pizza class, examples of this type do little to illustrate the value of inheritance.
If nothing else, the proposed strategy leaves completely unanswered the questions
of how to classify a pizza that has both pepperoni and mushrooms or why one
would even try to define a class hierarchy with so many independent options. A far
better strategy is to store a list of toppings as part of a simple Pizza class without
any hierarchical structure.
Summary
This chapter includes a brief introduction to the idea of inheritance in JavaScript
along with some appropriate examples. Important points in this chapter include the
following:
where message is a string describing the error. This statement stops the program
and returns control to the JavaScript debugger. If no debugger is running, the
message is displayed on the system console.
• Inheritance allows you to apply the principles of top-down design and stepwise
refinement in the data domain.
• The classes in the graphics library, especially GPolygon and GCompound, offer
useful starting points for inheritance relationships as illustrated by the
[Link], [Link], and [Link] programs in Figures 11-6, 11-7,
and 11-9.
• Inheritance can easily be overused. In many situations, it is better to embed an
existing object inside a new class and then use forwarding to implement the
desired operations.
406 Inheritance
Review questions
1. Choose a favorite animal and add it to the biological hierarchy in Figure 11-1.
To find the appropriate place in the hierarchy, you will need to use the web to
look up its phylum, class, order, family, genus, and species.
2. Every organism that descends from the class Insecta has six legs because that is
one of the properties that all insects share. Does this fact mean that all
organisms with six legs are insects?
4. True or false: A subclass inherits the methods in its superclass along with all of
its superclasses in the inheritance hierarchy.
7. Given a class in a UML diagram, how can you determine what methods it
supports?
9. In your own words, explain the purpose of the GFillableObject class in the
graphics hierarchy shown in Figure 11-2.
10. Does the GOval class implement the rotate method? Why or why not?
12. What term is used to describe the process of providing a new definition for a
method to replace one defined in a superclass?
14. The Train class in Figure 11-9 exports a method called append to add a car to
the end of the train. Would it have worked just as well to use add as the name
of this method?
Exercises
1. Complete the definition of the Employee hierarchy from Figures 11-4 and 11-5
by defining CommissionedEmployee and SalariedEmployee.
For example, calling Queen("W", "d1") would create a white queen on d1,
which is its initial square.
sides, and radius, which indicates the distance from the reference point at the
center to any of its vertices. The polygon should be oriented so that it is flat
along the bottom. For example, calling GRegularPolygon(5, 30) should
create a GRegularPolygon object that looks like this:
5. The factory method for the GRect class takes two possible argument forms.
Calling GRect(x, y, width, height) creates a GRect object with the specified
dimensions. You can also call GRect(width, height), which creates a GRect
of the specified size initially positioned at the origin. Add this feature to the
GTextBox class from Figure 11-7.
6. Extend the GTextBox class so that it also exports a setFont method that
resets the font used for the text string. Because changing the font typically
changes the dimensions of the label, the implementation of setFont will need
to adjust the position of the label within the box.
410 Inheritance
9. In Chapter 6, you had the chance to work with several programs that let you
create shapes by dragging the mouse on the graphics window. Using those
programs as a starting point, create a more elaborate DrawShapes program that
displays an onscreen menu of five shapes—a filled rectangle, an outlined
rectangle, a filled oval, an outlined oval, and a straight line—along the left side
of the window, as shown in the following diagram:
Exercises 411
Clicking one of the squares in the menu chooses that shape as a drawing tool.
Thus, if you click the filled oval in the middle of the menu area, your program
should draw filled ovals. Clicking and dragging outside the menu should draw
the currently selected shape.
Each of the drawing tools along the left edge of a window should be a
GCompound that combines the symbol for the tool and the enclosing square.
Each tool, moreover, should define additional methods that create the shapes.
Your program should check to see whether a mouse click is inside one of the
tools and, if so, store that tool in a variable so that the callback functions for the
mouse events can perform whatever actions are required to implement that tool.
10. Extend the DrawShapes application from the preceding exercise so that the left
sidebar also includes a palette of colors. Clicking one of the colors sets the
current color for the application, so that subsequent shapes are drawn in that
color. Since there is not enough room to display all of JavaScript’s predefined
colors, you will need to choose a subset that you find aesthetically pleasing.
The sample world on the preceding page is populated with twenty creatures,
ten of a species called Flytrap and ten of a species called Rover. In each case,
the creature is identified in the graphics window with the first letter in its name.
The orientation is indicated by the figure surrounding the identifying letter; the
creature points in the direction of the arrow. Each creature—which you can
think of as a simple robot much like Karel—runs a program that is particular to
its species. Thus, all Rovers behave in the same way, as do all Flytraps, but the
behavior of each species is different from that of the other.
Each creature instance keeps track of the index of the current instruction in this
program, which always begins at 0 when a creature is created. On a turn, the
creature executes instructions until one of the Darwin actions occurs. The
Flytrap creature, for example, begins by executing the "ifDifferent 3"
instruction at index 0 in the array. If the Flytrap is facing a creature of a
different species, it goes to the "infect" instruction at index 3 and executes
that operation. If the ifDifferent instruction does not apply, the creature
continues with the "turnRight" instruction at index 1. In either case, this
creature’s turn ends after executing the action. On its next turn, the creature
begins by executing one of the "goto 0" instructions, which sends the program
back to the top. The Flytrap creature therefore rotates clockwise until it sees a
creature of a different species, in which case it infects it to make it a Flytrap.
Your job in this exercise is to implement both the [Link] file that runs
the simulation and the Creature class, which is the abstract superclass of
Flytrap and Rover as well as any other creatures you design. The definitions
of these two subclasses appear in Figure 11-14 on the next page. Your
definition of Creature must provide the methods that these subclasses need.
The Creature class is also responsible for managing the display of the
creature on the graphics window, which is most easily accomplished by making
Creature a subclass of GCompound.
The most interesting part of this problem is designing new creatures that
perform well in the survival-of-the-fittest evolutionary challenge that the
Darwin game provides.
CHAPTER 12
JavaScript and the Web
The Web as I envisaged it, we have not seen it yet. The
future is still so much bigger than the past.
— Tim Berners-Lee, 18th World
Wide Web Conference, 2009
Tim Berners-Lee graduated from Oxford University with a degree in physics and went on to become a
research fellow at CERN, the international nuclear research lab near Geneva, Switzerland. In March 1989,
Berners-Lee wrote a proposal for a new set of communication protocols that would allow users to navigate
easily through a large collection of data repositories stored on many different computers. That vision
became the World Wide Web, now used by billions of people throughout the world. Throughout the web’s
history, Berners-Lee has campaigned to ensure that access to the web remains free and open, unrestricted
by either government or corporate control. For his pioneering contributions, Berners-Lee was knighted by
Queen Elizabeth II in 2004 and received the Turing Award, the computing field’s highest honor, in 2016.
416 JavaScript and the Web
JavaScript is the most widely used language for implementing interactive content on
the web and is likely to remain so for some time to come. After completing the first
11 chapters of this book, you have learned enough about JavaScript to write exciting
programs of your own. So far, however, the chapters have focused on JavaScript in
isolation without considering its relationship to other programming models that are
fundamental to the web. The point of Chapter 12 is to teach you how to combine
these models to create more sophisticated applications.
Although covering HTML and CSS in detail would require another book at least as
long as this one, the examples in this chapter will establish the foundation you need
to create interesting interactive applications.
Arthur listened for a short while, but being unable to understand the vast
majority of what Ford was saying he began to let his mind wander,
trailing his fingers along the edge of an incomprehensible computer
bank, he reached out and pressed an invitingly large red button on a
nearby panel. The panel lit up with the words “Please do not press this
button again.”
It is easy to simulate this vignette in a web page. The first step is to create a
button in the window, which for the moment can be labeled with its color, like this:
12.1 A simple interactive example 417
<button>RED</button>
displays a button marked with the label RED. The action performed by the button is
specified in the attributes supplied with the <button> tag, as illustrated by the
complete [Link] file shown in Figure 12-1. The type="button" attribute is
required to indicate that pressing the button will be handled by JavaScript and not
by the server supplying the web page. The onclick attribute specifies the code to
execute when the button is pressed, which in this case is the JavaScript call
418 JavaScript and the Web
Figure 12-2 on the next page defines a “starter kit” of 25 HTML tags that are
sufficient to implement the examples and exercises appearing in this book. As you
know, HTML tags are typically paired, so that the <body> tag is matched with a
</body> tag marking its end. A few tags, however, are standalone and do not
require a corresponding closing tag. For example, you can insert a line break by
including a <br /> tag in the HTML specification. The slash preceding the closing
angle bracket indicates that <br /> serves as its own closing tag.
You have already used the tags in the first section of Figure 12-2 and the
<title> and <script> tags from the second section. The sections that follow
explain how to use the other tags to create more interesting web content.
[Link]
420 JavaScript and the Web
For the most part, the text you see on a web page appears in the [Link]
file in more or less the same form. HTML files, however, are written in plain text.
You cannot, for example, set a word in italics simply by changing the font of that
word in the [Link] file the way you would with a word processor. What you
need to do instead is specify that formatting information using HTML tags. For
instance, to make text appear in italics, you need to enclose it between the paired
tags <i> and </i>. In much the same way, you enclose the text for a paragraph
between the tags <p> and </p>. Thus, if an [Link] file includes the lines
<p>
This paragraph consists of one sentence and
includes a word set in <i>italic</i> type.
</p>
the corresponding image in the browser window will look something like this:
Beyond the fact that the word italic appears, as expected, in italics, there are
other important details to notice about this output. First of all, the text is set in a
serif font (typically Times New Roman), which is the standard font used by the
browser if no additional style information appears. The word italic therefore
appears in the italic version of the surrounding serif font. In addition, the line
breaks appear in different places in the [Link] file and in the browser
window. By default, the browser displays words in a paragraph on a single line
until the next word would no longer fit, at which point the browser advances to the
next line. This behavior is called filling. The filling operation, moreover, occurs
dynamically. If you change the size of the window, the line breaks in the paragraph
will occur at different places. Finally, the extra spaces that appear at the beginning
of each line in the [Link] file have disappeared in the screen version. With
the exception of content enclosed between the <pre> and </pre> tags, HTML
treats all whitespace characters identically and collapses strings of consecutive
whitespace characters into a single space.
The <p> and <i> tags used in this example are representatives of two distinct
classes of HTML tags. The <p> tag is an example of a block element, which is a
region of the web page that stands alone as a vertical unit. The <i> tag is an
example of an inline element, a region of the web page that can exist within a line.
Filling, for example, applies to the interior of a block element but not to two block
elements that appear consecutively in the HTML file. By contrast, inline elements
appearing within a block element are filled just as if they were normal text. Thus,
12.2 An expanded look at HTML 421
the word italic, which is specified by the HTML inline element <i>italic</i>, is
positioned in the browser display in the same fashion as the rest of the words.
Several of the tags in Figure 12-2 are easy to understand by making the
appropriate mental connections to the <p> and <i> tags. The <blockquote> tag,
for example, functions similarly to the <p> tag except that it also indents the text on
both the left and right sides. The inline tags <b> and <code> are analogous to the
<i> tag but specify a boldface or monospaced font, respectively. The other tags are
somewhat more complicated and are introduced as they are needed.
The fact that HTML encloses tags in angle brackets raises a couple of interesting
questions. What happens if you want to include angle brackets in the text of a web
page, as you might if you were trying to illustrate HTML code or if you needed to
include the less-than and greater-than operators? Similarly, how can you display
special characters that don’t appear on the standard keyboard but may nonetheless
be essential for a web page that includes text in a language with a different
alphabet? HTML solves both of these problems by defining symbolic
representations for characters that would otherwise be impossible to include in a
plain-text HTML file. These symbolic character designations, which are called
character entities, begin with an ampersand and end with a semicolon. HTML
defines a large number of character entities; the ones in Figure 12-3 represent the
most common entities used in a typical web page.
[Link]
12.2 An expanded look at HTML 423
resulting screen image for a web page that displays the beginning of section 12.1,
which introduces the example from The Hitchhiker’s Guide to the Galaxy. The
<body> section includes two <p> tags for the text paragraphs, a <blockquote> tag
for the inset quotation from the novel, and the <button> tag from the original
example to simulate the operation of the button. The HTML code uses the <i> tag
to italicize the title along with the character entities ’, “, and
” to produce the apostrophe and the two double quotation marks. As you
can see from the screen image at the bottom of the figure, the line breaks in the text
are different from those in the [Link] file because filling is applied in the <p>
and <blockquote> elements.
Displaying images
The modern web would be unimaginable if it were impossible to display images
along with text. Since most web pages include images, it is important for you to
know how to include images in the web pages you design.
If you wanted to create a web page to honor the contributions of this little-known
pioneer, it would be useful to include a picture of Otlet along with other
biographical information. Fortunately, there are many pictures of Otlet on the web,
including several that are in the public domain. If you download one of these
images and store it in the file [Link], you can display that image by
including the following line in the <body> section of the [Link] file:
The src attribute tells the browser where to find the image file. The value can
be the complete URL of an image on the web, in which case the value begins with
the prefix http: (or https: if you insist on a secure connection as most modern
web pages do). More commonly, the src attribute specifies the name of a file,
which typically appears together with the other files used to display the page. Here,
for example, the image file [Link] must appear in the same directory as
the [Link] file.
424 JavaScript and the Web
The alt attribute specifies what the browser should show if it is unable to
display the image. In the early days of the web, the primary purpose of using the
alt attribute was to accommodate browsers that did not support images, but few
such tools still exist. The alt attribute today fills the important function of
providing greater service to users with impaired vision. When users enable the
appropriate accessibility options, the text-to-speech synthesis technology that reads
the content of the web page can offer a description of what each image contains.
Loading a web page containing this <img> tag produces the following browser
display:
The size of the image on the screen is the same as the size of the image contained in
the file. When you are designing a web page, you will often have occasion to
change the size and position of an image. Doing so in a modern way, however,
requires the use of CSS styling, as described in section 12.3.
Hyperlinks
One of the fundamental advantages of the web is that pages can include references
to other pages that provide additional information. These references to other pages
are called hyperlinks.
You can add hyperlinks to a web page using the <a> tag, which is called an
anchor tag, mostly for historical reasons. In early releases of HTML, the <a> tag
was used to mark specific locations in a web page and therefore served as an anchor
for a specific location. That function is now the responsibility of the id attribute,
but the <a> tag nonetheless retains its name as an abbreviation of its older function.
The destination page of an anchor tag appears in an href attribute, which uses the
URL of the destination page as its value. If, for example, you want to include a
hyperlink to the New York Times homepage in your [Link] file, you can
include an anchor tag that looks like this:
The text between the <a> and </a> tags becomes the hyperlink, which appears in a
highlighted form in the browser. Clicking the highlighted link asks the browser to
show the web page specified by the href attribute.
computing pioneers Vannevar Bush and Ted Nelson. If you load the [Link]
file from Figure 12-5 into a browser, the display will look something like this:
Clicking any of the three underlined hyperlinks on this page will redirect the
browser to the page specified in the associated href attribute.
CSS declarations
Web designers control the style of a page by writing one or more CSS declarations,
each of which consists of a property name, a colon, a value, and a semicolon, like
this:
name:value;
The values associated with the CSS properties listed in Figure 12-6 take different
forms depending on the property. The following value types are the most common:
• Keywords. Many CSS properties define a set of keyword values specific to that
property. The display property, for example, supports the keywords block,
inline, and none. The most common keywords supported by each property
are listed in Figure 12-6.
12.3 Using CSS 427
[Link]
428 JavaScript and the Web
You have already seen the font property in the discussion of the GLabel object
on page 129. The value of the font property consists of four values—the font style,
weight, size, and family—separated by spaces. Each of these values is optional, but
any options that appear must be in the specified order. For example, to change the
font so that text appears in boldface Times New Roman with a size of 20 pixels, you
would use the following CSS declaration:
As with the font specifications you worked with in Chapter 4, this declaration
includes the standard serif font as an alternative in case Times New Roman is not
available.
asks the browser to render the contents of this paragraph in a 16-pixel sans-serif
font, which is Helvetica Neue if that font is available and the default sans-serif font
otherwise. The result in the browser window looks like this:
The problem with this strategy is that the style attribute applies only to the
element in which it appears. In most cases, what you want to do is define styles that
apply throughout the document. To do so, the conventional approach is to define a
set of CSS rules, each of which has the following form:
selector {
CSS style declarations separated by semicolons
}
The selector component of this pattern can take any of the following forms:
• An HTML tag name. This selector form applies the associated rule to every tag
with the specified name. Thus, the selector p applies to all paragraphs marked
with the <p> tag, and the selector code applies to every <code> tag.
• A period followed by a class name. This selector applies to every element that
includes a class attribute that matches the specified class. For example, the
selector .example matches any HTML tag that includes a class="example"
attribute. This form of the selector makes it possible to apply a set of styles
selectively to a set of designated elements.
• A hashtag symbol followed by an id name. This selector applies only to the
element that has the specified id attribute. Thus the selector #appendix applies
only to an element that includes the attribute id="appendix".
If more than one selector applies to an element, the browser chooses the most
specific one, so that selection based on the id attribute overrides selection based on
the class attribute, which in turn overrides selection based on the tag name.
The list of rules for a document can appear in two distinct places. If you want to
use a set of rules in a single web page, you can include those rules in a <style> tag
that appears as part of the <head> section. For example, if you want to change all
paragraphs in your document so that the text appears in a 16-pixel sans-serif font
and the first line of each paragraph is indented half an inch from the left margin,
you can add the following <style> section to the [Link] file:
430 JavaScript and the Web
<style>
p {
text-font: 16px 'Helvetica Neue','Sans-Serif';
text-indent: 0.5in;
}
</style>
If you insert this <style> section into the [Link] file for the hyperlinks
example shown in Figure 12-5 on page 425, the output will look like this:
The list of CSS rules can also be stored in a file, which makes it possible to share
the same rule set among many different pages. If, for example, you create a file
called [Link] containing the rule shown at the top of this page,
you can then import these rules, which in the file-based model are usually referred
to as a style sheet, by including the following <link> tag in the <head> section:
Styles defined in a style sheet can be overridden either by defining additional local
styles for the document or by including a style attribute for specific elements.
The border, padding, and margin properties apply by default to all four sides
of the content area. For example, the CSS style declaration
draws a solid blue border with a width of one pixel just outside the content and
padding areas on all four sides. You can, however, control the properties of the
individual sides by adding -left, -right, -top, and -bottom to any of these
property names. As an example, the declaration
draws a three-pixel black border down the right side of the content area that is
separated from the text by 10 pixels of space. You could use this style of border to
create what editors call a change bar, which is a solid black line in the right margin
used to indicate parts of a document that have changed since the preceding version.
The [Link] file in Figure 12-9 at the top of the next page illustrates each
of these properties that apply to the CSS box model in the context of a page that
produces the following screen display:
The CSS rule marked by the selector #example applies to the <div> element that
includes the attribute id="example". The declarations in this rule define the
432 JavaScript and the Web
properties for that element in the order in which those properties are described in the
text. The CSS rule for the code tag ensures that the text within a <code> tag is set
in boldface, which makes it stand out more clearly on the page.
This action succeeds because console is a global variable and can therefore be
used in any JavaScript code. To create more sophisticated websites that support
interactivity, you need to learn how HTML entities can interact with other code.
12.4 Connecting JavaScript and HTML 433
In its entirety, the DOM is extremely complex and difficult to understand. Part
of the complexity arises from the fact that the DOM must support obsolete features
that still appear in some web pages. As a result, the DOM is an assemblage of old
and new features that resists all attempts to describe it in a coherent, integrated way.
Fortunately, you can use much of the DOM without having to master its intricacies.
The examples in this text limit themselves to classes, fields, and methods that make
sense if you view the DOM as a conventional class hierarchy.
A simplified UML diagram for the DOM classes appears in Figure 12-10. To
make this diagram as easy to understand as possible, several classes have been
434 JavaScript and the Web
eliminated from the hierarchy and a few of the fields and methods have been moved
into the classes in which someone writing JavaScript code would be most likely to
expect them. The fields and methods shown in the concrete classes at the bottom of
the hierarchy are precisely the ones you would want to use, even if they are defined
at slightly different levels than the diagram suggests.
Figure 12-11 offers one-line descriptions for each of the fields and methods
shown in Figure 12-10. More complete details for these methods—and a listing of
the many fields and methods that are omitted from this simplified description—are
easy to find on the web.
12.4 Connecting JavaScript and HTML 435
When you load a web page, the browser sets the global variable document so
that it holds the data structure for that page. JavaScript code can then call methods
on the document object, which gives it complete access to the contents. Those
methods allow you to analyze or modify the structure of the page, change the style
of existing elements by supplying new CSS declarations, and attach callback
functions to elements in order to make them interactive.
The only new HTML tag in this example is <input>, which creates an input
field that allows the user to enter text. Each of the <input> fields includes an id
attribute, which makes it possible for JavaScript code to refer to that element, and a
value attribute, which indicates the initial value of the field. The [Link] file
includes all the information that allows the browser to display the following page:
The first two lines use the getElementById method to store references to DOM
elements to the variables fahrenheit and celsius. The next two lines assign
callback functions that are triggered whenever an "input" event occurs. Typing a
character into the <input> element stored in fahrenheit, for example, generates
an input event, which calls the function convertFToC. This function reads the
value field of the fahrenheit element, converts that string to a number, applies
the formula for converting Fahrenheit to Celsius, and then uses the result of that
calculation to set the value field of the celsius element. For example, if the user
enters 212 in the fahrenheit field, the callback function stores the corresponding
Celsius temperature in the celsius field, like this:
The conversion operation is triggered on every keystroke, so the user would see the
Celsius equivalents of 2°F and 21°F before seeing the Celsius equivalent of 212°F.
[Link]
438 JavaScript and the Web
Collapsible lists
The <ul> and <ol> tags support the display of formatted lists in HTML. The <ul>
tag generates unordered lists in which a marker character precedes each element in
the list. The <ol> tag generates ordered lists, in which the elements are numbered
consecutively as determined by the list-style-type property. The individual
elements in either type of list appear in <li> tags inside the enclosing element.
Lists may be nested inside other lists. For example, the HTML specification
<ul>
<li>Item 1</li>
<li>
Item 2
<ul>
<li>Item 2.1</li>
<li>Item 2.2</li>
</ul>
</li>
<li>Item 3</li>
</ul>
generates a two-level list that looks like this when it is displayed in the browser:
The default formatting for lists works well if a list is small but is hard to read if a
list is large or deeply nested. In such cases, users find it much more convenient if
the list shows only part of the detail. At the beginning, the browser shows only the
top-level elements of the list. Each element, however, is marked with a clickable
triangle that opens that item to reveal its internal structure. Lists that support this
sort of selective viewing are called collapsible lists.
Clicking the triangle preceding the entry for Chapter 1 expands that item to reveal
the next level of detail, as follows:
Clicking the triangle again collapses the item and hides its contents. Computer
scientists use the verb toggle to describe the operation of switching back and forth
between two states. In this case, clicking the triangle toggles between the collapsed
and expanded state of an item. The state of each list item is indicated by the
orientation of the triangle that serves as the list marker. A collapsed element is
marked with a triangle pointing to the right. An expanded element is marked with a
triangle pointing down.
When you expand the list for Chapter 1, the entries for sections 1.1 through 1.4
are marked with rightward-facing triangles indicating that they contain additional
data at a further level of nesting. Section 1.5, by contrast, contains no subsections
and therefore appears without a marker. You can expand any of the first four
440 JavaScript and the Web
sections by clicking the triangle that precedes the list item. For example, if you
click the triangle before the list item for section 1.3, that item will expand, revealing
the following three-level list:
Amazingly, the code to convert a conventional HTML list into the far more
useful form of a collapsible list fits on a single page of JavaScript code, as shown in
Figure 12-14 on the next page. The onload attribute in the <body> tag calls the
collapseAll function, which goes through every <li> tag and performs the
following operations on the corresponding DOM element:
The setDisplayState function loops through the children of the list node
looking for nested <ul> elements. If it finds them, it changes the display field in
the style field of the node to "none" or "block" depending on whether the
element is collapsed or expanded. It also records the display state in a field called
isCollapsed, which is then used to check the current state in the callback function
that toggles the element between collapsed and expanded. Finally, the code for
setDisplayState changes the marker at the beginning of each list item either to
the URL for an image file ([Link] or [Link]) for lists
containing nested sublists or to "none" for list items without nested children.
12.4 Connecting JavaScript and HTML 441
[Link]
442 JavaScript and the Web
There are a few aspects of the [Link] code in Figure 12-14 that
are worth looking at in more detail. First, the code for setDisplayState makes
the necessary changes to the list style by assigning new values to individual fields
inside the record returned by [Link]. The names of the subfields in the
style component of an HTMLElement are chosen to be as close as possible to the
names of the corresponding CSS property. In many cases the names are exactly the
same, as in the assignment
[Link] = "none";
display:none;
The names, however, need to change if the CSS property names contain hyphens,
which are illegal in JavaScript identifiers. The solution used by the DOM is to
remove the hyphens and then capitalize the letter that follows each one, creating a
camel-case field name consistent with JavaScript’s rules. Thus, the assignment
[Link] = "none";
list-style-image:none;
At first glance, it’s probably not immediately clear why it is necessary to set the
listStyleImage field to "none" in the case of list elements that have no nested
children. By default, list items inherit the behavior from any enclosing list, which
means that leaving out the explicit assignment of "none" would cause the list to
inherit the image of its parent, which is not the desired behavior in this case.
The one other feature that requires further explanation is the line
[Link]();
in the callback function. The purpose of this line is to ensure that the click event
affects only the list item on which it was invoked. If a list is nested, a click will
typically occur inside more than one list node. A click on a particular triangle is
located within the list node that the triangle marks, but it is also inside the list node
at every higher level. When JavaScript delivers events to DOM objects, it begins by
invoking the event listener at the lowest level but, by default, continues to call event
listeners for every higher-level node that contains the current node. This process is
called bubbling. Each event bubbles up through every level of the hierarchy to give
each node a chance to respond. Calling stopPropogation prevents any further
bubbling and ensures that only the current node is toggled.
12.5 Storing data in the [Link] file 443
Both XML and HTML represent structured data in the form of sections bounded
by tags enclosed in angle brackets. These tags are typically paired, so that the
<body> tag that marks the start of the body of the web page is matched with a
</body> tag that marks its end. Tags that have no closing tag like the <br /> tag
in HTML must include the slash before the closing angle bracket when used in
XML, even though that slash is optional in HTML. Tags in both XML and HTML
may specify additional information in the form of attributes, which have the same
form in both languages.
The essential idea behind using XML to represent the application data is that all
modern browsers allow the [Link] file to include XML code beyond the
HTML tags that drive the browser. The browser, moreover, scans the entire
contents of the [Link] file and assembles it into a hierarchical data structure
that includes all the tags and their attributes, even if those tags and attributes are not
defined in HTML. As a result, it is perfectly legal to invent new XML tags for a
specific application and then include those tags in the [Link] file.
The program then waits for the student to enter an answer. Depending on the
response, the program will choose the next question either to provide more review
or to let the student move ahead more quickly. For example, if the student enters an
incorrect answer, the program will continue with another question about numbers,
which might look like this:
If the student instead supplies the correct response, the program moves on to a more
advanced question, such as the one shown in the following console log, which asks
the student a question about the remainder operator:
The text of each question, the list of possible answers, and the appropriate next
question to ask are stored in XML format in the [Link] file. For example, the
12.5 Storing data in the [Link] file 445
first question in the simple JavaScript course is represented in the [Link] file
in the following form:
<question id="Numbers1">
True or false: Numbers can have fractional parts.
<answer response="true" nextQuestion="Remainders1" />
<answer response="false" nextQuestion="Numbers2" />
</question>
The <question> tag encloses both the text of the question and the list of possible
responses, each of which appears in the form of an <answer> tag with two
attributes. The response attribute indicates a possible response from the user, and
the corresponding nextQuestion attribute provides the identifying key for the
next question to ask if the user gives that response. Here, for example, entering the
answer true sends the teaching machine to a question whose id attribute is
"Remainders1", which is the first question on the topic of the remainder operator.
Conversely, entering the answer false sends the teaching machine to the question
whose id attribute is "Numbers2" on the theory that the student needs more
practice with this topic.
The [Link] file for a tiny JavaScript course with seven questions appears
in Figure 12-15 on the next page. The <question> tags for those seven questions
are included in the [Link] file enclosed within the following tag:
<div style="display:none;">
The CSS declaration style="display:none;" ensures that the browser does not
display the embedded text within the <question> tags.
The code for the TeachingMachine application itself appears in Figure 12-16
on page 447. The program begins by setting the variable questionXML to the
DOM object that corresponds to the first question in the [Link] file, which
appears at index 0 in the array of all <question> tags. The program then calls
askQuestion to display the text of the current question. It does so by calling a
new method exported by the console object. The [Link] method is
similar to [Link] in that it sends output to the console. The difference is
that [Link] interprets its argument as HTML rather than a simple string,
which means that course designers can include HTML formatting tags in their
questions. The argument to [Link] is the entire HTML content enclosed
within the <question> tag concatenated with the string "<br />", thereby
ensuring that the question ends with a line break.
446 JavaScript and the Web
[Link]
12.5 Storing data in the [Link] file 447
[Link]
448 JavaScript and the Web
Once the text of the question has been written to the console, askQuestion
calls requestInput to get a line from the user. When the user completes the line,
the console invokes the callback function checkAnswer to process the user’s
response. The checkAnswer function calls getAnswerXML to look up the string
entered by the user to see whether it matches any of the responses expected for this
question. If not, getAnswerXML returns null, which allows checkAnswer to
display a message asking the user to try again. If it finds a match, getAnswerXML
returns the DOM object for the corresponding <answer> tag. In this case,
checkAnswer can retrieve the nextQuestion attribute from that object and ask
the next question.
• If the response attribute in an <answer> tag is the string "*", it matches any
answer that the user enters. The getAnswerXML function checks the <answer>
tags in order, so it makes sense to use the "*" option only as the final option.
• If the nextQuestion attribute is the string "EXIT", the TeachingMachine
interprets that value as a request to end the course.
Summary
This chapter gives you a taste of how to use JavaScript together with HTML and
CSS to create interactive web pages. Although a complete discussion of these
facilities would require another book, the examples in this chapter are sufficient to
get you started. Important points in this chapter include the following:
• HTML includes a <button> tag that displays an onscreen button. Clicking the
button runs the JavaScript code associated with the onclick attribute.
• The <button> tag is only one of many new HTML tags introduced in this
chapter. Figure 12-2 on page 419 summarizes the tags used in this chapter.
• Most tags in HTML are paired so that they have both a start and an end tag; a
few tags are standalone. In keeping with conventional practice, standalone tags
in this book all include a slash before the closing angle bracket, as in <br />.
• Tags that create the elements displayed on a web page fall into two classes.
Block elements represent a region of text that has its own vertical space. Inline
elements occur within a line and are subject to filling, just like standard text.
450 JavaScript and the Web
Review questions
1. What attribute in the <button> tag makes it possible to associate JavaScript
code with an HTML button?
3. In your own words, describe the differences between a block element and an
inline element.
6. Why is it still important to include the alt attribute in an <img> tag even
though all modern browsers make it possible to display images?
7. Who coined the term hyperlink? Which two individuals does this chapter
recognize for having come up with a similar idea earlier in the 20th century?
8. What CSS rule would you use to justify the text in all paragraphs on a page?
9. What are the three forms for a CSS selector listed in this chapter?
10. What are the three places in which CSS style rules may appear?
11. Describe the relationship between the border, margin, padding, and content
components used in the CSS box model.
13. What reason does the chapter offer for the complexity of the DOM?
16. Why is there no triangle in front of the line for section 1.5 in the second table of
contents listing on page 439?
18. How does JavaScript handle the fact that the names of many CSS properties
contain hyphens, which are not legal in JavaScript identifiers?
19. What JavaScript code would you use to double the font size of an HTML
element stored in the variable node?
20. What does the x stand for in the Microsoft file types .docx and .pptx?
21. True or false: Browsers report an error when they encounter an unrecognized
tag in an HTML file.
Exercises
1. As noted in the chapter, the button displayed by the HitchhikerButton page
is not the “invitingly large red button” that Douglas Adams describes. Use CSS
styles to change the font size, text color, background color, and alignment of
the button so that it looks like this, horizontally centered in the window:
2. Starting with the version you wrote for the preceding exercise, rewrite the
HitchhikerButton page so that the messages become increasingly strident
the first three times the button is pressed, as follows:
After the user presses the button a third time, it should stop responding. To
make this change, you will need to create a [Link] file that
contains the code to implement the callback function for the button.
3. Add a Clear button to the [Link] program from Figure 6-1 on page 199.
Pressing the Clear button should erase all the dots previously drawn in the
graphics window.
Exercises 453
4. Add the necessary CSS styles to the [Link] file for the web page that
displays a picture of Paul Otlet so that the image is centered in the window and
dynamically resized so that its width is a third of the window size. Add a
centered caption underneath the picture so that the screen image looks like this:
will be removed from the layout. Your program should pause two seconds
before removing or turning over the cards so that the player can remember their
location.
• Update the XML structure so that the names of the new tags are appropriate
to the Adventure context. For example, it seems better to use the tag names
<room> and <passage> than the tag names <question> and <answer>.
• Add objects to the game by introducing a new XML <object> tag that
looks something like this:
<object name="keys" description="a set of keys"
room="InsideBuilding" />
The attributes in this example specify the name of the object, the string used
to describe the object, and the room in which the object is initially placed.
• Implement the user commands TAKE, DROP, and INVENTORY that allow the
player to work with objects. For example, the command TAKE KEYS should
take the keys from the current room and add them to the player’s collection,
DROP KEYS should leave the keys in the current room, and INVENTORY
should display the descriptions of the objects the player is carrying.
• Make it possible to create interesting puzzles by allowing the player to
move through a passage only if the player is carrying some object. For
example, the tag
<passage direction="down" key="keys"
destination="BeneathGrate" />
indicates that the player can move down to the room whose id attribute is
"BeneathGrate" if the object named "keys" is in the player’s inventory.
Later <passage> tags can specify what action to take if the player tries to
move down without the keys.
Index