Review

Note: if you didn't read through the "style" and "testing" tips at the end of last lecture's notes, you should do so!

Now then... Let's take a moment to remember some of the tools we learned from last class!

Write a script that prompts a user for a number, and alerts true if that number is an integer (no decimal) and false if that number is a floating point number (decimal point number). Assume the user only enters legally formed numbers, and no junk input like text.



Logical Operators

We looked at a couple of comparison operators earlier in the course (>, ===, etc.) but we didn't talk about how to combine Boolean values using logical operators.

For example, what would I do to alert true if a number is *between* two values? I need some tools to connect Booleans!

The logical AND operator (&&) returns true if its left value and right value are both true. False otherwise.

  let x = 1,
      y = 3;
  
  alert(x > y && y > 2); // false
  alert(x < y && y > 2); // true
  alert(x === y && false); // false

The logical OR operator (||) returns true if at least one of its left value and right value are true. False otherwise.

  let x = 1,
      y = 3;
  
  alert(x > y || y > 2); // true
  alert(x < y || y > 2); // true
  alert(x === y || false); // false

The logical NOT (!) negates its right value, returning false if it's true, and true if it's false.

  let x = 1,
      y = 3;
  
  alert(!(x > y)); // true
  alert(!(x < y)); // false
  alert(!true); // false

Logical operators are the perfect tools when we need to meaningfully combine two or more Boolean values.

Write a script that prompts a user for two strings, and alerts true if and only if the two strings share the same first and last character. Alert false otherwise.



Operator Precedence

We've talked about a lot of operators thus far, but we've done very simple examples where there are only two operands or different operators...

But one important question remains untouched: how do we decide which operations occur in what order?

Operator precedence describes the order in which operators evaluate their operands.

Operands are the arguments to operators. So for example, in 5 + 3, the operator is + and the operands are 5, 3.


So what exactly are the rules that dictate the order of operation in JavaScript?

Remember all those times you excused your dear Aunt Sally in grade school? Well, things just got a little more complicated...

You see, operators have a few properties in programming languages:

An operator's arity describes how many operands it... well... operates upon.

For example, addition is a binary operator because it has two operands (e.g., 5 + 2), but typeof is a unary operator because it has one operand (e.g., typeof "test")


An operator's associativity is the direction of application when operators of the same precedence level are parsed. Associativity can be either left-to-right or right-to-left.

Almost all of the common mathematical operators are left-to-right associative (+, -, *, /, %), but a few operators we've seen will evaluate their rvalue before their left.

For example, the assignment operator (=) is a right-to-left operator, since we must first compute the right-hand value before assigning the value to a variable.

One such example would be: let y, x = y = 5 + 5 + 5;. We see that the value 15 gets stored into y and THEN into x because assignment is right-to-left.


An operator's precedence level determines the order of evaluation in an expression. The higher the precedence level, the sooner it will be evaluated.

Remember in basic math when we had something like: 5 + 3 * 2? This should give us 11 (PEMDAS), even though the addition operator comes before the multiplication one.

Recall also that if we use parentheses in basic math, we evaluate anything inside of the parentheses first, so (5 + 3) * 2 will give us 16.

That said, there are a lot of operators in JavaScript, each with slightly different precedence levels... let's look at an abbreviated list now:


The following JavaScript precedence rules govern the order in which operators are evaluated. (taken and revised from the Mozilla Developer's Network).

Note: some operators have been omitted because we have yet to cover them (or will not), thus leaving some precedence levels missing.

Precedence Operator type Associativity Individual operators
19 Grouping n/a ( x )
18 Member Access left-to-right x . y
Computed Member Access left-to-right x [ y ]
17 Function Call left-to-right f ( x )
16 Postfix Increment n/a x ++
Postfix Decrement n/a x --
15 Logical NOT right-to-left ! x
Prefix Increment right-to-left ++ x
Prefix Decrement right-to-left -- x
typeof right-to-left typeof x
Multiplication left-to-right x * y
Division left-to-right x / y
Remainder left-to-right x % y
13 Addition left-to-right x + y
Subtraction left-to-right x - y
11 Less Than left-to-right x < y
Less Than Or Equal left-to-right x <= y
Greater Than left-to-right x > y
Greater Than Or Equal left-to-right x >= y
10 Strict Equality left-to-right x === y
Strict Inequality left-to-right x !== y
6 Logical AND left-to-right x && y
5 Logical OR left-to-right x || y
3 Assignment right-to-left x = y
x += y
x -= y
x **= y
x *= y
x /= y
x %= y
0 Comma / Sequence left-to-right x , y

Now that you've read through that list, note that you do not have to memorize it! Just be aware that it exists, and be able to consult it if necessary.

Let's try an example by hand!

Note: when trying to simplify precedence-related problems by hand, consider writing the expression operator-by-operator in small steps to make your life easier and avoid any needless errors. I'll illustrate this process below.

Simplify the following expression: (typeof "testing!").charAt(0).toUpperCase() === "S"
Note: the precedence levels of interest are: () [19], . [18], typeof [15], === [10]

  (typeof "testing!").charAt(0).toUpperCase() === "S"
  "string".charAt(0).toUpperCase() === "S"
  "s".toUpperCase() === "S"
  "S" === "S"
  true

Let's try a couple more!

Simplify each of the following expressions using the rules of precedence. Relevant precedence levels and associativity are listed above the expression. Show your steps.

  // ===, !== [10, LR]
  5 === "5" !== true
  // * [15, LR]
  // + [13, LR]
  // <, >= [11, LR]
  // && [6, LR]
  5 + 10 < 15 && 5 * 2 >= 10


Type Coercion

We've already seen what happens when operators are given values of the type they expect, for example:

  5 + 5         // number + number
  !true         // ! boolean
  "con" + "cat" // string + string

...but what happens if we provide an operator with different types or types different than what it expects?!

  "5" + 5       // string + number
  !5            // ! number
  null + "cat"  // null + string

In some other languages, we would call this a type clash, which would often throw an error and our program would stop executing...

JavaScript, however, is called weakly typed, such that if an operator is given different types (or different from what it expects), then our interpreter will attempt to perform an implicit type conversion to make the operation work.

Whenever such an implicit conversion occurs, we refer to this as type coercion, because data of one type is being forced into data of another type.


There are many rules governing JavaScript type conversion (consult Section 3.7 in your text), but we'll look at a few right now.

When a number is expected (arithmetic operations):

  • Booleans: false is treated as 0, and true is treated as 1.

  • Strings: treated as their converted number equivalent if they look like a number (e.g., "5"), or NaN otherwise (special consideration for +, to be considered later).

  • Null: treated as 0

  • Undefined: treated as NaN

  • Objects: to be discussed later!

  "5" * 5        => 25
  "text" * 5     => NaN
  5 * true       => 5
  3 + null       => 0

When a boolean is expected (logical operations):

  • "Falsy" Values: false, 0, the empty string (""), null, undefined, and NaN

  • "Truthy" Values: everything else!

  ! 0            => true
  0 || 1         => true
  "" && 1        => false
  ! null         => true

When a string is expected (text operations): the interpreter will typically just treat the other data as text itself, though some operations differ.

  5 + "5"           => "55"
  "c" + 4           => "c4"
  "un" + undefined  => "unundefined" // so... defined?
  "un" + null       => "unnull"
  "un" + true       => "untrue" // but still truthy!
  "K9".indexOf(9)   => 1

Why does "5" + 5 => "55", but "5" * 5 => 25?

The interpreter defaults to using the concatenation operator when a string is involved (rather than addition), but since * is defined only for multiplication, we use the arithmetic interpretation by default.


Let's try a couple more complicated examples, combining what we learned about precedence above!

For each of the following examples, (a) use the given precedence hints to evaluate the expression and (b) determine the type of the final answer. Hint: try to compute each expression one step at a time!

  // + [13, LR]
  5 + "5" + 5
  // + [13, LR]
  5 + 5 + "5"
  // * [15, LR]
  // + [13, LR]
  5 * "5" + "5" * 5
  // () [19]
  // * [15, LR]
  // + [13, LR]
  2 * ("2" + "2") * 2
  // () [19]
  // * [15, LR]
  // < [11, LR]
  (5 * "3") * (null < 5)

Now you might also be asking, "Why is it important to know of these conversion rules?"

  • It can help you debug when you have surprising results that are coming from interactions between data types.

  • It can simplify some algorithms when you know how data types interact.

  • If another programmer uses type coercion in their code, you'll be able to understand it.



Logical Operators without Booleans

Here's a small note related to type coercion (though not really the same) where our logical connectors are given values that are not Booleans.

As it turns out, there's a slightly different way of thinking about how logical AND and logical OR operate that gives us the rule for how they work with non-Boolean values.

Logical AND will return the first falsy operand it encounters (from left to right), or the last truthy one if no falsy ones were found.

Logical OR will return the first truthy operand it encounters (from left to right), or the last falsy one if no truthy ones were found.


What does this look like? Let's try a couple of examples:

  // Logical AND
  0 && 1       => 0
  1 && 0       => 0
  1 && 2       => 2
  1 && 2 && 3  => 3
  1 && 0 && 3  => 0
  "hi" && "" && "yo"  => ""
  "hi" && "sup" && "yo"  => "yo"
  
  // Logical OR
  0 || 1       => 1
  1 || 0       => 1
  1 || 2       => 1
  1 || 2 || 3  => 1
  "yo" || ""   => "yo"
  "" || "yo" || "hey"   => "yo"

Suppose you wanted specifically the Boolean values true or false, you can use another type conversion tool:

The Boolean(value) type conversion command returns true if value is truthy, or false if value is falsy.

Sometimes you'll see this same behavior represented as !!, which says "first convert the value to a boolean, negate it, then negate it again" (thus giving you the Boolean equivalent of the value).

  Boolean(0)     => false
  Boolean("0")   => true
  Boolean("")    => false
  Boolean(1)     => true
  
  !!1            => true
  !!""           => false


  PDF / Print