Chapter 4. Controlling Flow

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.

Note

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.

Writing an if Condition

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.

Invoking mayfly.addToTally four times with the parameter 10

Figure 4-1. Invoking mayfly.addToTally four times with the parameter 10

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."

Appending an else Clause

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!

To Wrap or Not to Wrap

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!)

Coding Several Paths with the else if Idiom

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.

Simplifying things with the else if idiom

Figure 4-2. Simplifying things with the else if idiom

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."
Testing the default else path

Figure 4-3. Testing the default else path

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."

Controlling Flow with Conditional Expressions

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.

Note

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.

Replacing an if else statement with a ?: expression

Figure 4-4. Replacing an if else statement with a ?: expression

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.

Taking One of Several Paths with a Switch

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.

Coding a multiway branch with a switch statement

Figure 4-5. Coding a multiway branch with a switch statement

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.

Falling through from one case clause to another

Figure 4-6. Falling through from one case clause to another

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.

Within a function, you can replace break statements with return statements.

Figure 4-7. Within a function, you can replace break statements with return statements.

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.

Writing a while Loop

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.

Iterating over an array with a while loop

Figure 4-8. Iterating over an array with a while loop

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".

Aborting an Iteration but Not the Loop

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.

Culling elements containing "" or undefined from looseLeafTea

Figure 4-9. Culling elements containing "" or undefined from looseLeafTea

Replacing Break with Return in a Function

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.

Inside a function, you can abort a while loop with a return statement.

Figure 4-10. Inside a function, you can abort a while loop with a return statement.

Note

Chapter 6 covers functions more fully.

Writing a do while loop

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.

Rummaging through the spices array with a do while loop

Figure 4-11. Rummaging through the spices array with a do while loop

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!

Replacing a do while loop with a while loop takes some doing!

Figure 4-12. Replacing a do while loop with a while loop takes some doing!

Writing a for Loop

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."
Iterating over theFall with a for loop

Figure 4-13. Iterating over theFall with a for loop

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.

Tip

In the event that you have previously declared the loop variables, i and j in our sample, the var keyword is optional.

Enumerating Members with a for in Loop

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"]
Enumerating members with a for in loop

Figure 4-14. Enumerating members with a for in loop

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");
}

Snappier Conditionals

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."
Mapping jersey numbers to names for the Pittsburgh Pirates with a switch

Figure 4-15. Mapping jersey numbers to names for the Pittsburgh Pirates with a switch

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."
Replacing a switch with an object member query

Figure 4-16. Replacing a switch with an object member query

Now that you know three ways to make conditionals run snappy, let's explore some ways to do so for loops.

Snappier 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.

Replacing a for in sloth with a for gazelle

Figure 4-17. Replacing a for in sloth with a for gazelle

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!

Snappier for loop

Figure 4-18. Snappier for loop

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!

Summary

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!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.133.160