Chapter 3. Operators

Having data is no use if you can't do anything with it, so step forward operators. Operators do just what their name suggests; they operate on things. In the case of JavaScript, those things are the values stored in variables and any other value used in your script. For example, we'd use a division operator to divide one value by another value to return a result. There are some very straightforward operators in JavaScript and some not-so-straightforward ones; we'll cover all the useful ones and mention the others in passing, even though you will never use them.

To work with operators, you must first understand operator precedence, which is how JavaScript decides which operator to apply first if there is more than one in an expression, and operator associativity, which is the direction JavaScript reads through an expression when applying operators. We'll look at precedence and associativity as a general theory at the start of the chapter and then see how JavaScript operators really work. In the course of the chapter, you'll learn how to use operators effectively so that your expressions really zing.

Introducing Operator Precedence and Associativity

I've been running more than 100 miles per week for 14 years. To take in enough daily carbohydrates, I've become not so much a foodie as a doughie. Monday to Friday I run in the evening after work. So by the time I begin making dinner, it's pretty late. To save some time, I typically go with soda leavening rather than yeast.

One of my favorite late-night soda breads is leavened with soda, cream of tartar, and Organic Valley pourable vanilla yogurt, which is drinkable like kefir but tastes like yogurt:

  • 2 cups Bob's Red Mill organic whole wheat pastry flour

  • ⅓ cup blanched almonds, freshly ground into fine flour

  • 1 tsp. Saigon cinnamon

  • 2 tsp. minced lemon zest

  • ¼ tsp. sea salt

  • 1 tsp. soda

  • 1 tsp. cream of tartar

  • 1 cup Organic Valley pourable vanilla yogurt

  • 1 egg

  • 1 ¼ cups fresh wild blueberries

Remember from Chapter 1 that an expression is like a recipe whereby operators combine ingredients referred to as operands to create a value. So, with that analogy in mind, we might write an expression for the dough like so:

var dough = pastryFlour + almonds + saigonCinnamon + lemon + seaSalt + soda + tartar +
pourableVanillaYogurt + egg + wildBlueberries;

Insofar as that tells JavaScript blindly combine ingredients left to right, not only would the soda leavening fail, but the wild blueberries and lemon would get mulched by the mixer. We'd wind up with a very tart purple stone rather than a yummy loaf. I think I'd just go to bed hungry.

Rather than sequentially mulch everything, we want JavaScript to asynchronously take eight steps:

  1. Grind almonds into a very fine flour.

  2. Zest lemon.

  3. Sift flour, almonds, saigonCinnamon, lemon, and seaSalt.

  4. Whisk egg, pourableVanillaYogurt, and tartar.

  5. Whisk in soda to touch off alkaline and acid reaction.

  6. Mix now bubbling liquid with dry ingredients.

  7. Fold in wildBlueberries by hand.

  8. Knead dough to form gluten strands.

Okeydokey. But can JavaScript precisely follow a recipe? That is to say, how can it evaluate an expression asynchronously?

Yup, you bet your fern.

Operators have a couple of traits that determine the order of operation. Precedence is a value between 1 and 15 indicating an operator's priority. 1 is very low priority, and 15 is very high priority. So if an expression sequentially contains operators with a priority of 2, 14, 9, and 3, JavaScript does the 14 operation first, followed by the 9, then the 3, and finally the 2.

The other trait, associativity, tells JavaScript what to do when consecutive operators have the same priority. Just two options here. R associativity means do the one on the right first. Conversely, L associativity means do the one on the left first. So if an expression sequentially contains operators with priorities of 15, 15, 3, and 3 and L, L, R, and R associativity, JavaScript does the first 15, then the second 15, then the second 3, then the first 3.

With this in mind, let's create some fictitious operators for JavaScript to cook with:

  • G for grind—14 precedence, R associativity

  • Z for zest—14 precedence, R associativity

  • S for sift—11 precedence, L associativity

  • W for whisk—10 precedence, R associativity

  • F for fold—3 precedence, L associativity

  • K for knead—3 precedence, R associativity

So, we can now express dough to JavaScript with the following expression:

var dough = K flour S G almonds S saigonCinnamon S Z lemon S seaSalt W soda W tartar W
pourableVanillaYogurt W egg F wildBlueberries;

Let's unpick this in steps:

  1. All right, both G and Z have 14 priority. So, does JavaScript evaluate G almonds or Z lemon first? If two operators have equal priority but are not adjacent, JavaScript works left to right. Therefore, the almonds are ground, and then the lemon is zested.

  2. Sifting is next up because of S having 11 priority. But there are four S operations in a row, so JavaScript turns to associativity, which is L for sifting, to determine the order of operation. Therefore, JavaScript groups them as follows, evaluating them from left to right:

    (((flour S almond) S saigonCinnamon) S lemon) S seaSalt
  3. Insofar as W has 10 priority compared to 3 from K and F, JavaScript does the whisking next. There are four W operations in a row. Associativity, which is R for W, tells JavaScript to do those from right to left. Therefore, JavaScript groups the W operations as follows. Note that W having R associativity does not mean JavaScript whisks the right operand with the left operand.

    siftedIngredients W (soda W (tartar W (pourableVanillaYogurt W egg)))
  4. Now we have a K followed by an F, both of which have 3 priority. However, K has R associativity and F has L. Does JavaScript do the K or F first? Yup, the F folding. R followed by R or L means do the one on the right first. Conversely, L followed by L or R means do the one on the left first. So, JavaScript folds in wildBlueberries.

  5. Now we just have to K knead the dough for a minute or two to form bubble trapping gluten strands, shape the loaf, and slash a shallow X across the top, and we're done.

  6. Not quite. = is an operator, too. But it has a very low priority of 2. Only the, operator, which we'll explore a little later, has 1 priority. So, JavaScript does the G, Z, S, W, F, and K operations before assigning the loaf to the variable dough with the = operator.

The = operator having low priority and R associativity enables you to assign an expression to two or more variables. So if we wanted to make three loafs of wild blueberry swope rather than one, we would do so by chaining = operators:

var loaf1, loaf2, loaf3;
loaf1 = loaf2 = loaf3 = K flour S almond S saigonCinnamon S lemon S seaSalt W soda W tartar W
pourableVanillaYogurt W egg F wildBlueberries;

JavaScript would do the higher priority G, Z, S, W, F, and K operations first. Insofar as = has R associativity, JavaScript would first assign wild blueberry swope to loaf3, then to loaf2, and finally to loaf1.

This section has given you an idea how operator precedence works in principle, so let's look at the actual JavaScript operators, their precedence, and how to use them.

Using JavaScript Operators

As we explore JavaScript operators in this chapter, you might want to refer to the handy Table 3-1, which lists precedence and associativity values. Though you're probably thinking you'll never remember precedence and associativity for all those operators, it's actually very straightforward:

  • Unary operators have R associativity and 14 precedence, except for new, which has 15 precedence.

  • Binary assignment operators (=, *=, /=, %=, +=, −=) have R associativity and 2 precedence. All other binary operators have L associativity and varying precedence.

  • The ?: ternary operator has R associativity and 3 precedence.

Remembering value types for operands will take time. But a year from now, if not by the end of this book, those will be as simple to remember as associativity and precedence.

Table 3-1. Table 3-1. Precedence and Associativity for Essential JavaScript Operators

Operator

Precedence

Associativity

.

15

L

[]

15

L

()

15

L

new

15

R

++

14

R

--

14

R

!

14

R

delete

14

R

typeof

14

R

void

14

R

*

13

L

/

13

L

%

13

L

+

12

L

-

12

L

>

10

L

>=

10

L

<

10

L

<=

10

L

instanceof

10

L

in

10

L

==

9

L

!=

9

L

===

9

L

!==

9

L

&&

5

L

||

4

L

?:

3

R

=

2

R

*=

2

R

/=

2

R

%=

2

R

+=

2

R

-=

2

R

,

1

L

   

Note

JavaScript has a group of operators called bitwise operators. Bitwise operators are generally very fast in other programming languages, but they are very slow in JavaScript. So, no one does bit manipulation with JavaScript. Nor will we, so I won't cover them in this book.

Combining Math and Assignment Operations

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. In Chapter 1, we explored the + (addition), - (subtraction), * (multiplication), and / (division) operators, noting that + adds numbers but glues strings. Moreover, we explored how to save a value to a variable with the = (assignment) operator.

In the event that the left operand to +, -, *, or / is a variable, member, element, or parameter, you may replace the = operator and +, -, *, or / operator with a +=, -=, *=, or /= shortcut operator. Those do the math and assignment operations in one fell swoop. In Firebug, let's create the following dough object so that we have some values to explore +=, -=, *=, and /= with.

var dough = {
  pastryFlour: [1 + 3/4, "cup"],
  almondFlour: [1/3, "cup"],
  saigonCinnamon: [1, "tsp"],
  mincedLemonZest: [2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  pourableVanillaYogurt: [1, "cup"],
  egg: [1],
  wildBlueberries: [1 + 1/4, "cup"]
};

Say I want to triple the recipe. To do so, we could pass each element and 3 to the *= operator like so in Firebug. Then query the new values of a couple of elements, verifying your work with Figure 3-1:

var dough = {
  pastryFlour: [1 + 3/4, "cup"],
  almondFlour: [1/3, "cup"],
  saigonCinnamon: [1, "tsp"],
  mincedLemonZest: [2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  pourableVanillaYogurt: [1, "cup"],
  egg: [1],
  wildBlueberries: [1 + 1/4, "cup"]
};
dough.pastryFlour[0] *= 3;
dough.almondFlour[0] *= 3;
dough.saigonCinnamon[0] *= 3;
dough.mincedLemonZest[0] *= 3;
dough.seaSalt[0] *= 3;
dough.soda[0] *= 3;
dough.tartar[0] *= 3;
dough.pourableVanillaYogurt[0] *= 3;
dough.egg[0] *= 3;
dough.wildBlueberries[0] *= 3;
dough.pastryFlour[0];
// 5.25
dough.pourableVanillaYogurt[0];
// 3
Doubling elements with *=

Figure 3-1. Doubling elements with *=

The following is what we would have had to type in if we didn't have *=, so *= saved us from having to separately key in = and * as Figure 3-2 displays.

var dough = {
  pastryFlour: [1 + 3/4, "cup"],
  almondFlour: [1/3, "cup"],
  saigonCinnamon: [1, "tsp"],
  mincedLemonZest: [2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  pourableVanillaYogurt: [1, "cup"],
  egg: [1],
  wildBlueberries: [1 + 1/4, "cup"]
};
dough.pastryFlour[0] = dough.pastryFlour[0] * 3;
dough.almondFlour[0] = dough.almondFlour[0] * 3;
dough.saigonCinnamon[0] = dough.saigonCinnamon[0] * 3;
dough.mincedLemonZest[0] = dough.mincedLemonZest[0] * 3;
dough.seaSalt[0] = dough.seaSalt[0] * 3;
dough.soda[0] = dough.soda[0] * 3;
dough.tartar[0] = dough.tartar[0] * 3;
dough.pourableVanillaYogurt[0] = dough.pourableVanillaYogurt[0] * 3;
dough.wildBlueberries[0] = dough.wildBlueberries[0] * 3;
dough.pastryFlour[0];
// 5.25
dough.pourableVanillaYogurt[0];
// 3
Tripling elements the hard way with = and *

Figure 3-2. Tripling elements the hard way with = and *

Note

The for in loop, which we'll cover in Chapter 4, would further eliminate drudgery.

Like the * multiplication operator, *= converts its operands to numbers if necessary. So if we inadvertently defined some elements with strings, JavaScript would convert those to numbers so that the multiplication part of *= will work. Insofar as *= does assignment too, values are permanently converted from strings to numbers

Like *=, -= and /= convert their operands to numbers, too. However, += favors gluing strings over adding numbers just as + does. So if one operand is a string and the other is a number, boolean, object, null, or undefined, += converts the nonstring to a string. Therefore, += will do addition only if one operand is a number and the other is not a string.

To illustrate, try doubling pourableVanillaYogurt with += rather than *=. As Figure 3-3 displays, += converts its left operand 1 to 1 and then glues it to its right operand to create the string 11.

var dough = {
  pastryFlour: [1 + 3/4, "cup"],
  almondFlour: [1/3, "cup"],
  saigonCinnamon: [1, "tsp"],
mincedLemonZest: [2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  pourableVanillaYogurt: [1, "cup"],
  egg: [1],
  wildBlueberries: [1 + 1/4, "cup"]
};
dough.pourableVanillaYogurt[0] += "1";
dough.pourableVanillaYogurt[0];
// "11"
+= only does concatenation if its right operand is not a number.

Figure 3-3. += only does concatenation if its right operand is not a number.

Remember that every JavaScript value can be converted to a boolean or string, but not to a number. Not one you can do math with anyway. Most non-numbers convert to the "not a number" literal NaN. Moreover, the return value of any math operation containing a NaN operand will always be NaN.

Insofar as JavaScript returns NaN whenever a value cannot be converted to a number, +=, -=, *=, and /= may overwrite a variable, member, element, or parameter with NaN. To illustrate the point, try the following sample, verifying your work with Figure 3-4. Insofar as we forgot to refine our query with the [] operator, JavaScript multiplies the array [1 + 1/4, "cup"] by 3. Therefore, [1 + 1/4, "cup"] is converted to the number NaN and multiplied by 3. So, the array in dough.wildBlueberries is overwritten with the return value of NaN * 3, which of course is NaN.

var dough = {
  pastryFlour: [1 + 3/4, "cup"],
  almondFlour: [1/3, "cup"],
  saigonCinnamon: [1, "tsp"],
  mincedLemonZest: [2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  pourableVanillaYogurt: [1, "cup"],
  egg: [1],
  wildBlueberries: [1 + 1/4, "cup"]
};
dough.wildBlueberries *= 3;
dough.wildBlueberries;
// NaN
Failing to refine our query with [] results in the array being overwritten with NaN.

Figure 3-4. Failing to refine our query with [] results in the array being overwritten with NaN.

OK, so what would happen if we try to query the first element in dough.wildBlueberries now that that's NaN, not an array?

Here's a hint: see Chapter 2.

Yup, if you query NaN with the . or [] operators, JavaScript creates a number wrapper for NaN. Since that number wrapper does not contain any elements, querying its first element returns undefined, as the following sample and Figure 3-5 illustrate. Note that + converts undefined to "undefined" prior to gluing it to "There are". Note too that finding and fixing coding typos like this are what debugging primarily entails.

var dough = {
  pastryFlour: [1 + 3/4, "cup"],
  almondFlour: [1/3, "cup"],
  saigonCinnamon: [1, "tsp"],
  mincedLemonZest: [2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  pourableVanillaYogurt: [1, "cup"],
  egg: [1],
  wildBlueberries: [1 + 1/4, "cup"]
};
dough.wildBlueberries *= 3;
"There are " + dough.wildBlueberries[0] + " cups of wild blueberries in the dough.";
// "There are undefined cups of wild blueberries in the dough."
JavaScript converts NaN to a wrapper object if you query it with the . or [] operator.

Figure 3-5. JavaScript converts NaN to a wrapper object if you query it with the . or [] operator.

Finally, it's vital to remember that the left operand to +=, -=, *=, or /= must be a variable, member, element, or parameter. Otherwise, JavaScript will slap you upside the head by returning a SyntaxError noting, "invalid assignment left-hand side", which means the left operand must be one of these things.

3 -= 1;
//  SyntaxError: invalid assignment left-hand side { message="invalid assignment left-hand
side" }
"blue" += "berries";
//  SyntaxError: invalid assignment left-hand side { message="invalid assignment left-hand
side" }

Incrementing or Decrementing Values

In the event that you are adding or subtracting 1 from a value with += or -=, you may even more succinctly do so with the ++ increment and -- decrement unary operators, which convert it to a number if necessary. Note that although += may do addition or concatenation, ++ always does addition.

So in Firebug, let's double saigonCinnamon with ++ and halve mincedLemonZest with --, verifying our work with Figure 3-6.

var dough = {
  pastryFlour: [1 + 3/4, "cup"],
  almondFlour: [1/3, "cup"],
  saigonCinnamon: [1, "tsp"],
  mincedLemonZest: [2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  pourableVanillaYogurt: [1, "cup"],
  egg: [1],
  wildBlueberries: [1 + 1/4, "cup"]
};
dough.saigonCinnamon[0] ++;
dough.mincedLemonZest[0] --;
dough.saigonCinnamon[0];
// 2
dough.mincedLemonZest[0];
// 1
++ is a shortcut for += 1, and -- is a shortcut for -= 1.

Figure 3-6. ++ is a shortcut for += 1, and -- is a shortcut for -= 1.

Note that ++ and -- may appear in the prefix or postfix positions, that is to say, to the left or right of their operand. The prefix and postfix positions are irrelevant to the operand's new value: ++ will add 1 to its operand, and -- will subtract 1 from its operand either way. However, the return value of the ++ and -- will differ. In the prefix position, ++ returns the unincremented value, and -- returns the undecremented value. Conversely, in the postfix position, ++ returns the incremented value, and -- returns the decremented value.

To illustrate the point, try the following sample, which has ++ and -- in both the prefix and postfix positions. As Figure 3-7 displays, the return values of the ++ and -- expressions differ in the prefix and postfix positions, but not the new member values. Those are always incremented by ++ or decremented by --. Remember to stop and click Run prior to each comment, as explained in the preface.

var dough = {
  pastryFlour: [1 + 3/4, "cup"],
  almondFlour: [1/3, "cup"],
  saigonCinnamon: [1, "tsp"],
  mincedLemonZest: [2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  pourableVanillaYogurt: [1, "cup"],
  egg: [1],
  wildBlueberries: [1 + 1/4, "cup"]
};
dough.saigonCinnamon[0] ++;
// 1
++ dough.mincedLemonZest[0];
// 3
dough.wildBlueberries[0] --;
// 1.25
-- dough.pastryFlour[0];
// .75
dough.saigonCinnamon[0];
// 2
dough.mincedLemonZest[0];
// 3
dough.wildBlueberries[0];
// .25
dough.pastryFlour[0];
// .75
The return value for ++ differs in the prefix and postfix position.

Figure 3-7. The return value for ++ differs in the prefix and postfix position.

The differing return values of ++ and -- in the prefix and postfix positions provide additional flexibility in limiting the number of roundabouts JavaScript takes of a for, while, or dowhile loop. Those are covered in Chapter 4.

Testing for Equality

Scones are particularly fast to make—maybe 20 minutes from mixing the dough to putting the scones on the cooling rack. Hazelnut cream scones are among my favorites. I make those with either Organic Valley heavy whipping cream or half & half. Those are deliciously sweet and creamy because of Organic Valley pasturing their cows rather than feeding them corn.

Note that for cream scones the leavening bubbles derive from soda reacting with cream of tartar. That is to say, heavy whipping cream or half & half contain no lactic acid. Note that the ratio of soda tsp. to flour cups is 1:4 for scones compared to 1:2 for bread; we don't want as much lift. Note too that the ratio of soda to tartar is 1:2. Although tartar is tasteless, soda is bitter, so we want to ensure there's soda left in the dough following the leavening reaction.

var dough = {
  pastryFlour: [1 + 2/3, "cup"],
  hazelnutFlour: [1/3, "cup"],
  butter: [3, "tbs"],
  sugar: [2, "tbs"],
  seaSalt: [1/4, "tsp"],
  soda: [1/2, "tsp"],
  tartar: [1, "tsp"],
  heavyWhippingCream: [1, "cup"],
  currants: [1/3, "cup"]
};

If you're not familiar with making scones, you combine the ingredients in dough like so:

  • Sift pastryFlour, hazelnutFlour, sugar, and seaSalt.

  • Grate cold butter into flour and work into course meal with pastry blender.

  • Sequentially whisk heavyWhippingCream, orangeJuice, mincedLemonZest, tartar, and soda.

  • Make a well in center of flour, pour in wet ingredients and currants, and quickly blend with rubber spatula to form soft, slightly moist dough.

  • Turn dough onto a well-floured work surface and roll into round 9 inches in diameter (1/2-inch thick).

  • Slice into 8 wedges with sharp, floured knife. Cut away and discard center with 3-inch biscuit cutter.

  • Bake slightly separated wedges on parchment lined sheet for 12 minutes at 425°F.

  • Cool on wire rack for ten minutes.

Note that rolling dough for scones or other pastries can be difficult. So if you're a budding doughie, I recommend buying a marble pastry board. Insofar as marble stays cooler than room temperature, it's easier to roll dough on.

Anyway, say I'd like to compare my guess at how much of an ingredient to add to the dough to what the recipe calls for. I could do so by way of the === identity operator.=== returns true if its operands evaluate to identical values and false if not. That boolean verdict derives from the following protocol:

  • If the values are of different types, return false.

  • If both values are of the undefined type, return true.

  • If both values are of the null type, return true.

  • If both values are of the number type and one or both are NaN, return false. Otherwise, return true if the numbers are the same and false if not.

  • If both values are of the string type and have the same sequence and number of characters, return true. Otherwise, return false.

  • If both values are of the boolean type, return true if both are false or both are true. Otherwise, return false.

  • If both memory addresses refer to the same location, return true. Otherwise, return false.

Did that final step go over your head? Don't worry, we'll explore comparing memory addresses, which along with pointers are more generally referred to as references, in Chapter 5. For now just know that undefined, null, numbers, strings, and booleans are compared by value while objects, arrays, and functions are compared by memory address, which is to say by reference (which all implies that the two sets are stored in different ways, more of which in Chapter 5).

Note that === does not do datatype conversion, but its predecessor, the == equality operator, does. Insofar as == can tell you whether only one expression is not entirely unlike another, savvy JavaScript programmers frown upon its use. So since you're a clean slate, I won't teach you bad habits here.

Enough with the theory of ===; in Firebug, try the following sample, verifying your work with Figure 3-8:

var dough = {
  pastryFlour: [1 + 2/3, "cup"],
  hazelnutFlour: [1/3, "cup"],
  butter: [3, "tbs"],
  sugar: [2, "tbs"],
  seaSalt: [1/4, "tsp"],
  soda: [1/2, "tsp"],
  tartar: [1, "tsp"],
  heavyWhippingCream: [1, "cup"],
  currants: [1/3, "cup"]
};
dough.heavyWhippingCream[0] === 2/3;
// false
dough.currants[0] === dough.hazelnutFlour[0];
// true
dough.hazelnutFlour[0] * 5 === dough.pastryFlour[0];
// true
dough.soda[0] / dough.tartar[0] === 1;
// false
Verifying the amount of cream of tartar and soda with ===

Figure 3-8. Verifying the amount of cream of tartar and soda with ===

Testing for Inequality

Frequently you will want to test for inequality, which is to say for a value you don't want an expression to return. To do so, we might invert the boolean returned by === with the ! logical not operator. ! flips true to false and false to true. However, ! has 14 priority and === 9. To trump the 14 with the 9, we would wrap the === expression in the () grouping operator as the following sample and Figure 3-9 illustrate.

var dough = {
  pastryFlour: [1 + 2/3, "cup"],
  hazelnutFlour: [1/3, "cup"],
  butter: [3, "tbs"],
  sugar: [2, "tbs"],
  seaSalt: [1/4, "tsp"],
  soda: [1/2, "tsp"],
  tartar: [1, "tsp"],
  heavyWhippingCream: [1, "cup"],
  currants: [1/3, "cup"]
};
! (dough.heavyWhippingCream[0] === 2/3);
// true
! (dough.currants[0] === dough.hazelnutFlour[0]);
// false
! (dough.hazelnutFlour[0] * 5 === dough.pastryFlour[0]);
// false
! (dough.soda[0] / dough.tartar[0] === 1);
// true
Querying JavaScript as to whether two expressions are not equal

Figure 3-9. Querying JavaScript as to whether two expressions are not equal

As a shortcut for comparing two expressions for equality with === and flipping the verdict with !, JavaScript provides the !== operator. !== first runs through the === protocol and then does a logical not on the verdict. So if === would return true, !== returns false, and if === would false, !== returns true. So !== is quite the contrarian!

The important thing to remember is that both === and !== run through the same protocol; !== just inverts the verdict. It's sort of like a judge sending you to jail when the jury says innocent and letting you go free when the jury says guilty. Wouldn't that be something?

Let's simplify the previous sample with !==, verifying our work with Figure 3-10:

var dough = {
  pastryFlour: [1 + 2/3, "cup"],
  hazelnutFlour: [1/3, "cup"],
  butter: [3, "tbs"],
  sugar: [2, "tbs"],
  seaSalt: [1/4, "tsp"],
  soda: [1/2, "tsp"],
  tartar: [1, "tsp"],
  heavyWhippingCream: [1, "cup"],
  currants: [1/3, "cup"]
};
dough.heavyWhippingCream[0] !== 2/3;
// true
dough.currants[0] !== dough.hazelnutFlour[0];
// false
dough.hazelnutFlour[0] * 5 !== dough.pastryFlour[0];
// false
dough.soda[0] / dough.tartar[0] !== 1;
// true
!== does a logical not on the verdict of the === protocol.

Figure 3-10. !== does a logical not on the verdict of the === protocol.

Comparing Objects, Arrays, and Functions

Thus far we've compared strings and numbers with ===. So, JavaScript never made it to the final step where memory addresses rather than values are compared. Referring to the === protocol, there's no step where JavaScript tediously compares object members, array elements, or function bodies. Not an error on my part. JavaScript never wastes time and memory doing that. Moreover, if you compare an array to a function, === does not return false (or !==true) because of those being different subtypes. Rather, the boolean verdict simply derives from the array and function being in different locations in memory. Remember, JavaScript stores string, number, boolean, undefined, and null values in a different way to object, array, and function values (as we inferred earlier in the chapter).

Now don't be rolling your eyes at me. It's vital to get this point. So, let's compare some of the identical arrays in the following dough object representing the recipe for another of my favorite scones, hazelnut cherry, with === and !== like so in Firebug. As Figure 3-11 displays, === returns false and !== returns true for separate but identical arrays.

var dough = {
  pastryFlour: [1 + 2/3, "cup"],
  hazelnutFlour: [1/3, "cup"],
  butter: [3, "tbs"],
  sugar: [2, "tbs"],
  seaSalt: [1/4, "tsp"],
  soda: [1/2, "tsp"],
  tartar: [1, "tsp"],
  heavyWhippingCream: [1, "cup"],
  currants: [1/3, "cup"]
};
dough.pastryFlour === [1 + 2/3, "cup"];
// false
dough.currants !== [1/3, "cup"];
// true
Separate but identical arrays are not equal.

Figure 3-11. Separate but identical arrays are not equal.

Separate but identical values of the object type or array and function subtypes are never equal. Like you and me, those are equal only to themselves, as the following sample and Figure 3-12 display:

var dough = {
  pastryFlour: [1 + 2/3, "cup"],
  hazelnutFlour: [1/3, "cup"],
  butter: [3, "tbs"],
  sugar: [2, "tbs"],
  seaSalt: [1/4, "tsp"],
  soda: [1/2, "tsp"],
tartar: [1, "tsp"],
  heavyWhippingCream: [1, "cup"],
  currants: [1/3, "cup"]
};
dough === {
  pastryFlour: [1 + 2/3, "cup"],
  hazelnutFlour: [1/3, "cup"],
  butter: [3, "tbs"],
  sugar: [2, "tbs"],
  seaSalt: [1/4, "tsp"],
  soda: [1/2, "tsp"],
  tartar: [1, "tsp"],
  heavyWhippingCream: [1, "cup"],
  currants: [1/3, "cup"]
};
// false
dough === dough;
// true
dough.pastryFlour === [1 + 2/3, "cup"];
// false
dough.pastryFlour === dough.pastryFlour;
// true
An object, array, or function is only equal to itself.

Figure 3-12. An object, array, or function is only equal to itself.

Furthermore, separate but otherwise identical object, array, or function literals are never equal inasmuch as JavaScript saves those to different locations in memory. To illustrate the point, try the following sample in Firebug:

[1 + 2/3, "cup"] === [1 + 2/3, "cup"];
// false

We'll more fully explore comparing by value or reference in Chapter 5. It's time to move on to determining the relative order of numbers and strings with the > greater and < less operators.

Determining Whether One Number or String Is Greater Than Another

If you were to strain most of the watery whey from a 1 quart tub of whole milk yogurt, which is 4 percent cream, you would have 1 2/3 cups of delicious full cream Greek yogurt, yiaourti, which is 10 percent cream. Mmmh. Moreover, if you were to strain all of the watery whey, you would have 1 1/3 cups of yogurt cheese, a healthier cream cheese. To do so, simply put a sieve lined with eight layers of cheesecloth overtop a bowl in the fridge, dump in the yogurt, and let the whey drain for 6 hours to make yiaourti or 12 hours to make yogurt cheese. However, Greek yogurt and yogurt cheese makers are inexpensive; I bought mine for $21 from www.kingarthurflour.com, but Target has one for $16.

Insofar as yogurt cheese retains the lactic acid from the yogurt, you can leaven dough with it in place of sour cream. One of my favorite apple cakes does so. Plus the icing is made with whipped yogurt cheese rather than cream cheese. The recipe can be represented by nesting dough and icing objects within the following cake object. Note that you would do the following:

  • Strain 2 2/3 cups of watery whey from 4 cups of Stonyfield cream-top yogurt to create 1 1/3 cups yogurt cheese.

  • Shred 1 2/3 cups of tart Granny Smith apples.

  • Sift Red Mill organic whole wheat pastry flour with the nutmeg and cinnamon.

  • Whisk 2 eggs, with 2/3 cup yogurt cheese and 1/3 cup pure maple syrup.

  • Dissolve the tartar and soda in the liquid.

  • Immediately mix liquid with sifted dry ingredients.

  • Fold in the shredded Granny Smith and chopped pecans.

  • Bake for 40 minutes at 350°F.

  • To make icing, whisk until creamy the remaining 2/3 cup yogurt cheese with 1 1/3 tbs (4 tsp) pure maple syrup and 2 tsp. ground pecans.

  • Wait for cake to cool before topping with icing.

var cake = {
  dough: {
    organicPastryFlour: [1 + 1/2, "cup"],
    freshlyGroundNutmeg: [1/4, "tsp"],
    saigonCinnamon: [1/2, "tsp"],
    soda: [1, "tsp"],
    tartar: [1, "tsp"],
    egg: [2],
    yogurtCheese: [2/3, "cup"],
    pureMapleSyrup: [1/3, "cup"],
    shreddedGrannySmith: [1 + 2/3, "cup"],
    choppedPecans: [1/2, "cup"]
  },
  icing: {
    yogurtCheese: [2/3, "cup"],
pureMapleSyrup: [1 + 1/3, "tbs"],
    groundPecans: [2, "tsp"]
  }
};

What if we want to know whether there's more pastry flour or shredded Granny Smiths in the dough? Neither === nor !== would be of any help. Instead, we'd compare those ingredients with the > greater than operator, which like === and !== returns a boolean verdict—true if its first operand is greater than its second operand, and false if not. Try comparing some members with the > operator, verifying your work with Figure 3-13:

var cake = {
  dough: {
    organicPastryFlour: [1 + 1/2, "cup"],
    freshlyGroundNutmeg: [1/4, "tsp"],
    saigonCinnamon: [1/2, "tsp"],
    soda: [1, "tsp"],
    tartar: [1, "tsp"],
    egg: [2],
    yogurtCheese: [2/3, "cup"],
    pureMapleSyrup: [1/3, "cup"],
    shreddedGrannySmith: [1 + 2/3, "cup"],
    choppedPecans: [1/2, "cup"]
  },
  icing: {
    yogurtCheese: [2/3, "cup"],
    pureMapleSyrup: [1 + 1/3, "tbs"],
    groundPecans: [2, "tsp"]
  }
};
cake.dough.organicPastryFlour[0] > cake.dough.shreddedGrannySmith[0];
// false
cake.dough.choppedPecans[0] > cake.dough.pureMapleSyrup[0];
// true
cake.dough.freshlyGroundNutmeg[0] > cake.dough.saigonCinnamon[0];
// false
cake.icing.yogurtCheese[0] > cake.dough.yogurtCheese[0];
// false
Determining whether one number is greater than another with the > operator

Figure 3-13. Determining whether one number is greater than another with the > operator

In addition to comparing numbers, > is sometimes used to compare strings. However, it does so numerically by the Unicode encoding for each character. Uppercase letters are greater than lowercase characters. Therefore, you want to invoke toLowerCase() on both operands to get an alphabetical comparison as Listing 3-1 displays. Note that we explored toLowerCase() and other string methods in Chapter 2.

Example 3-1. Comparing Strings Alphabetically with the > Operator

"apple" > "Granny Smith";
// false
"apple".toLowerCase() > "Granny Smith".toLowerCase();
// true

Determining Whether One Number or String Is Less Than Another

Now what if we want to do the inverse? In other words, we want to determine whether there's less pure maple syrup than ground pecans in the dough.

Hmm.

Want to take a guess?

Yup, turn the > around and you have the < less than operator, which tells you whether its first operand is less than its second operand. Let's muck around with < in Firebug, verifying our work with Figure 3-14.

var cake = {
  dough: {
    organicPastryFlour: [1 + 1/2, "cup"],
    freshlyGroundNutmeg: [1/4, "tsp"],
saigonCinnamon: [1/2, "tsp"],
    soda: [1, "tsp"],
    tartar: [1, "tsp"],
    egg: [2],
    yogurtCheese: [2/3, "cup"],
    pureMapleSyrup: [1/3, "cup"],
    shreddedGrannySmith: [1 + 2/3, "cup"],
    choppedPecans: [1/2, "cup"]
  },
  icing: {
    yogurtCheese: [2/3, "cup"],
    pureMapleSyrup: [1 + 1/3, "tbs"],
    groundPecans: [2, "tsp"]
  }
};
cake.dough.organicPastryFlour[0] < cake.dough.shreddedGrannySmith[0];
// true
cake.dough.choppedPecans[0] < cake.dough.pureMapleSyrup[0];
// false
cake.dough.freshlyGroundNutmeg[0] < cake.dough.saigonCinnamon[0];
// true
cake.icing.yogurtCheese[0] < cake.dough.yogurtCheese[0];
// false
Determining whether one number is less than another with the > operator

Figure 3-14. Determining whether one number is less than another with the > operator

Greater Than or Equal to, Less Than or Equal to

OK, in both the > and < samples, comparing the yogurt cheese in the dough and icing returned false since those are equal—2/3 cup. So if we wanted to know whether one member is greater than or equal to (or less than or equal to) another member, > and < wouldn't be of any help.

Or so it would seem: Saying "not less than" is the same as saying "greater than or equal to," and saying "not greater than" is the same as saying "less than or equal to." So, we'll just flip the boolean verdict of < with ! to do a "greater than or equal to" operation. Conversely, flipping the verdict of > with ! will do a "less than or equal to" operation. So, let's have at it in Firebug, verifying our work with Figure 3-15. Note that > and < have 10 priority, so we need to trump the ! operator's 14 priority by wrapping the > or < expression in parentheses:

var cake = {
  dough: {
    organicPastryFlour: [1 + 1/2, "cup"],
    freshlyGroundNutmeg: [1/4, "tsp"],
    saigonCinnamon: [1/2, "tsp"],
    soda: [1, "tsp"],
    tartar: [1, "tsp"],
    egg: [2],
    yogurtCheese: [2/3, "cup"],
    pureMapleSyrup: [1/3, "cup"],
    shreddedGrannySmith: [1 + 2/3, "cup"],
    choppedPecans: [1/2, "cup"]
  },
  icing: {
    yogurtCheese: [2/3, "cup"],
    pureMapleSyrup: [1 + 1/3, "tbs"],
    groundPecans: [2, "tsp"]
  }
};
! (cake.icing.yogurtCheese[0] > cake.dough.yogurtCheese[0]);
// true
! (cake.icing.yogurtCheese[0] < cake.dough.yogurtCheese[0]);
// true
Inverting the boolean verdict of > or < with !

Figure 3-15. Inverting the boolean verdict of > or < with !

Just as JavaScript provides !== as a shortcut to flipping the boolean verdict of === with !, it provides >= as a shortcut to flipping the boolean verdict of < with !, and <= as a shortcut to flipping the boolean verdict of > with !. Just remember that neither >= nor <= tests for equality with the === operator. Rather, >= does a "not less than" operation while <= does a "not greater than" operation. Try comparing some ingredients with >= and <=, verifying your work Figure 3-16:

var cake = {
  dough: {
    organicPastryFlour: [1 + 1/2, "cup"],
    freshlyGroundNutmeg: [1/4, "tsp"],
    saigonCinnamon: [1/2, "tsp"],
    soda: [1, "tsp"],
    tartar: [1, "tsp"],
    egg: [2],
    yogurtCheese: [2/3, "cup"],
    pureMapleSyrup: [1/3, "cup"],
    shreddedGrannySmith: [1 + 2/3, "cup"],
    choppedPecans: [1/2, "cup"]
  },
  icing: {
    yogurtCheese: [2/3, "cup"],
    pureMapleSyrup: [1 + 1/3, "tbs"],
    groundPecans: [2, "tsp"]
  }
};
cake.icing.yogurtCheese[0] <= cake.dough.yogurtCheese[0];
// true
cake.icing.yogurtCheese[0] >= cake.dough.yogurtCheese[0];
// true
cake.dough.organicPastryFlour[0] <= cake.dough.shreddedGrannySmith[0];
// true
cake.dough.choppedPecans[0] >= cake.dough.pureMapleSyrup[0];
// true
Comparing ingredients with >= and <=

Figure 3-16. Comparing ingredients with >= and <=

Note that >, <, >=, and <= can only compare numbers or strings, so JavaScript converts operands of other value types as follows:

  • Convert objects to numbers if possible. Otherwise, convert them to strings.

  • If both operands are now strings, compare them by their Unicode encodings.

  • Convert any string, boolean, null, or undefined operand to a number, that is, true to 1, false to 0, null to 0, and undefined to NaN, and strings to a number or NaN. Both operands will now be numbers, so compare them mathematically unless one or both are NaN, in which case return false no matter what.

So if one or both operands are of the number, boolean, null, or undefined value type, then >, <, >=, and <= will always compare operands mathematically. That is to say, string comparison is done only when both operands are strings or objects that cannot be converted to numbers.

Creating More Complex Comparisons

Brown Cow, my favorite organic cream-top yogurt, comes in a maple flavor that is just yogurt sweetened with pure maple syrup. Comparing labels of maple and plain Brown Cow to pure maple syrup, I calculated that there's 1 tbsp. pure maple syrup per cup of maple Brown Cow. So if I'm making boysenberry muffins and don't have maple Brown Cow in the fridge, I'll sweeten 1 1/2 cups plain Brown Cow or Stonyfield cream-top with 1 /12 tbs pure maple syrup. Note that boysenberries were created by horticulturist Charles Boysen in 1923 from several varieties of blackberries, raspberries, and loganberries. Note too that Brown Cow is a Stonyfield subsidiary, so their cream-top yogurts taste fairly similar.

The following muffin object represents the recipe for boysenberry muffins. I buy the oat and barley flours from www.kingarthurflour.com. However, you can replace those with the Bob's Red Mill organic whole wheat pastry flour we've been making dough with. To make the muffins, you would do the following:

  1. Sift oatFlour, barleyFlour, pastryFlour, sugar, freshlyGroundNutmeg, saigonCinnamon, and seaSalt.

  2. Sequentially whisk mapleBrownCow, tartar, and soda.

  3. Immediately mix in sifted dry ingredients.

  4. Fold in boysenberries and choppedPecans.

  5. Fill muffin cups 2/3 full.

  6. Bake for 20 minutes at 375°F. However, this will vary depending on the size of the muffin cups.

  7. Cool on wire rack for 10 minutes.

var muffin = {
  oatFlour: [1/3, "cup"],
  barleyFlour: [1/3, "cup"],
  pastryFlour: [1 + 1/3, "cup"],
  freshlyGroundNutmeg: [1/4, "tsp"],
  saigonCinnamon: [1/2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  mapleBrownCow: [1 + 1/2, "cup"],
  boysenberries: [2, "cup"],
  choppedPecans: [1/3, "cup"]
};

OK, say we'd like to do a more complex comparison. Say verify that one of two comparisons are valid. Or that both of them are. Maybe even that two of five comparisons are valid. Could we do so?

Yup. In addition to being able to say "not" with the ! logical not operator, we can say "or" with the || logical or operator and "and" with the && logical and operator. Both || and && return one of their two operands relative to the boolean their first operand evaluates or converts to. If the first operand evaluates to or converts to true:

  • || returns its first operand.

  • && returns its second operand.

On the other hand, if the first operand evaluates to or converts to false:

  • || returns its second operand.

  • && returns its first operand.

Note that && and || only convert their first operand to a boolean in order to determine which operand to return. In other words, if || or && choose to return their first operand, it is the unconverted value that is returned.

The odd way in which || and && choose their return value is the basis for boolean algebra. Scary term, but not to worry. Doing algebra with booleans is simpler than with numbers. Here's how it works.

  • The return value for || will convert to true if its first or second operand or both evaluate or convert to true. Otherwise, the return value for || will convert to false.

  • The return value for && will convert to true if its first and second operand evaluate or convert to true. Otherwise, the return value for || will convert to false.

That was simple, but why would you want to do boolean algebra? For one thing, the operators we explored for comparing expressions, ===, !==, ==, !=, >, <, >=, and <=, all return a boolean verdict. So, boolean algebra provides a way to express complex comparisons. For another, insofar as objects and functions convert to true and undefined converts to false, boolean algebra is the foundation for DOM feature testing, which we'll explore in gory detail in the final four chapters of this book.

Saying or With ||

Insofar as ===, !==, >, <, >=, and <= return a boolean verdict and || returns one of its operands, you can use || to do boolean algebra on two comparison expressions. If one of two comparisons return true or both of them do, || will return true. That is to say, || will only return false if both comparisons return false. So, we can test if one or both comparisons are valid like so. Note that it's fine to add a new line between a binary operator like || and its second operand. Be sure to click Run prior to each comment. So four times overall. Then verify your work with Figure 3-17.

var muffin = {
  oatFlour: [1/3, "cup"],
  barleyFlour: [1/3, "cup"],
  pastryFlour: [1 + 1/3, "cup"],
  freshlyGroundNutmeg: [1/4, "tsp"],
  saigonCinnamon: [1/2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  mapleBrownCow: [1 + 1/2, "cup"],
  boysenberries: [2, "cup"],
  choppedPecans: [1/3, "cup"]
};
muffin.mapleBrownCow[0] > muffin.boysenberries[0] ||
  muffin.oatFlour[0] === muffin.barleyFlour[0];
// true
muffin.oatFlour[0] === muffin.barleyFlour[0] ||
  muffin.mapleBrownCow[0] > muffin.boysenberries[0];
// true
muffin.boysenberries[0] > muffin.choppedPecans[0] ||
  muffin.pastryFlour[0] > muffin.barleyFlour[0];
// true
muffin.boysenberries[0] < muffin.choppedPecans[0] ||
  muffin.pastryFlour[0] < muffin.barleyFlour[0];
// false
|| will return true if at least one of two comparisons is valid.

Figure 3-17. || will return true if at least one of two comparisons is valid.

Saying "and" with &&

Though || will return true if one of two comparisons is true, && will return true only if both comparisons are true. To illustrate, try the following in Firebug, verifying your work with Figure 3-18. Remember to click Run prior to each comment—so four times overall.

var muffin = {
  oatFlour: [1/3, "cup"],
  barleyFlour: [1/3, "cup"],
  pastryFlour: [1 + 1/3, "cup"],
  freshlyGroundNutmeg: [1/4, "tsp"],
  saigonCinnamon: [1/2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  mapleBrownCow: [1 + 1/2, "cup"],
  boysenberries: [2, "cup"],
  choppedPecans: [1/3, "cup"]
};
muffin.mapleBrownCow[0] > muffin.boysenberries[0] &&
  muffin.oatFlour[0] === muffin.barleyFlour[0];
// false
muffin.oatFlour[0] === muffin.barleyFlour[0] &&
  muffin.mapleBrownCow[0] > muffin.boysenberries[0];
// false
muffin.boysenberries[0] > muffin.choppedPecans[0] &&
  muffin.pastryFlour[0] > muffin.barleyFlour[0];
// true
muffin.boysenberries[0] < muffin.choppedPecans[0] &&
muffin.pastryFlour[0] < muffin.barleyFlour[0];
// false
&& will return true only if both comparisons are valid.

Figure 3-18. && will return true only if both comparisons are valid.

Chaining || Expressions

If you chain two || expressions, you can test whether one of three comparisons is valid. Try it in Firebug, verifying your work with Figure 3-19. Note that || has L associativity and that JavaScript does not evaluate the second operand when the first operand evaluates or converts to true. So in the following sample, since the first comparison is true, JavaScript does not evaluate muffin.oatFlour !== muffin.barleyFlour or muffin.pastryFlour < muffin.barleyFlour.

var muffin = {
  oatFlour: [1/3, "cup"],
  barleyFlour: [1/3, "cup"],
  pastryFlour: [1 + 1/3, "cup"],
  freshlyGroundNutmeg: [1/4, "tsp"],
  saigonCinnamon: [1/2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  mapleBrownCow: [1 + 1/2, "cup"],
  boysenberries: [2, "cup"],
  choppedPecans: [1/3, "cup"]
};
muffin.mapleBrownCow[0] > muffin.boysenberries[0] ||
  muffin.oatFlour[0] !== muffin.barleyFlour[0] ||
  muffin.pastryFlour[0] < muffin.barleyFlour[0];
// true
Determining whether at least one of three comparisons is true

Figure 3-19. Determining whether at least one of three comparisons is true

As you might imagine, you can keep right on going, chaining as many || operations as you like. So in the following sample, we're testing whether at least one of five comparisons is valid. So as Figure 3-20 displays, even though just the second and fourth comparison are valid, which is to say return true, overall the chained || expressions return true:

var muffin = {
  oatFlour: [1/3, "cup"],
  barleyFlour: [1/3, "cup"],
  pastryFlour: [1 + 1/3, "cup"],
  freshlyGroundNutmeg: [1/4, "tsp"],
  saigonCinnamon: [1/2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  mapleBrownCow: [1 + 1/2, "cup"],
  boysenberries: [2, "cup"],
  choppedPecans: [1/3, "cup"]
};
muffin.mapleBrownCow[0] > muffin.boysenberries[0] ||
  muffin.oatFlour[0] !== muffin.barleyFlour[0] ||
  muffin.freshlyGroundNutmeg[0] >= muffin.saigonCinnamon[0] ||
  muffin.choppedPecans[0] <= muffin.mapleBrownCow[0] ||
  muffin.pastryFlour[0] === muffin.barleyFlour[0];
// true
Determining whether at least one of five comparisons is true

Figure 3-20. Determining whether at least one of five comparisons is true

Chaining && Expressions

So || is pretty lenient—just one of two or more chained || has to return true for the whole shebang to return true. && on the other hand, is very strict. Every comparison in a chain of && expressions must be valid for the chain to return true as a whole. So as Figure 3-21 illustrates, even though the first and last two comparisons are true, since the third is false the chain of && expressions return false. So we're asking JavaScript all five comparisons are true, not whether most of them are true.

var muffin = {
  oatFlour: [1/3, "cup"],
  barleyFlour: [1/3, "cup"],
  pastryFlour: [1 + 1/3, "cup"],
  freshlyGroundNutmeg: [1/4, "tsp"],
  saigonCinnamon: [1/2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  mapleBrownCow: [1 + 1/2, "cup"],
  boysenberries: [2, "cup"],
  choppedPecans: [1/3, "cup"]
};
muffin.mapleBrownCow[0] < muffin.boysenberries[0] &&
  muffin.oatFlour[0] === muffin.barleyFlour[0] &&
  muffin.freshlyGroundNutmeg[0] >= muffin.saigonCinnamon[0] &&
  muffin.choppedPecans[0] <= muffin.mapleBrownCow[0] &&
  muffin.pastryFlour[0] > muffin.barleyFlour[0];
// false
Determining whether all five comparisons are true

Figure 3-21. Determining whether all five comparisons are true

Let's make && happy and change the third comparison "not less than" to "less than." As Figure 3-22 displays, since all five comparisons are true, overall the && chain returns true. Hurray!

var muffin = {
  oatFlour: [1/3, "cup"],
  barleyFlour: [1/3, "cup"],
  pastryFlour: [1 + 1/3, "cup"],
  freshlyGroundNutmeg: [1/4, "tsp"],
  saigonCinnamon: [1/2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  mapleBrownCow: [1 + 1/2, "cup"],
  boysenberries: [2, "cup"],
  choppedPecans: [1/3, "cup"]
};
muffin.mapleBrownCow[0] < muffin.boysenberries[0] &&
  muffin.oatFlour[0] === muffin.barleyFlour[0] &&
  muffin.freshlyGroundNutmeg[0] < muffin.saigonCinnamon[0] &&
  muffin.choppedPecans[0] <= muffin.mapleBrownCow[0] &&
  muffin.pastryFlour[0] > muffin.barleyFlour[0];
// true
Since all five comparisons are true, overall the && chain returns true.

Figure 3-22. Since all five comparisons are true, overall the && chain returns true.

Chaining || and && Expressions

Note that && has a priority of 5 while || has a priority of 4. So in the following sample, JavaScript evaluates the two && expressions prior to the two || expressions. Therefore, as Figure 3-23 illustrates, by the time JavaScript does the || operations, the comparison has been simplified to false || true || muffin.pastryFlour < muffin.barleyFlour. So, it's not necessary for JavaScript to evaluate the final < comparison.

var muffin = {
  oatFlour: [1/3, "cup"],
  barleyFlour: [1/3, "cup"],
  pastryFlour: [1 + 1/3, "cup"],
  freshlyGroundNutmeg: [1/4, "tsp"],
  saigonCinnamon: [1/2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  mapleBrownCow: [1 + 1/2, "cup"],
  boysenberries: [2, "cup"],
  choppedPecans: [1/3, "cup"]
};
muffin.mapleBrownCow[0] < muffin.boysenberries[0] &&
  muffin.oatFlour[0] === muffin.barleyFlour[0] ||
  muffin.freshlyGroundNutmeg[0] < muffin.saigonCinnamon[0] &&
  muffin.choppedPecans[0] <= muffin.mapleBrownCow[0] ||
  muffin.pastryFlour[0] < muffin.barleyFlour[0];
// true
false || true ||  muffin.pastryFlour[0]< muffin.barleyFlour[0];
// true
Chaining && and || expression to do a complex comparison

Figure 3-23. Chaining && and || expression to do a complex comparison

To better illustrate the JavaScript's lazy evaluation of || and &&, let's replace the final comparison with an alert() call that would say "Don't panic!" Now click Run in Firebug. Firebug does not open an alert dialog box because JavaScript never bothered to invoke alert().

var muffin = {
  oatFlour: [1/3, "cup"],
  barleyFlour: [1/3, "cup"],
  pastryFlour: [1 + 1/3, "cup"],
  freshlyGroundNutmeg: [1/4, "tsp"],
  saigonCinnamon: [1/2, "tsp"],
  seaSalt: [1/4, "tsp"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  mapleBrownCow: [1 + 1/2, "cup"],
  boysenberries: [2, "cup"],
  choppedPecans: [1/3, "cup"]
};
muffin.mapleBrownCow[0] < muffin.boysenberries[0] &&
  muffin.oatFlour[0] === muffin.barleyFlour[0] ||
  muffin.freshlyGroundNutmeg[0] < muffin.saigonCinnamon[0] &&
  muffin.choppedPecans[0] <= muffin.mapleBrownCow[0] ||
  alert("Don't panic!");
// true

I think our boysenberry Brown Cow muffins are done, so let's pull them from the oven. Mmmh, share and enjoy.

Conditionally Returning One of Two Values

What if we'd like to conditionally choose a return value rather than just verify it? This is where the ?: conditional operator, JavaScript's only ternary operator, earns its keep. Like || and &&, ?: chooses a return value based on the boolean its first operand evaluates or converts to. If the first operand evaluates to or converts to true:

  • || returns its first operand.

  • && returns its second operand.

  • ?: returns its second operand.

On the other hand, if the first operand evaluates to or converts to false:

  • || returns its second operand.

  • && returns its first operand.

  • ?: returns its third operand.

Though all three operators can be used to conditionally return a value, the ?: conditional operator's separation of the boolean condition from the possible return values make it the preferred way of doing so. Here's a quick example:

var first = 30;
var second = 20;
var result = first > second ? "first is larger" : "first is smaller";
result;
//"first is larger"

Here the first operand evaluates to true, so ?: returns its second operand. As you can see, the return value is not used as any part of the conditional test.

If I'm making cranberry bread with buttermilk, I'd add 1/2 cup buttermilk as in the following dough object. Note that the leavening bubbles are created by soda reacting with citric acid in the orange juice, lactic acid in the buttermilk, and tartaric acid in the cream of tartar. Note too that for this recipe I like to go with Bob's Red Mill organic hard white whole wheat flour. Though it contains as much fiber-rich bran and vitamin-rich germ as hard red, hard white wheat does have the bitter red tannins. Therefore, hard white whole wheat flour has a milder, sweeter flavor than hard red, making it ideal for sweet soda breads.

var dough = {
  hardWhiteWholeWheatFlour: [2, "cup"],
  sugar: [1/3, "cup"],
  madagascarVanilla: [1, "tsp"],
  orangeZest: [1, "tbs"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  orangeJuice: [1/2, "cup"],
  buttermilk: [1/2, "cup"],
  egg: [1],
  cranberries: [2/3, "cup"]
};

If I don't have buttermilk and do have kefir, I'd add 9/16 cup kefir. Note that there are 16 tbsp. per cup, so 9/16 cup kefir means 1/2 cup plus 1 tbsp.

var dough = {
  hardWhiteWholeWheatFlour: [2, "cup"],
  sugar: [1/3, "cup"],
  madagascarVanilla: [1, "tsp"],
  orangeZest: [1, "tbs"],
  soda: [1, "tsp"],
tartar: [1, "tsp"],
  orangeJuice: [1/2, "cup"],
  kefir: [9/16, "cup"],
  egg: [1],
  cranberries: [2/3, "cup"]
};

But if I don't have buttermilk or kefir but do have yogurt, I'd add 10/16 cup yogurt, which is 1/2 cup plus 2 tbs:

var dough = {
  hardWhiteWholeWheatFlour: [2, "cup"],
  sugar: [1/3, "cup"],
  madagascarVanilla: [1, "tsp"],
  orangeZest: [1, "tbs"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  orangeJuice: [1/2, "cup"],
  yogurt: [10/16, "cup"],
  egg: [1],
  cranberries: [2/3, "cup"]
};

With this in mind, let's conditionally set the amount of a culturedMilk[0] element to 1/2, 9/16, or 10/16 depending on whether there's enough buttermilk, kefir, or yogurt in the fridge. To do so, we'll chain two ?: expressions, one as the return value of the other. As Figure 3-24 displays, since there was not enough buttermilk but enough kefir in the fridge, JavaScript set the amount to 9/16, which evaluates to 0.5625 decimal. In other words, the first ?: returns its second operand because the conditional expression is true; if the conditional had been false, it would have returned the second ?: expression, which in turn would have been evaluated:

var fridge = {
  buttermilk: [1/3, "cup"],
  kefir: [1 + 1/2, "cup"],
  yogurt: [4, "cup"],
};
var dough = {
  hardWhiteWholeWheatFlour: [2, "cup"],
  sugar: [1/3, "cup"],
  madagascarVanilla: [1, "tsp"],
  orangeZest: [1, "tbs"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  orangeJuice: [1/2, "cup"],
  culturedMilk: [1/2, "cup"],
  egg: [1],
  cranberries: [2/3, "cup"]
};
dough.culturedMilk[0] = fridge.buttermilk[0] < 1/2 && fridge.kefir[0] >= 9/16 ? 9/16 :
  fridge.yogurt[0] >= 10/16 ? 10/16 :
  alert("No cranberry bread for you!");
dough.culturedMilk;
// [0.5625, "cup"]
Conditionally choosing the amount of cultured milk with the ?: operator

Figure 3-24. Conditionally choosing the amount of cultured milk with the ?: operator

Making Two Expressions Count as One

Now what if we'd like to also update the amount of buttermilk, kefir, or yogurt in the fridge? Insofar as we can put only one expression following the ? and : tokens of the ?: operator, it would appear we're out of luck.

Not so. JavaScript provides the , comma operator for circumstances like this. , works with two operands and simply evaluates both of its operands and returns the value of the second operand. So if we make our updating of the fridge the first operand to , and the value we want to assign to dough.culturedMilk[0] the second operand, that'll work just dandy. Try it in Firebug, verifying your work with Figure 3-25:

var fridge = {
  buttermilk: [1/3, "cup"],
  kefir: [1 + 1/2, "cup"],
  yogurt: [4, "cup"],
};
var dough = {
  hardWhiteWholeWheatFlour: [2, "cup"],
  sugar: [1/3, "cup"],
  madagascarVanilla: [1, "tsp"],
  orangeZest: [1, "tbs"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  orangeJuice: [1/2, "cup"],
  culturedMilk: [1/2, "cup"],
  egg: [1],
  cranberries: [2/3, "cup"]
};
dough.culturedMilk[0] = fridge.buttermilk[0] >= 1/2 ? (fridge.buttermilk[0] -= 1/2, 1/2) :
  fridge.kefir[0] >= 9/16 ? (fridge.kefir[0] -= 9/16, 9/16) :
fridge.yogurt[0] >= 10/16 ? (fridge.yogurt[0] -= 10/16, 10/16) :
  alert("No cranberry bread for you!");
dough.culturedMilk;
// [0.5625, "cup"]
fridge.kefir;
// [0.9375, "cup"]
Making two expressions count as one with the , operator

Figure 3-25. Making two expressions count as one with the , operator

Deleting a Member, Element, or Variable

If we don't have buttermilk, kefir, or yogurt, we can still make cranberry bread by doubling the orange juice. How do we tell JavaScript to delete dough.culturedMilk and then double dough.orangeJuice?

To delete the culturedMilk member from dough we'd pass it to the delete operator, which works with an any variable, member, element, or parameter. To double the OJ, we'd pass 2 to the *= operator, which we covered earlier. We can do both in one statement by separating them with the , comma operator.

To verify the demise of culturedMilk, we can pass it to the typeof operator, which returns the value type of its operand as a string. So for a missing member like culturedMilk, typeof would return "undefined". Or we could verify its demise with in, an operator that returns true if an object contains a member named with a particular string. Otherwise, in returns false. Note that typeof has a couple of quirks. For null, typeof returns "object" not "null", and for a function typeof returns "function" not "object". Yep, pretty stupid. But try it, verifying your work with Figure 3-26.

var dough = {
  hardWhiteWholeWheatFlour: [2, "cup"],
  sugar: [1/3, "cup"],
madagascarVanilla: [1, "tsp"],
  orangeZest: [1, "tbs"],
  soda: [1, "tsp"],
  tartar: [1, "tsp"],
  orangeJuice: [1/2, "cup"],
  culturedMilk: [1/2, "cup"],
  egg: [1],
  cranberries: [2/3, "cup"]
};
delete dough.culturedMilk, dough.orangeJuice[0] *= 2, dough.orangeJuice;
// [1, "cup"]
typeof dough.culturedMilk;
// "undefined"
"culturedMilk" in dough;
// false
"orangeJuice" in dough;
// true
Deleting a member with delete and verifying its demise with typeof and in

Figure 3-26. Deleting a member with delete and verifying its demise with typeof and in

Summary

In addition to querying objects or arrays, invoking functions, doing math, gluing strings, and assigning values, operators provide a way to verify return values for expressions. Good thing—other than literals, JavaScript expressions tend to evaluate to different values depending on what a visitor does or the browser they're doing it with. So there are many operators to help verify return values with. Those will prove invaluable in Chapter 4, where we'll explore controlling flow, and in the DOM chapters, where we'll test for features before trying to use them.

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

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