Running a forest trail in May, I startled a doe and her newborn fawn. The doe made a raspy snort to warn the fawn of danger. Not knowing what to do, the fawn wobbled over to me and plopped down between my legs. Trembling with fear, it looked up at me and bleated faintly, imploring me to keep it safe. The doe stood 20 yards off, quivering with agitation. Obviously, it had wanted the fawn to run away from the predator, not to it.
JavaScript is like the newborn fawn in that it does not know which way you want it to run. So by default, it will simply run forward—that is to say, from the first line in your script to the last. However, there are four ways to manipulate this mindless, sequential flow.
First, you can send JavaScript down different paths with if
and switch
statements. They are referred to as conditional statements because the paths run conditionally relative to the boolean value of an expression. true
gives JavaScript the green light to take a path, while false
tells JavaScript either to do nothing or to take a fall-through path. Second, you can tell JavaScript to take several roundabouts of a loop path with one of the four looping statements: while, do while, for, for
, or in
. Like if
and switch
, loops run conditionally relative to a boolean expression: true
tells JavaScript to take another roundabout, while false
tells JavaScript not to. Third, you can disrupt flow with a disruptive statement such as break, continue
, or return
. These statements prevent JavaScript from continuing on its way. Fourth, you can temporarily jump elsewhere in a script by way of function invocation. By this, I mean JavaScript goes off and runs the function and then comes back to the spot from which you invoked it.
Chapter 6 more fully covers functions.
Neither disruptive statements nor function invocations are dynamic. That is to say, neither provides a way for JavaScript to make a decision relative to circumstances. So, with them, a fawn would have to run away from squirrels as well as from wolves. On the other hand, conditional and looping statements are dynamic, so they do provide a way for JavaScript to think before it leaps.
So alrighty then, how does JavaScript think? I alluded to this earlier, but the answer is simple: boolean expressions. Truthy expressions, those that return true
or can be converted to true
, are a green light, while falsy expressions, those that return undefined, null, "", 0, NaN
, or false
, are a red light. So, not surprisingly, every conditional or looping statement contains a boolean expression, which enables JavaScript to make a decision.
What else do conditional or looping statements contain? They contain paths in the form of child statements or blocks, which are statements wrapped in curly braces. For this reason, conditional and looping statements are referred to as compound statements. So, if you want JavaScript to think, you write a compound statement.
The thing is, formal JavaScript syntax limits a compound statement to one child statement. For example, an if
conditional statement, which we will explore in a moment, can have only one child statement following its boolean expression. In the following if
statement, run(miles);
is the one child statement permitted by JavaScript:
if (timeToRun === true) run(miles);
Oftentimes this will not do, and JavaScript knows this. If you bundle several child statements in a pair of curly braces, JavaScript will look the other way and view the bundle, referred to as a block, as one child statement. So if I want JavaScript to run three child statements whenever it's time to run, which is to say timeToRun
contains true
, then I can bundle those statements in a block. JavaScript will be happy as a clam, and I get to run in shoes rather than barefoot:
if (timeToRun === true) { lace(shoes); run(miles); shower(); }
Note that the block of child statements is not followed by a semicolon. However, the child statements within the block are.
Oftentimes, you will want JavaScript to run a path if circumstances permit but otherwise do nothing and move on. if
conditional statements will be your bread and butter for this type of decision. To write one of these statements, simply type the keyword if
, followed by an expression in parentheses, and then a path in the form of a child statement or block. In the event that the expression does not return a boolean, JavaScript will convert the value to a boolean by passing it to Boolean()
, which we explored in Chapter 2. So if the expression returns any value other than undefined, null, "", 0, NaN
, or false
, JavaScript has a green light to run the path.
Therefore, a JavaScript interpreter views an if
condition like so:
if (Boolean(expression)) path
But you write it like this:
if (expression) path
Open firebug.html in Firefox and then press F12 to enable Firebug. If you're just joining us, flip back to the Preface for details on how to do this.
For any fast running I do, I tend to wear the Nike Mayfly, which weighs just four ounces. By comparison, most running shoes weigh three or four times as much. However, the downside to the Mayfly's minimalist design is that its cushioning goes dead after just 100 kilometers.
Let's create a mayfly
object containing two methods that may query a secret variable named tally
, which will contain a tally of kilometers run in a pair of Mayfly shoes. mayfly.addToTally()
adds its parameter (named km
) to tally
only if km
is safe for addition—that is to say, if km
is of the number datatype but not the special numbers NaN
or Infinity
. The other method, mayfly.kmLeftToLive()
, will return a message indicating how many kilometers of cushioning the Mayfly has left only if tally
is less than 100
.
So in Firebug, enter the following code, and click Run
. Doing so creates a closure so that tally
may be queried only by addToTally()
and kmLeftToLive()
. Closures are covered in Chapter 6, so just nod knowingly for now. Anyway, just focus on the two if
conditions.
var mayfly = function () { var tally = 0; return {
addToTally: function (km) { if (typeof km === "number" && isFinite(km)) { return tally += km; } }, kmLeftToLive: function () { if (tally < 100) { return "Mayfly has " + (100 - tally) + " kilometers left to live."; } } } }();
Now that we have initialized mayfly()
and the secret variable tally
, click Clear
in the bottom-right corner of Firebug. Doing so not only leaves mayfly()
in memory so that we can invoke it but also prevents us from overwriting it the next several times we click Run
.
Now let's add 10 kilometers to tally
. To do so, pass 10
to mayfly.addToTally()
by entering the following code and clicking Run
:
mayfly.addToTally(10); // 10
We can add 10 to tally
because we've passed in a number that passes the test in the first if
statement.
Click Run
three more times to log a few more 10K races. So now, as Figure 4-1 illustrates, our secret variable tally
contains 40
.
Click Clear in both Firebug panels and then try calling the other method, mayfly.kmLeftToLive()
. This one doesn't take any parameters. So, just enter the following code, and click Run in order to find out how many kilometers are left on our pair of Mayfly shoes. We see some output because the tally
variable is less than 100, and therefore the condition in the second if
is true
.
mayfly.kmLeftToLive(); // "Mayfly has 60 kilometers left to live."
Now what if you want JavaScript to go down one path when an expression is truthy but another path when it is falsy? Simply append an else
clause to your if
condition. To do so, simply type the else
keyword and then a path in the form of a child statement or block. JavaScript will run the if
path when the boolean expression is true
and else
path when it is false
. So, one or the other path will run, but never both.
Currently, both mayfly.addToTally()
and mayfly.kmLeftToLive()
return undefined
whenever JavaScript does not run their if
paths. Let's change that by adding else
clauses to both methods. To do so, click Clear in Firebug, enter the following, and click Run
:
var mayfly = function () { var tally = 0; return { addToTally: function (km) { if (typeof km === "number" && isFinite(km)) { return tally += km;} else {
return "Invalid parameter!";
}
}, kmLeftToLive: function () { if (tally < 100) { return "Mayfly has " + (100 - tally) + " kilometers left to live.";} else {
return "Mayfly is dead!";
}
} } }();
Now click Clear, and enter the following:
mayfly.addToTally("ten"); // "Invalid parameter!"
Here, typeof km
returns "string"
, so the ===
operator returns false
, and in turn the &&
operator returns false
. Therefore, JavaScript goes down the else
path, and mayfly.addToTally()
returns "Invalid parameter!"
rather than adding km
to tally
, which remains at 0
. Let's verify this by clicking Clear and then invoking mayfly.kmLeftToLive()
like so:
mayfly.kmLeftToLive(); // "Mayfly has 100 kilometers left to live."
Great, so our Mayfly still has a full tank. Now let's make sure the else
clause for mayfly.kmLeftToLive()
works by clicking Clear and then running the following:
mayfly.addToTally(110); mayfly.kmLeftToLive(); // "Mayfly is dead!"
So tally < 100
returns false
, and JavaScript goes down the else
path. Therefore, mayfly.kmLeftToLive()
returns "Mayfly is dead!"
to indicate that it's time to buy a new Mayfly.
By the way, even if you are not a runner, you might want to try the Mayfly sometime. The upper part is bright orange with a black support grid resembling the wing of a fly—you will be hard to miss in a pair of those!
As noted earlier, whenever a compound statement contains a single child statement, you do not have to wrap it in curly braces. So, we could have defined mayfly
like so, where the bold code shows the single child statements do not have curly braces:
var mayfly = function () { var tally = 0; return { addToTally: function (km) {if (typeof km === "number" && isFinite(km))
return tally += km;
else
return "Invalid parameter!";
}, kmLeftToLive: function () {if (tally < 100)
return "Mayfly has " + (100 - tally) + " kilometers left to live.";
else
return "Mayfly is dead!";
} } }();
Furthermore, we could have omitted the line breaks, too:
var mayfly = function () { var tally = 0; return { addToTally: function (km) {if (typeof km === "number" && isFinite(km)) return tally += km;
else return "Invalid parameter!";
}, kmLeftToLive: function () {if (tally < 100) return "Mayfly has " + (100 - tally) + " kilometers left to live.";
else return "Mayfly is dead!";
} } }();
The else if
idiom, which we will cover next, takes advantage of this single-line, no-bracket JavaScript feature. Moreover, you will encounter both styles in scripts written by others, myself included. However, as a beginner, you may want to wrap single child statements in curly braces inasmuch as this eliminates the need for you to remember that two or more child statements need to be wrapped in curly braces and that an else
clause goes with the nearest if
condition.
On the other hand, if you wrap all child statements in curly braces, you may find yourself wasting time debugging scripts that have, say, 143 opening braces but only 138 closing braces. Or you may find that peppering your scripts with optional braces makes them less readable.
Regardless of whether you wrap single child statements in curly braces, the important thing to note is that both styles are right. JavaScript does not care which style you go with. Moreover, even programmers who wrap everything in curly braces omit them to use the else if
idiom. (However, this is probably because they think else if
is a statement, not an idiom. So, they do not even know they are violating their mantra!)
I tend to have a smoothie for dessert most nights. Sometimes if it is late, that is all I will have. I don't worry too much about calories. My favorite full-throttle smoothie contains Brown Cow cream-top yogurt, grass-fed cream and milk, Saigon cinnamon, and wild blueberries. If you like yogurt, treat yourself to Brown Cow cream-top sometime. Trust me, you will never forget your first Brown Cow!
OK, that came out wrong. Anyway, for the Brown Cow and grass-fed cream and milk, I have to go to Whole Foods. But there is just one of those so far in Pittsburgh, and, to get there, I have to drive through murder alley. Most nights when I open the fridge there is no Brown Cow, and I have to choose some other yogurt or kefir for my smoothie. In descending order of preference, those are Stonyfield cream-top, Fage cultured cream, and Lifeway Greek-style kefir.
Alrighty then, let's create an object named fridge
with boolean members indicating what my cultured milk options are. Life is good tonight because I do have Brown Cow in there:
var fridge = { brownCow: true, stonyfield: false, fage: true, lifeway: false }; var smoothie;
Now we want JavaScript to choose my favorite available yogurt or kefir by testing the following four expressions in order from top to bottom:
fridge.brownCow fridge.stonyfield fridge.fage fridge.lifeway
But an if
condition can test only one expression. So, do we write four of those in a row? It's sort of a clunky solution, but let's do it in Firebug. Note that, for this to work, we have to test the four expressions from bottom to top so that the best available yogurt goes into my smoothie:
var fridge = { brownCow: true, stonyfield: false, fage: true, lifeway: false }; var smoothie; if (fridge.lifeway) { smoothie = "Lifeway Greek-style kefir"; } if (fridge.fage) {
smoothie = "Fage cultured cream"; } if (fridge.stonyfield) { smoothie = "Stonyfield cream-top yogurt"; } if (fridge.brownCow) { smoothie = "Brown Cow cream-top yogurt"; } smoothie += ", grass-fed cream and milk, Saigon cinnamon, and wild blueberries." // "Brown Cow cream-top yogurt, grass-fed cream and milk, Saigon cinnamon, // and wild blueberries."
Although this kludge works, it makes us look like bumpkins to JavaScript-savvy programmers because we test for every variation, even if we've previously found a match. There is a better way, right?
Yup, you betcha. First, reorder the if
conditions in descending order of preference. Second, nest if
conditions two through four in an else
clause for if
conditions one through three. We've now got some opt-outs if an if
condition is true
; in other words, we don't go on and test for every variation after an if
condition is found to be true
.
var fridge = { brownCow: true, stonyfield: false, fage: true, lifeway: false }; var smoothie; if (fridge.brownCow) { smoothie = "Brown Cow cream-top yogurt"; } else { if (fridge.stonyfield) { smoothie = "Stonyfield cream-top yogurt"; } else { if (fridge.fage) { smoothie = "Fage cultured cream"; } else { if (fridge.lifeway) { smoothie = "Lifeway Greek-style kefir"; } } } } smoothie += ", grass-fed cream and milk, Saigon cinnamon, and wild blueberries." // "Brown Cow cream-top yogurt, grass-fed cream and milk, Saigon cinnamon, // and wild blueberries."
This is more elegant, but we can do even better by using the else if
idiom. So, click Clear in both Firebug panels, and enter and run the following:
var fridge = { brownCow: true, stonyfield: false, fage: true, lifeway: false }; var smoothie;
if (fridge.brownCow) { smoothie = "Brown Cow cream-top yogurt"; } else if (fridge.stonyfield) { smoothie = "Stonyfield cream-top yogurt"; } else if (fridge.fage) { smoothie = "Fage cultured cream"; } else if (fridge.lifeway) { smoothie = "Lifeway Greek-style kefir"; } smoothie += ", grass-fed cream and milk, Saigon cinnamon, and wild blueberries." // "Brown Cow cream-top yogurt, grass-fed cream and milk, Saigon cinnamon, // and wild blueberries."
Verify your work with Figure 4-2.
That's much simpler to code and read, don't you think? Now what is going on here? In a nutshell, since all the else
clauses contain a single child statement, the curly braces are optional, so we omitted them along with the line breaks. Doing so means the else
and if
keywords come together, which is why the idiom is referred to as else if
.
But there is one problem: if none of the boolean expression returns true
, there's no fall-through path for JavaScript to take. Can we fix that? Oh, you betcha. Simply append an else
clause to the final nested if
condition. But this time, go with the optional curly braces. Let's make Dannon (Danone in the United Kingdom) the default and then set all fridge
members to false
so that we can test the fall-through path, as in Figure 4-3:
var fridge = { brownCow: false, stonyfield: false, fage: false, lifeway: false };
var smoothie; if (fridge.brownCow) { smoothie = "Brown Cow cream-top yogurt"; } else if (fridge.stonyfield) { smoothie = "Stonyfield cream-top yogurt"; } else if (fridge.fage) { smoothie = "Fage cultured cream"; } else if (fridge.lifeway) { smoothie = "Lifeway Greek-style kefir"; } else { smoothie = "Dannon yogurt"; } smoothie += ", grass-fed cream and milk, Saigon cinnamon, and wild blueberries." // "Dannon yogurt, grass-fed cream and milk, Saigon cinnamon, and wild blueberries."
So there it is. JavaScript has five paths to choose from. Before moving on, note that, since all five paths are single child statements, we may omit curly braces throughout:
var fridge = { brownCow: false, stonyfield: false, fage: false, lifeway: false }; var smoothie; if (fridge.brownCow) smoothie = "Brown Cow cream-top yogurt"; else if (fridge.stonyfield) smoothie = "Stonyfield cream-top yogurt"; else if (fridge.fage)
smoothie = "Fage cultured cream"; else if (fridge.lifeway) smoothie = "Lifeway Greek-style kefir"; else smoothie = "Dannon yogurt"; smoothie += ", grass-fed cream and milk, Saigon cinnamon, and wild blueberries." // "Dannon yogurt, grass-fed cream and milk, Saigon cinnamon, and wild blueberries."
In the event that your if
and else
clauses contain single expression statements, you may more elegantly control flow with a conditional expression using the ?:
operator, which we covered in Chapter 3. Moreover, you may nest conditional expressions to emulate the else if
idiom, too.
Even though you can create an expression statement by simply pinning a semicolon tail to any expression, you generally do so only for assignment, invocation, increment, or decrement expressions.
Click Clear in both Firebug panels, and rewrite our else if
sample using nested conditional expressions like so:
var fridge = { brownCow: true, stonyfield: false, fage: true, lifeway: false }; var smoothie = fridge.brownCow ? "Brown Cow cream-top yogurt" : (fridge.stonyfield ? "Stonyfield cream-top yogurt" : (fridge.fage ? "Fage cultured cream" : (fridge.lifeway ? "Lifeway Greek-style kefir" : "Dannon yogurt"))); smoothie += ", grass-fed cream and milk, Saigon cinnamon, and wild blueberries." // "Brown Cow cream-top yogurt, grass-fed cream and milk, Saigon cinnamon, // and wild blueberries."
Verify your work with Figure 4-4.
For reasons of readability, I recommend not nesting more than one ?:
expression. So, what we did earlier is not recommended. However, you will encounter such skullduggery in scripts written by others, and being familiar with the technique will prove helpful.
Now for a less common way to write a multiway branch, let's look at the switch
statement. Typically, switch
statements are used if all paths depend on the value of the same expression and if that expression returns a string or number.
From a bird's-eye view, if
and switch
statements look similar:
if (expression) {block} switch (expression) {block}
Beyond that, if
and switch
are markedly different. For one thing, the if
block contains just one path, while the switch
block contains many paths. Those are marked by one or more case
expressions. JavaScript decides which path to take by comparing the switch
expression to the case
expressions with the ===
operator. So, no datatype conversion takes place as would occur with the ==
operator.
For another, case
expressions mark only where JavaScript begins running statements in the switch
block. You have to manually mark the end of each path with a break
or return
disruptive statement. Doing so prevents JavaScript from running all paths downstream of the matching case
expression.
Finally, whereas an else
clause contains the fall-through path for an if
statement, a default
case clause contains the fall-through for a switch
. However, just like else
, the default
path is optional.
So, refresh Firefox to clear everything we coded thus far from memory. Then click Clear in Firebug to give you a clean slate, and let's try a switch
. For your favorite sports team, say you want JavaScript to return the name of a player based on a jersey number. Since all paths depend on the same expression, which returns a number, switch
is more efficient than else if
. For the Pittsburgh Steelers, a switch
for jersey numbers would look like this. Feel free to go with your favorite team rather than mine.
var jersey = 34, name = ""; switch (jersey) { case 7: name = "Roethlisberger";
break; case 10: name = "Holmes"; break; case 17: name = "Wallace"; break; case 34: name = "Mendenhall"; break; case 43: name = "Polamalu"; break; case 83: name = "Miller"; break; case 86: name = "Ward"; break; case 92: name = "Harrison"; break; case 94: name = "Timmons"; break; case 96: name = "Hood"; break; default: name = "not worn by any Steeler"; break; } "Number " + jersey + " is " + name + "."; // "Number 34 is Mendenhall."
Verify your work with Figure 4-5.
Here, JavaScript had to evaluate the first four case
clauses in order to identify 34
as Mendenhall. Now change jersey
to a number no case
expression matches in order to make sure the default
path works:
var jersey = 1, name = ""; switch (jersey) { case 7: name = "Roethlisberger"; break; case 10: name = "Holmes"; break; case 17: name = "Wallace"; break; case 34: name = "Mendenhall"; break; case 43: name = "Polamalu";
break; case 83: name = "Miller"; break; case 86: name = "Ward"; break; case 92: name = "Harrison"; break; case 94: name = "Timmons"; break; case 96: name = "Hood"; break; default: name = "not worn by any Steeler"; break; } "Number " + jersey + " is " + name + "."; // "Number 1 is not worn by any Steeler."
Since there is no case
clause for 1
, JavaScript ran the default
path. Note that, although the default
case typically goes last, that is not something JavaScript requires. So let's put it first instead:
var jersey = 1, name = ""; switch (jersey) { default: name = "not worn by any Steeler"; break; case 7: name = "Roethlisberger"; break; case 10: name = "Holmes"; break; case 17: name = "Wallace"; break; case 34: name = "Mendenhall"; break; case 43: name = "Polamalu"; break; case 83: name = "Miller"; break; case 86: name = "Ward"; break; case 92: name = "Harrison";
break; case 94: name = "Timmons"; break; case 96: name = "Hood"; break; } "Number " + jersey + " is " + name + "."; // "Number 1 is not worn by any Steeler."
It works just as well there. Now, as I noted earlier, case
clauses can have more than one case
expression. This provides a way for you to run a path for more than one string or number. For example, numbers 92
and 97
on the Steelers are both named Harrison, so let's kill two birds with one stone like this:
var jersey = 92, name = ""; switch (jersey) { case 7: name = "Roethlisberger"; break; case 10: name = "Holmes"; break; case 17: name = "Wallace"; break; case 34: name = "Mendenhall"; break; case 43: name = "Polamalu"; break; case 83: name = "Miller"; break; case 86: name = "Ward"; break; case 92: case 97: name = "Harrison"; break; case 94: name = "Timmons"; break; case 96: name = "Hood"; break; default: name = "not worn by any Steeler"; break; } "Number " + jersey + " is " + name + ".";
// "Number 92 is Harrison."
Verify your work with Figure 4-6.
JavaScript fell through from the case
clause for 92
to the one for 97
. Now let's be ornery, omit some break
statements, and see what happens:
var jersey = 7, name = ""; switch (jersey) { case 7: name = "Roethlisberger"; case 10: name = "Holmes"; case 17: name = "Wallace"; case 34: name = "Mendenhall"; case 43: name = "Polamalu"; case 83:
name = "Miller"; break; case 86: name = "Ward"; break; case 92: case 97: name = "Harrison"; break; case 94: name = "Timmons"; break; case 96: name = "Hood"; break; default: name = "not worn by any Steeler"; break; } "Number " + jersey + " is " + name + "."; // "Number 7 is Miller."
Here, JavaScript begins running the switch
block with the statement name = "Roethlisberger";
and stops when it encounters the break
statement after the statement name = "Miller";
, so for a jersey number of 7
, JavaScript incorrectly returns "Miller"
. Put another way, JavaScript just ran a path like the one for the following ridiculous if
condition, which overwrites name
six times in a row!
if (jersey === 7) { name = "Roethlisberger"; name = "Holmes"; name = "Wallace"; name = "Mendenhall"; name = "Polamalu"; name = "Miller"; }
Now put the break
statements back in, change jersey to 96
, and delete the break
after the case
clause for 96
. Click Run
to see what happens:
var jersey = 96, name = ""; switch (jersey) { case 7: name = "Roethlisberger"; break; case 10: name = "Holmes"; break; case 17: name = "Wallace"; break; case 34: name = "Mendenhall"; break; case 43: name = "Polamalu";
break; case 83: name = "Miller"; break; case 86: name = "Ward"; break; case 92: case 97: name = "Harrison"; break; case 94: name = "Timmons"; break; case 96: name = "Hood"; default: name = "not worn by any Steeler"; break; } "Number " + jersey + " is " + name + "."; // "Number 96 is not worn by any Steeler."
As you can see, JavaScript will continue running statements, even those in the default
clause, until it either encounters a disruptive statement or encounters the closing curly brace. By neglecting to put a break
statement after the case
clause for 96
, we effectively had JavaScript run the following if
condition:
if (jersey === 96) { name = "Hood"; name = "not worn by any Steeler"; }
Note that had we put the default
case at the top of the switch
, JavaScript would not have fallen through from the case
clause for 96
to the default
.
As previously noted, if
a switch
appears within a function, then you can end paths with a return
disruptive statement instead of a break
. Oftentimes, the return
statement not only marks the end of the path but also is the path itself. So, let's go ahead and put our switch
in a function so that we can use return
statements:
var jersey = 7, name = ""; function identifyPlayer() { switch (jersey) { case 7: return "Roethlisberger"; case 10: return "Holmes"; case 17: return "Wallace"; case 34: return "Mendenhall"; case 43: return "Polamalu"; case 83: return "Miller"; case 86:
return "Ward"; case 92: return "Harrison"; case 94: return "Timmons"; case 96: return "Hood"; default: return "not worn by any Steeler"; } } "Number " + jersey + " is " + identifyPlayer() + "."; // "Number 7 is Roethlisberger."
Verify your work with Figure 4-7.
One final note on switch
statements: case
expressions, which go between the case
keyword and the colon, typically are string or number literals. However, any expression will do. Just make sure those do not do anything other than return a value for the ===
operator to test for identity versus the value of the switch
expression. I say this because JavaScript does not evaluate case
expressions downstream of the one that matches the switch
expression. So, you never know how many of your case
expressions will run. For example, if your fourth case
expression invokes a function that returns a number for ===
to work with but also changes three variables elsewhere in your script and your second case
expression matches the switch
expression, then JavaScript never has a chance to change those three variables. This unpredictability is why writing case
expressions with secondary effects is frowned upon. Don't do it.
To eliminate the drudgery of coding a slew of identical conditional statements one after another, JavaScript provides you with four looping statements. Remember from earlier that those are while, do while, for
, and for in
. We will explore each of those in turn, beginning with the simple while
loop.
while
loops are like an if
condition that runs over and over until its expression returns false
. Not surprisingly, from a syntax point of view, while
and if
statements appear similar:
if (expression) path while (expression) path
Just as JavaScript converts the value of an if
condition's expression to a boolean if necessary, it does so for a while
loop's expression, too. So to JavaScript, the game plan looks like this:
while (Boolean(expression)) path
The first time JavaScript runs a while
statement, if the expression returns true
or a value that converts to true
, then the path runs. On the other hand, if the expression returns false
or a value that converts to false
(remember those are undefined, null, "", 0
, or NaN
), then the path does not run. That is to say, on its first iteration, a while
loop is no different from an if
condition.
Now if the path ran on the first iteration, JavaScript has to decide whether to take another roundabout of the path. To do so, it simply reevaluates the while
loop's expression. In the event that the expression again returns a truthy value, the path runs. But if the expression returns a falsy value, the path does not run, so JavaScript moves past the while
loop and continues with the remainder of your script.
Iterations of the while
loop continue until its expression returns a falsy value. With this in mind, you want to ensure that eventually the expression does return a falsy value. Otherwise, the loop will never stop iterating. Such a mistake is aptly referred to as an infinite loop. Those freeze the browser until its long-running script limit, typically between 5 and 10 seconds, is reached.
Essentially, we want to write an if
condition but somewhere in the path make sure that expression will eventually return a falsy value. Typically, this is done by incrementing a loop variable, traditionally named i, j
, or k
, which you in turn compare to the number of roundabouts you want JavaScript to take. So, click Clear in both Firebug panels, and let's enter and run a simple while
loop.
I don't know about you, but a cup of tea brightens my mood. So, if I am feeling a little glum and want to rummage through the looseleaf teas in the pantry looking for Borpatra, my favorite Assam tea, I could do so with the following while
loop. So, enter and run the following:
var looseLeafTea = [ "Ghillidary", "Kenilworth", "Milima", "Keemun", "Boisahabi", "Manohari", "Borpatra", "Lukwah", "Khongea" ]; var mood = "glum"; var i = 0; while (i < looseLeafTea.length) { if (looseLeafTea[i] === "Borpatra") { mood = "cheery"; break;
} i ++; } "I feel " + mood + "!"; // "I feel cheery!"
Verify your work with Figure 4-8.
Here we have a looseLeafTea
array with nine elements. Prior to running the while
loop, we initialize a loop variable named i
to 0
, the index of the first element in looseLeafTea
. For the while
loop's boolean expression, we test whether i
is less than looseLeafTea.length
, which is 9
. At the very end of the while
path, we add 1
to i
with the ++
operator. In this way, the loop will run at most nine times, one iteration per element in looseLeafTea
.
During a particular roundabout of the while
path, we can query the next element in looseLeafTea
with i
and the []
operator. So, for example, during the fourth iteration i
would be 3
(remember it started at 0
), and so looseLeafTea[i]
would be "Keemun"
. This behavior is typical of a loop. That is to say, on each roundabout you have JavaScript run the same set of commands on a different variable, member, or element. So, loops provide a way to do things in a batch. It's kind of like baking oatmeal cookies!
Now, unless we tell JavaScript otherwise, it will take all nine roundabouts of the while
path. There's no harm in that, but it is inefficient. In the event that an element contains "Borpatra"
, then there's no need to loop through the remainder of looseLeafTea
. To tell JavaScript that enough is enough, we add break
statement to the while
loop. Doing so tells JavaScript to move past the while
statement and continue with the next statement in the script, which in our case glues mood
to a couple of other strings.
So, our while
loop eliminated the drudgery of having to write separate if
conditions for the nine elements in looseLeafTea
like so:
var looseLeafTea = [ "Ghillidary", "Kenilworth", "Milima",
"Keemun", "Boisahabi", "Manohari", "Borpatra", "Lukwah", "Khongea" ]; var mood = "glum"; if (looseLeafTea[0] === "Borpatra") { mood = "cheery"; } if (looseLeafTea[1] === "Borpatra") { mood = "cheery"; } if (looseLeafTea[2] === "Borpatra") { mood = "cheery"; } if (looseLeafTea[3] === "Borpatra") { mood = "cheery"; } if (looseLeafTea[4] === "Borpatra") { mood = "cheery"; } if (looseLeafTea[5] === "Borpatra") { mood = "cheery"; } if (looseLeafTea[6] === "Borpatra") { mood = "cheery"; } if (looseLeafTea[7] === "Borpatra") { mood = "cheery"; } if (looseLeafTea[8] === "Borpatra") { mood = "cheery"; } "I feel " + mood + "!"; // "I feel cheery!"
Bet you're glad now that JavaScript provides looping statements so that you don't have to write all those if
conditions! Note that, in addition to saving programmers time, loops let JavaScript work smart. In our while
loop, JavaScript knew it was not necessary to query the final three elements, since it had already found "Borpatra"
.
A break
statement tells JavaScript to totally abort a loop. But what if you just want to abort an iteration but not the loop? Is there anything less draconian than break
? It turns out there is. continue
statements simply terminate an iteration. JavaScript then reevaluates the boolean expression to see whether it takes another roundabout.
Typically, continue
is used to abort an iteration when a variable contains an undesirable value such as undefined
or ""
. Let's corrupt looseLeafTea
by adding ""
prior to "Kenilworth"
and undefined
prior to "Keemun"
(by inserting two commas in a row). Then add an else if
clause that runs whenever an element in looseLeafTea
contains a falsy value. In there we will do two things. First, we will delete the falsy element from looseLeafTea
by way of the predefined splice()
method, which we will cover in more detail in Chapter 5, that just removes the element from the array and then brings the subsequent elements forward to fill the gap. Second, we will insert a continue
statement to abort the iteration. This statement will halt the current iteration of the loop and jump back to the start of the while
loop with a new iteration. Note that this means we will skip the i ++
line of code, so the counter will not be incremented. This is exactly what we want to happen because, when we removed the falsy element, JavaScript brought all the remaining elements forward to fill the gap, so there is a new element now occupying the position of the old falsy element. For that reason, we want to loop over the same index in the array twice to make sure we cover all the elements. Finally, let's increment i
within an else
clause just to make things read better.
So, modify the previous sample like so, and click Run
in Firebug:
var looseLeafTea = [ "Ghillidary", "", "Kenilworth", "Milima", , "Keemun", "Boisahabi", "Manohari", "Borpatra", "Lukwah", "Khongea" ]; var mood = "glum"; var i = 0; while (i < looseLeafTea.length) { if (looseLeafTea[i] === "Borpatra") { mood = "cheery"; break; } else if (! looseLeafTea[i]) { looseLeafTea.splice(i, 1); continue; } else { i ++; } } "I feel " + mood + "!"; // "I feel cheery!"
Before moving on, let's check to make sure JavaScript did weed out the ""
and undefined
values from looseLeafTea
. So, click Clear, and then query looseLeafTea
like so, verifying your work with Figure 4-9:
looseLeafTea; // ["Ghillidary", "Kenilworth", "Milima", "Keemun", "Boisahabi", "Manohari", // "Borpatra", "Lukwah", "Khongea"]
So there it is. JavaScript deleted the ""
and undefined
elements just like we wanted.
Now just as you can abort a switch
with return
rather than break
whenever the switch
appears in a function, you can abort a loop (while, do while, for, for in
) with return
rather than break
whenever the loop appears in a function. So, click Clear in both Firebug panels, and let's rewrite our while
loop inside a function, replacing break
with return
:
var looseLeafTea = [ "Ghillidary", "Kenilworth", "Milima", "Keemun", "Boisahabi", "Manohari", "Borpatra", "Lukwah", "Khongea" ]; function findTea(tea) { var i = 0; while (i < looseLeafTea.length) { if (looseLeafTea[i] === tea) { return "cheery";
} else if (! looseLeafTea[i]) { looseLeafTea.splice(i, 1); continue; } else { i ++; } } return "glum"; } "I feel " + findTea("Kenilworth") + "!"; // "I feel cheery!"
As Figure 4-10 illustrates, invoking our function findTea()
evaluates to "cheery"
or "glum"
depending upon whether JavaScript can find the value of the tea
parameter in looseLeafTea
.
Chapter 6 covers functions more fully.
while
loops provide a way to conditionally run a path zero or more times. That is, if the loop expression equates to false
when it is first evaluated, then the path will not run at all. Now what if you want to make sure the path runs at least one time? For this circumstance, you would write a do while
loop. The syntax for do while
is as follows:
do path while (expression);
As you might guess by now, path
can be either a single child statement or a block, and the value of expression is converted to a boolean if necessary by passing it to Boolean()
. Though it is easy to overlook, the semicolon following the expression in parentheses is required. With those things in mind, click Clear in both Firebug panels, and let's try a do while
loop.
More often than not, when it comes time to follow a recipe, there is a spice that if unavailable would put the kaibosh on my plans. Say I want to make lemon scones; the limiting ingredient would be, oddly enough, lemon peel. Being a foodie, it is safe to assume I have at least one spice. Therefore, it makes sense to rummage through the spice shelf with a do while
loop like so, because we want to check at least one spice:
var spices = [ "cinnamon", "ginger", "nutmeg", "cloves", "sesame seed", "pepper", "rosemary", "tarragon", "basil", "mace", "poppy seed", "lemon peel", "vanilla", "oregano", "allspice", "thyme" ]; var putTheKaiboshOn = true; var i = 0; do { if (spices[i] === "lemon peel") { putTheKaiboshOn = false; break; } i ++; } while (i < spices.length); (putTheKaiboshOn) ? "No can do!" : "Go right ahead!"; // "Go right ahead!"
Verify your work with Figure 4-11.
Here, ===
will compare "cinnamon"
to "lemon peel"
no matter what, since JavaScript always takes at least one roundabout of a do while
loop. Thereafter, JavaScript will do another iteration only if i < spices.length
returns true
. Since spices.length
evaluates to 15
, JavaScript will run the do while
path 15 times unless we tell it otherwise with a break
statement, which we do in the event we find "lemon peel"
in spices
. Finally, our loop variable i
contains the index by which we query elements in spices
, so we increment i
with the ++
operator at the end of the path. In this way, JavaScript can decide whether there is any point to taking another roundabout.
In the event that JavaScript finds "lemon peel"
while rummaging through spices
, it assigns false
to putTheKaiboshOn
. This variable in turn enables JavaScript to decide whether our recipe is doable. If putTheKaiboshOn
is false
, JavaScript prints "Go right ahead!"
. Otherwise, it prints "No can do!"
. Test that your code works both ways by running the sample with and without "lemon peel"
in the spices
array.
Before moving on, let's rework our do while
loop as a while
loop. Doing so illustrates that JavaScript unconditionally takes the first roundabout of a do while
loop and then conditionally takes any subsequent ones. Therefore, to emulate such behavior with a while
loop, you would have to key in the path twice like so:
path while (expression) path
With this in mind, click Clear in both Firebug panels, and let's try to pull this off:
var spices = [ "cinnamon", "ginger",
"nutmeg", "cloves", "sesame seed", "pepper", "rosemary", "tarragon", "basil", "mace", "poppy seed", "lemon peel", "vanilla", "oregano", "allspice", "thyme" ]; var putTheKaiboshOn = true; var i = 0;if (spices[i] === "lemon peel") {
putTheKaiboshOn = false;
} else {
i ++;
while (i < spices.length) {
if (spices[i] === "lemon peel") {
putTheKaiboshOn = false;
break;
}
i ++;
}
}
(putTheKaiboshOn) ? "No can do!" : "Go right ahead!"; // "Go right ahead!"
As Figure 4-12 illustrates, the while
equivalent to our do while
loop is much more verbose. Therefore, for circumstances where you want JavaScript to go down a path one or more times, it is much more elegant to control flow with do while
rather than while
. Many beginners shy away from do while
. Don't be one of them!
Observant readers will notice that in both our while
and do while
samples, we initialized a loop variable prior to the loop and then incremented it at the very end of the loop's path. It's a bit of a pain to have to remember to initialize and increment a loop variable, don't you think? JavaScript thinks so, too. Consequently, there is a third kind of looping statement, for
, which puts the initialization to the left of the boolean expression and the increment to the right. Note that semicolons separate the initialization, boolean, and increment expressions.
Click Clear in both Firebug panels, and let's try a for
loop.
Norah Jones is one of my favorite recording artists. I was listening to a prerelease of her new album, The Fall, today. I am thinking that "Back to Manhattan" will be a hit. It is with me anyway. However, since the album is new, I have a hard time remembering the track number for "Back to Manhattan."
Let's create an array named theFall
containing a chronological list of tracks on The Fall and then have JavaScript iterate through those with a for
loop looking for "Back to Manhattan"
, which we will save to a variable named song
. JavaScript will then add 1
to the index of the matching element and store the result in j
, which represents the track number. We do so since array elements are numbered with integers beginning with 0
, while album tracks obviously are numbered with integers beginning with 1
.
Enter and run the following for
loop, then verify your work with Figure 4-13:
var theFall = [ "Chasing Pirates", "Even Though", "Light as a Feather", "Young Blood", "I Wouldn't Need You", "Waiting", "It's Gonna Be", "You've Ruined Me", "Back to Manhattan", "Stuck", "December", "Tell Yer Mama", "Man of the Hour" ]; var song = "Back to Manhattan"; for (var i = 0, j = 0; i < theFall.length; i ++) { if (theFall[i] === song) { j = i + 1; break; } } song + (j > 0 ? " is track " + j : " is not") + " on The Fall."; // "Back to Manhattan is track 9 on The Fall."
Here, JavaScript initializes i
and j
to 0
prior to evaluating the boolean expression i < theFall.length
. Following any roundabout of the for
loop, JavaScript increments i
with the ++
operator. Then it reevaluates the boolean expression to see whether it should run the path again. Note that the initialization expressions are not reevaluated. That is to say, JavaScript runs those just the first time. Note too that we use the comma operator to separate the two initialization expressions. Recall from Chapter 3 that doing so makes i = 0
and j = 0
count as one expression rather than two. So, the comma operator does for expressions what curly braces do for statements—it makes two or more count as one.
In the event that you have previously declared the loop variables, i
and j
in our sample, the var
keyword is optional.
The fourth and final looping statement, for in
, provides a way to enumerate the members of an object. JavaScript ensures the boolean expression, which uses the in
operator from Chapter 3, returns true
by assigning the name of a different member to the left operand of in
prior to an iteration. There's no need to initialize and increment a loop variable in order to prevent an infinite loop; JavaScript already knows to do one iteration per member.
for (member in object) path
There are three things to note. First, regardless of whether the member was named with an identifier or string, JavaScript returns its name as a string. Second, the left operand to in
may be a variable, member, or element. That is to say, it can be anything you can assign a string to. Third, the right operand to in
may be any expression for an object—typically, an identifier or function invocation. With those things in mind, click Clear in both Firebug panels, and let's work through a sample for in
loop.
I like to wear a shoe tailored to the pace and distance of a run. So, I have a number of running shoes in the cellar. There are eight Nikes down there at the moment. Let's create an object named shoes
containing the weight in ounces of each shoe I could run in this evening:
var shoes = { "LunaRacer": 6.6, "Air Max": 13, "LunarGlide": 10.2, "Zoom Streak XC": 7, "Free": 8.6, "Mayfly": 4, "Zoom Vomero": 11.6, "LunarElite": 9.7 }
Generally, the more a shoe weighs, the more cushioning it provides. Therefore, if I am running far afield, I wear a pair of shoes weighing more than 10 ounces. Let's enumerate the members in shoes
, saving the name of any shoe weighing more than 10 ounces to an array named myOptions
. In this way, I will know what my options are.
Note that during each roundabout of the for in
loop, JavaScript assigns the name of a member in shoes
to shoe
. So, shoes[shoe]
will return the weight of a shoe. In the event that this weight is greater than or equal to 10
, we add the name of the shoe to myOptions
by way of the push()
method, which I will cover in Chapter 5.
Try entering and running the following for in
loop in Firebug, then verify your work with Figure 4-14:
var shoes = {
"LunaRacer": 6.6, "Air Max": 13, "LunarGlide": 10.2, "Zoom Streak XC": 7, "Free": 8.6, "Mayfly": 4, "Zoom Vomero": 11.6, "LunarElite": 9.7 } var myOptions = []; for (var shoe in shoes) { if (shoes[shoe] >= 10) { myOptions.push(shoe); } } myOptions; // ["Air Max", "LunarGlide", "Zoom Vomero"]
Our for in
loop eliminated the drudgery of having to write the following eight if
conditions:
if (shoes["LunaRacer"] >= 10) { myOptions.push("LunaRacer"); } if (shoes["Air Max"] >= 10) { myOptions.push("Air Max"); } if (shoes["LunarGlide"] >= 10) { myOptions.push("LunarGlide"); } if (shoes["Zoom Streak XC"] >= 10) { myOptions.push("Zoom Streak XC"); }
if (shoes["Free"] >= 10) { myOptions.push("Free"); } if (shoes["Mayfly"] >= 10) { myOptions.push("Mayfly"); } if (shoes["Zoom Vomero"] >= 10) { myOptions.push("Zoom Vomero"); } if (shoes["LunarElite"] >= 10) { myOptions.push("LunarElite"); }
Now that we have explored conditionals and loops, let's work through some techniques to make them run snappier—not just a little but two to seven times snappier, if not more. Insofar as the bulk of any JavaScript behavior is comprised of conditionals and loops, doing so will in turn make your behaviors feel that much more responsive to visitors. Yup, I like the sound of that, too!
Let's begin with a few ways to code speedier switch
and if else
conditionals. One simple thing you can do is favor switch
over if else
whenever coding five or more paths for JavaScript to conditionally take. Past four, incrementally adding conditions to a switch
decrements speed much less than doing so for an if else
.
OK, that was painless. And so is the second way to code snappier conditionals—just order their paths from the one JavaScript is most likely to take to the one JavaScript is least likely to take. Doing so minimizes the number of boolean expressions JavaScript has to evaluate.
Now for the third way to code snappier conditionals: don't code them in the first place. No, that's not a typo. In the event that you have written an if else
or switch
for variations of a string or number and every path does the same thing (just with a different value of that string or number), replacing the if else
or switch
with an array or object member query will result in an extraordinary speed gain, to put it mildly.
There are a couple of reasons why doing so is preferable to conditional statements. For one thing, JavaScript does not have to drill down through conditions, just one []
operation. For another, whereas adding conditions to a switch
or if else
decrements evaluation speed, adding members to an object or elements to an array does not decrement query speed.
Ideally, you are sold on replacing conditionals with an object or array query whenever possible. Now let's give the technique a try. Double-clear Firebug, and then enter the following function, which contains a switch
mapping jersey numbers to player names for Opening Day, 2010 as my Pittsburgh Pirates try to avert their eighteenth losing season in a row. Insofar as 17 of 25 Pirates were not in the dugout last April, namePirate()
will come in handy!
function namePirate(jersey) { var name; switch(jersey) { case 77: name = "D.J. Carrasco"; break; case 53: name = "Brendan Donnelly"; break; case 29: name = "Octavio Dotel";
break; case 57: name = "Zach Duke"; break; case 48: name = "Javier Lopez"; break; case 28: name = "Paul Maholm"; break; case 34: name = "Daniel McCutchen"; break; case 47: name = "Evan Meek"; break; case 37: name = "Charlie Morton"; break; case 49: name = "Ross Ohlendorf"; break; case 62: name = "Hayden Penn"; break; case 43: name = "Jack Taschner"; break; case 41: name = "Ryan Doumit"; break; case 35: name = "Jason Jaramillo"; break; case 13: name = "Ronny Cedeno"; break; case 6: name = "Jeff Clement"; break; case 2: name = "Bobby Crosby"; break; case 3: name = "Akinori Iwamura"; break; case 15: name = "Andy LaRoche"; break; case 19: name = "Ryan Church"; break; case 46: name = "Garrett Jones";
break; case 22: name = "Andrew McCutchen"; break; case 85: name = "Lastings Milledge"; break; case 58: name = "John Raynor"; break; case 24: name = "Delwyn Young"; break; default: name = "not worn by any Pirate"; } return jersey + " is " + name + "."; }
We're only two games into the season, but the big fella wearing 46 has already hit three home runs. One of those even landed in the Allegheny River, which flows past PNC Park where the Pirates play. So, let's pass 46
to namePirate()
and find out who that thumper is. Verify your work with Figure 4-15.
function namePirate(jersey) { var name; switch(jersey) { case 77: name = "D.J. Carrasco"; break; case 53: name = "Brendan Donnelly"; break; case 29: name = "Octavio Dotel"; break; case 57: name = "Zach Duke"; break; case 48: name = "Javier Lopez"; break; case 28: name = "Paul Maholm"; break; case 34: name = "Daniel McCutchen"; break; case 47: name = "Evan Meek"; break; case 37: name = "Charlie Morton"; break; case 49:
name = "Ross Ohlendorf"; break; case 62: name = "Hayden Penn"; break; case 43: name = "Jack Taschner"; break; case 41: name = "Ryan Doumit"; break; case 35: name = "Jason Jaramillo"; break; case 13: name = "Ronny Cedeno"; break; case 6: name = "Jeff Clement"; break; case 2: name = "Bobby Crosby"; break; case 3: name = "Akinori Iwamura"; break; case 15: name = "Andy LaRoche"; break; case 19: name = "Ryan Church"; break; case 46: name = "Garrett Jones"; break; case 22: name = "Andrew McCutchen"; break; case 85: name = "Lastings Milledge"; break; case 58: name = "John Raynor"; break; case 24: name = "Delwyn Young"; break; default: name = "not worn by any Pirate"; } return jersey + " is " + name + "."; }namePirate(46);
// "46 is Garrett Jones."
Now then, I ordered the switch
like a scorecard (pitchers, catchers, infielders, outfielders) and then by surname. JavaScript had to evaluate 21 case
clauses to determine who wears 46. But if we were to replace the switch
with an object member query, we would in turn replace those 21 ===
operations with 1 []
operation. Yup, that sounds like a good idea to me, too.
So, double-clear Firebug, and then enter the following object literal. Note that, although we are adding members in the same order as in the switch
, which is to say by position and then by surname, remember that ECMAScript does not define an order for object members. So, neither D. J. Carrasco nor any other Pirate is the first member in pirates
. Note too that, since the jersey numbers are integers, we could have done this with an array. However, that would be a bear to type. Insofar as an object may have elements just like an array, a feature I will explore in Chapter 5, there is no point in doing all the extra typing to create an array lookup.
var pirates = { "77": "D.J. Carrasco", "53": "Brendan Donnelly", "29": "Octavio Dotel", "57": "Zach Duke", "48": "Javier Lopez", "28": "Paul Maholm", "34": "Daniel McCutchen", "47": "Evan Meek",
"37": "Charlie Morton", "49": "Ross Ohlendorf", "62": "Hayden Penn", "43": "Jack Taschner", "41": "Ryan Doumit", "35": "Jason Jaramillo", "13": "Ronny Cedeno", "6": "Jeff Clement", "2": "Bobby Crosby", "3": "Akinori Iwamura", "15": "Andy LaRoche", "19": "Ryan Church", "46": "Garrett Jones", "22": "Andrew McCutchen", "85": "Lastings Milledge", "58": "John Raynor", "24": "Delwyn Young" };
Now let's code a much simpler version of namePirate()
. There's just one line of code in the body now:
var pirates = {
"77": "D.J. Carrasco",
"53": "Brendan Donnelly",
"29": "Octavio Dotel",
"57": "Zach Duke",
"48": "Javier Lopez",
"28": "Paul Maholm",
"34": "Daniel McCutchen",
"47": "Evan Meek",
"37": "Charlie Morton",
"49": "Ross Ohlendorf",
"62": "Hayden Penn",
"43": "Jack Taschner",
"41": "Ryan Doumit",
"35": "Jason Jaramillo",
"13": "Ronny Cedeno",
"6": "Jeff Clement",
"2": "Bobby Crosby",
"3": "Akinori Iwamura",
"15": "Andy LaRoche",
"19": "Ryan Church",
"46": "Garrett Jones",
"22": "Andrew McCutchen",
"85": "Lastings Milledge",
"58": "John Raynor",
"24": "Delwyn Young"
};
function namePirate(jersey) {
return jersey + " is " + (pirates[jersey] ? pirates[jersey] : "not worn by a Pirate") + ".";
}
Alright, let's test this thing and see if it works. Hmm. Someone wearing 3 is playing second base now that Freddy Sanchez has been traded to the Giants for prospects. Let's find out who's on second (no, not first base) by passing 3
to namePirate()
. Remember from Chapter 3 that []
converts its operand to a string; therefore, you can pass either 3
or "3"
to namePirate()
. Inasmuch as numbers take fewer keystrokes, let's pass 3
. Then verify your work with Figure 4-16.
var pirates = { "77": "D.J. Carrasco", "53": "Brendan Donnelly", "29": "Octavio Dotel", "57": "Zach Duke", "48": "Javier Lopez", "28": "Paul Maholm", "34": "Daniel McCutchen", "47": "Evan Meek", "37": "Charlie Morton", "49": "Ross Ohlendorf", "62": "Hayden Penn", "43": "Jack Taschner", "41": "Ryan Doumit", "35": "Jason Jaramillo", "13": "Ronny Cedeno", "6": "Jeff Clement", "2": "Bobby Crosby", "3": "Akinori Iwamura", "15": "Andy LaRoche", "19": "Ryan Church", "46": "Garrett Jones", "22": "Andrew McCutchen", "85": "Lastings Milledge", "58": "John Raynor", "24": "Delwyn Young" }; function namePirate(jersey) { return jersey + " is " + (pirates[jersey] ? pirates[jersey] : "not worn by a Pirate") + "."; }namePirate(3);
// "3 is Akinori Iwamura."
Now that you know three ways to make conditionals run snappy, let's explore some ways to do so for loops.
So, which of the four loops is fastest?
No, not for
.
Not while
either.
Definitely not for in
. That one runs like a tortoise.
So then, it has to be do while
, right? Sorry, no.
Alright, so that was a trick question. for in
loops are the slowest. Sometimes they're seven times slower than the others. But while, for
, and do while
loops run neck and neck. So, of those three, code the one that you prefer for a particular job.
On the other hand, try to replace for in
loops with one of the other three whenever you can. That might seem to be a tall order, though. After all, for in
is the only loop that can enumerate members in an object, right?
Yes and no: for in
loops are the only one that can enumerate unknown object members, in other words, members you do not know the name of. However, you can enumerate members that you know the name of with one of the other three loops. Just put those names in an array and iterate through them. Taking this tack can speed up your code sevenfold. Yup, that's a lot! So, double-clear Firebug, and I'll show you how.
First, let's create an object to loop through. Hmm. OK, I thought of one: there are a couple of games remaining in the NHL regular season, and several players are vying for the Rocket Richard trophy, which is awarded annually to the top goal scorer. Right now, Sidney Crosby for my Pittsburgh Penguins has the most at 49. Let's create an object containing the top 20 goal scorers entering the final weekend of play:
var topTwenty = { "Crosby": 49, "Ovechkin": 48, "Stamkos": 48, "Marleau": 43, "Gaborik": 41, "Kovalchuk": 40, "Heatley": 39, "Semin": 39, "Parise": 37, "Burrows": 35, "Kopitar": 34, "Ryan": 34, "Carter": 33, "Nash": 33, "Iginla": 32, "Penner": 32, "Backstrom": 31, "Hornqvist": 30, "Jokinen": 30, "Kane": 30 };
Now with two games to go, most of those players have no chance whatsoever of passing Crosby and winning the Rocket Richard. So, not only would enumerating every topTwenty
member with a for in
loop be slow, it would also be irrational. Not wanting to appear ridiculous, let's create an array named rocketRichard
containing just the names of the four players that have a chance of finishing first in goals. While we're at it, let's create a note
string for later:
var topTwenty = { "Crosby": 49, "Ovechkin": 48, "Stamkos": 48, "Marleau": 43, "Gaborik": 41, "Kovalchuk": 40, "Heatley": 39, "Semin": 39, "Parise": 37, "Burrows": 35, "Kopitar": 34, "Ryan": 34, "Carter": 33, "Nash": 33, "Iginla": 32, "Penner": 32, "Backstrom": 31, "Hornqvist": 30,
"Jokinen": 30,
"Kane": 30
};
var rocketRichard = ["Ovechkin", "Crosby", "Marleau", "Stamkos"], note = "";
Now let's order the names in rocketRichard
by goals and then by name. To do so, we will use the sort()
method that every array, including rocketRichard
, defines so that you can order its elements. We will cover sort()
and other Array
methods in the next chapter. So for now, just type carefully!
var topTwenty = { "Crosby": 49, "Ovechkin": 48, "Stamkos": 48, "Marleau": 43, "Gaborik": 41, "Kovalchuk": 40, "Heatley": 39, "Semin": 39, "Parise": 37, "Burrows": 35, "Kopitar": 34, "Ryan": 34, "Carter": 33, "Nash": 33, "Iginla": 32, "Penner": 32, "Backstrom": 31, "Hornqvist": 30, "Jokinen": 30, "Kane": 30 }; var rocketRichard = ["Ovechkin", "Crosby", "Marleau", "Stamkos"], note = "";rocketRichard.sort(function(p1, p2) {
var d = topTwenty[p2] - topTwenty[p1];
if (d !== 0) {
return d;
} else {
return (p1 < p2) ? −1 : 1;
}
});
Now we can code either a for, while
, or do while
loop to indirectly enumerate members in topTwenty
by way of the member names in rocketRichard
. Let's go with a for
loop. This one will build up a string in note
that lists the top four goal scorers. Following the for
loop, let's clip off the ", "
from the end of note
with String.slice()
, a method we covered in Chapter 2. Then click Run
, and verify your work with Figure 4-17.
var topTwenty = { "Crosby": 49, "Ovechkin": 48, "Stamkos": 48, "Marleau": 43, "Gaborik": 41, "Kovalchuk": 40,
"Heatley": 39, "Semin": 39, "Parise": 37, "Burrows": 35, "Kopitar": 34, "Ryan": 34, "Carter": 33, "Nash": 33, "Iginla": 32, "Penner": 32, "Backstrom": 31, "Hornqvist": 30, "Jokinen": 30, "Kane": 30 }; var rocketRichard = ["Ovechkin", "Crosby", "Marleau", "Stamkos"], note = ""; rocketRichard.sort(function(p1, p2) { var d = topTwenty[p2] - topTwenty[p1]; if (d !== 0) { return d; } else { return (p1 < p2) ? −1 : 1; } });for (var i = 0; i < rocketRichard.length; i ++) {
note = note + rocketRichard[i] + ": " + topTwenty[rocketRichard[i]] + ", ";
}
note.slice(0, −2);
// "Crosby: 49, Ovechkin: 48, Stamkos: 48, Marleau: 43"
By the way, the previous sample illustrates a hidden perk in enumerating object members with a helper array. Doing so enables you to set the order members are enumerated.
Now that you know how to replace a for in
sloth with a for, while
, or do while
gazelle, let's explore a couple of ways to make those three snappier. Let's begin with our for
loop from the previous sample.
First, JavaScript can query local variables within a function or global variables outside of a function faster than object members such as length
—like two times as fast in Explorer 7 and 8. So, rather than query length
over and over in the boolean expression, i < rocketRichard.length
, let's do so one time in the initialization expression, replacing i = 0
with i = rocketRichard.length
. Second, it's faster to iterate over an array in reverse because doing so provides a way to combine the boolean expression with the increment or decrement expression. Therefore, omit the latter, and decrement the loop variable i
in the boolean expression. In turn, since we are now iterating over the array in reverse, we need to tweak the function literal we pass to sort()
so that rocketRichard
is ordered from fewest to most goals and then from Z
to A
. Make the following changes, click Run
, and then verify your work with Figure 4-18:
var topTwenty = { "Crosby": 49, "Ovechkin": 48, "Stamkos": 48, "Marleau": 43, "Gaborik": 41, "Kovalchuk": 40, "Heatley": 39,
"Semin": 39, "Parise": 37, "Burrows": 35, "Kopitar": 34, "Ryan": 34, "Carter": 33, "Nash": 33, "Iginla": 32, "Penner": 32, "Backstrom": 31, "Hornqvist": 30, "Jokinen": 30, "Kane": 30 } var rocketRichard = ["Ovechkin", "Crosby", "Marleau", "Stamkos"], note = ""; rocketRichard.sort(function(p1, p2) {var d = topTwenty[p1] - topTwenty[p2];
if (d !== 0) { return d; } else {return (p2 < p1) ? −1 : 1;
} }); for(var i = rocketRichard.length; i --; ) {
note = note + rocketRichard[i] + ": " + topTwenty[rocketRichard[i]] + ", "; } note.slice(0, −2); // "Crosby: 49, Ovechkin: 48, Stamkos: 48, Marleau: 43"
Note that in i --
, the --
operator is in the post-decrement position. Why does that matter? It matters for a couple of reasons. For one thing, if you wrote -- i
instead of i --
, JavaScript would never query the fourth element in rocketRichard
. For another, if rocketRichard
were empty, which is to say its length was 0
, then our for loop would never stop iterating. So, be sure that --
is in the post-decrement position!
Alright, now let's try rewriting our snappy for
loop as an equally snappy while
loop. Just move the initialization of i
to rocketRichard.length
to a separate statement prior to the while
loop, and decrement i
in the boolean expression. Make those two quick edits like so, and click Run
:
var topTwenty = { "Crosby": 49, "Ovechkin": 48, "Stamkos": 48, "Marleau": 43, "Gaborik": 41, "Kovalchuk": 40, "Heatley": 39, "Semin": 39, "Parise": 37, "Burrows": 35, "Kopitar": 34, "Ryan": 34, "Carter": 33,
"Nash": 33, "Iginla": 32, "Penner": 32, "Backstrom": 31, "Hornqvist": 30, "Jokinen": 30, "Kane": 30 } var rocketRichard = ["Ovechkin", "Crosby", "Marleau", "Stamkos"], note = ""; rocketRichard.sort(function(p1, p2) { var d = topTwenty[p1] - topTwenty[p2]; if (d !== 0) { return d; } else { return (p2 < p1) ? −1 : 1; } });var i = rocketRichard.length;
while (i --) {
note = note + rocketRichard[i] + ": " + topTwenty[rocketRichard[i]] + ", "; } note.slice(0, −2); // "Crosby: 49, Ovechkin: 48, Stamkos: 48, Marleau: 43"
Finally, let's rewrite our snappy while
loop as a snappy do while
. That makes sense since we want to run the loop at least one time. OK, four times. With that in mind, be sure to initialize i
to one less than rocketRichard.length
. Otherwise, note
will contain "undefined: undefined, Crosby: 49, Ovechkin: 48, Stamkos: 48, Marleau: 43"
since there is no element with an index of 4
in rocketRichard
. Edit the while
loop from the previous sample like so, and then click Run
:
var topTwenty = { "Crosby": 49, "Ovechkin": 48, "Stamkos": 48, "Marleau": 43, "Gaborik": 41, "Kovalchuk": 40, "Heatley": 39, "Semin": 39, "Parise": 37, "Burrows": 35, "Kopitar": 34, "Ryan": 34, "Carter": 33, "Nash": 33, "Iginla": 32, "Penner": 32, "Backstrom": 31, "Hornqvist": 30, "Jokinen": 30, "Kane": 30 } var rocketRichard = ["Ovechkin", "Crosby", "Marleau", "Stamkos"], note = ""; rocketRichard.sort(function(p1, p2) {
var d = topTwenty[p1] - topTwenty[p2]; if (d !== 0) { return d; } else { return (p2 < p1) ? −1 : 1; } });var i = rocketRichard.length - 1;
do {
note = note + rocketRichard[i] + ": " + topTwenty[rocketRichard[i]] + ", "; }while (i --);
note.slice(0, −2); // "Crosby: 49, Ovechkin: 48, Stamkos: 48, Marleau: 43"
So, there it is. We're done exploring conditionals and loops. By the way, Sidney Crosby and Steven Stamkos finished in a tie for most goals in the NHL with 51 apiece. They'll share the Rocket Richard trophy. Note to NHL: I'd suggest fewest empty net goals being the tie-breaker for the Rocket Richard in the future. Crosby had just one empty net goal this year, while Stamkos had five, including number 51!
In this chapter, you learned how to control flow with if
and switch
conditional statements and with while, do while, for
, and for in
looping statements. You also learned to make decisions, as JavaScript does, with boolean expressions. true
is a green light to do something, and false
is a red light not to.
In the next chapter, we will delve more deeply into objects and arrays, folder-like datatypes for organizing data. Take a well-deserved break, and I'll see you there!
18.119.133.160