CSE 134B Web Client Languages
  1. Interaction
  2. JavaScript
  3. Types and coercion

Interaction · JavaScript 04

Types and coercion

JavaScript converts values for you, often without being asked. 0 + '5' is '05', 0 - '5' is -5, and 2 == true is false even though if (2) runs its branch. Each of those follows a written rule. This page gives you the rules, so you can read an expression and say what it does.

The previous page, Statements, variables and flow, left a question open: a form field's '5' added to a total of 0 gives '05'. The answer is in section 4, and it depends on the types and conversion tables that come before it. Remember the Prof's law of "weird stuff": focus on the constructs in a new language that are odd to you, because they show how it varies from what you know from Java or C and C++. Types are where JavaScript has the most of them.

1. The primitive types

JavaScript has seven primitive types: undefined, null, boolean, number, BigInt, string and Symbol. Everything else is an object, including arrays and functions. A primitive is a single immutable value. Objects and arrays covers the other side of that line.

The seven primitive types and objects, with what typeof reports for eachEight boxes. undefined reports 'undefined'. null reports 'object', marked as a quirk. boolean reports 'boolean'. number reports 'number'. BigInt reports 'bigint'. string reports 'string'. Symbol reports 'symbol'. Objects report 'object', or 'function' for a function.undefined'undefined'null'object'boolean'boolean'number'number'BigInt'bigint'string'string'Symbol'symbol'objects'object''function'
Each box shows a type and what typeof reports for it. null is the one that does not match.

Strings

A string is written in single quotes, double quotes or backticks. "Thomas", 'x' and 'Who are you?' are all the same type, and JavaScript has no separate character type. A string may include special escaped characters and formatting characters.

// Strings may include special escaped characters let str = 'This isn\'t hard'; console.log(str); // logs: This isn't hard // Strings may contain some formatting characters str = 'Here are some newlines \n\n\n and tabs \t\t\t yes!'; console.log(str.includes('\n')); // logs: true

ES6 added template strings, which use backticks. A ${ } inside one holds any expression, and its value is converted to a string in place.

let firstName = 'Camdyn'; let lastName = 'Rasque'; let number = 5; let msg = `${lastName}, ${firstName}: #${number}`; console.log(msg); // logs: Rasque, Camdyn: #5

Numbers and BigInt

Numbers such as 5, -345, 56.7 and -456.45677 are all one type. There is no separate integer type, and numbers in JavaScript tend not to be complex, in the sense of higher math. Each is a 64-bit floating-point value, the same as a double in Java or C, so the usual rounding appears. Integers are exact only up to Number.MAX_SAFE_INTEGER, which is 9007199254740991.

BigInt is the type for integers beyond that. A BigInt literal ends in n. It has been available across browsers since September 2020, according to MDN. Arithmetic does not mix a BigInt with a number, so you have to convert one of them yourself.

console.log(0.1 + 0.2); // logs: 0.30000000000000004 console.log(Number.MAX_SAFE_INTEGER); // logs: 9007199254740991 console.log(9007199254740992 === 9007199254740993); // logs: true console.log(String(9007199254740992n + 1n)); // logs: 9007199254740993 console.log(typeof 1n); // logs: bigint console.log(1n + 1); // throws: TypeError

Booleans and symbols

A boolean is true or false. A symbol is a newer type for unique values. Every call to Symbol() makes a new one, even with the same description, so a symbol can name a property without any chance of colliding with another name. You will meet the built-in ones, such as Symbol.iterator, which is how for/of finds out how to walk a collection.

console.log(Symbol('x') === Symbol('x')); // logs: false console.log(typeof Symbol('x')); // logs: symbol

undefined and null

JavaScript has two values for nothing, each the only value of its own type. Consider a variable's lifecycle to remember which is which. It starts as undefined, because nothing has been assigned. It holds values for a while. At the end, a program sets it to null to say on purpose that it holds nothing. So undefined usually comes from the language, and null from a programmer.

let user; console.log(user); // logs: undefined user = 'Thomas'; console.log(user); // logs: Thomas user = null; console.log(user); // logs: null

Asking with typeof

The typeof operator returns a value's type as a string. Two answers are odd. typeof null is 'object', which is a historical quirk and does not make null an object. MDN traces it to the first implementation, where objects had a type tag of 0 and null was the null pointer, and a proposed fix was rejected. To test for null, write value === null. An array also reports 'object', so test with Array.isArray(). Unlike any other use of an unknown name, typeof on a name that was never declared returns 'undefined' and does not throw, although it does throw on a let read before its declaration.

console.log(typeof 'x'); // logs: string console.log(typeof 5); // logs: number console.log(typeof true); // logs: boolean console.log(typeof undefined); // logs: undefined console.log(typeof null); // logs: object console.log(typeof []); // logs: object console.log(typeof {}); // logs: object console.log(typeof function () {}); // logs: function console.log(typeof neverDeclared); // logs: undefined

2. Weak typing

Two separate properties get called weak typing, and JavaScript has both. It is dynamically typed: a variable has no declared type, so one holding a string can later hold a number.

let x; x = 'hello'; console.log(typeof x); // logs: string x = 5; console.log(typeof x); // logs: number x = false; console.log(typeof x); // logs: boolean

Some languages are more strongly typed, in that you must declare the type of a variable and stick with it. The next sample imagines that syntax in JavaScript, and it is close to how TypeScript writes it.

let myName: string; // imagine syntax like this myName = 3; // would throw an error

JavaScript is also weakly typed in the narrower sense: an operator given the wrong type converts its operands and carries on, where a stronger language stops. Python is dynamically typed too, but 1 + '5' there is a TypeError. In JavaScript it is '15'. Implicit conversion of this kind is called coercion, and most of this page is about it.

While weak typing seems beneficial to a programmer's thinking at first, it leads to confusion and errors. Two questions to consider as you read: would you prefer errors at compile or interpret time, or at run time? And do you think weak typing makes web apps safer?

The big concern is not knowing what things do. && and || do not behave as they do in other languages, toString() on objects and arrays has details to learn, and an occasional implementation difference adds to it. Together these lead to an assumption of craziness, which Gary Bernhardt's short talk Wat plays for laughs. By the end of this page you should be able to explain its JavaScript examples.

3. The conversion tables

Three conversions account for nearly all the coercion you will see: to boolean, to number and to string. The specification names them ToBoolean, ToNumber and ToString. These tables give the results, and the sections after them say when each one runs.

To boolean

Original typeConverted to a boolean
undefinedfalse
nullfalse
Numberfalse if 0, -0 or NaN, else true
BigIntfalse if 0n, else true
Stringfalse if the string's length is 0, else true
Symboltrue
Objecttrue, every time

A value that converts to false is called falsy, and anything else truthy. There are few enough falsy values to memorize: false, 0, -0, 0n, NaN, '', null and undefined. Three truthy values catch people: '0' and ' ' are strings with a length, and [] is an object. (The browser has one falsy object, the old document.all, kept for compatibility.)

To number

Original typeConverted to a number
undefinedNaN
null0
Boolean1 if true, 0 if false
StringThe numeric value, if the whole string is a number. An empty or whitespace-only string is 0. All other strings become NaN.
ObjectThe object becomes a primitive first, then this table applies to that. A plain object becomes a string, so NaN.
ArrayThe array becomes a string by joining its elements with commas, then this table applies to the string.
BigIntNumber(1n) is 1. Implicit conversion, as in +1n, throws a TypeError.
SymbolThrows a TypeError.

The array row explains results that otherwise look arbitrary. An empty array becomes '', which is 0. An array of one element becomes that element as a string, so [5] is 5, and this works recursively. Two or more elements give a string with a comma in it, which is NaN. Inside an array, null and undefined join as an empty string, so [undefined] is 0 even though undefined alone is NaN, and [true] is NaN because the string 'true' is not a number.

To string

Original typeConverted to a string
undefined'undefined'
null'null'
Boolean'true' if true, 'false' if false
NumberA string representation of the numeric value, such as '5'. This may include the special values 'NaN', 'Infinity' or '-Infinity'. Note that -0 gives '0'.
BigIntThe digits, with no n.
ObjectThe value of the object's toString() method, in most cases. An array gives its elements joined with commas, and a plain object gives '[object Object]'. An object with no way to become a primitive throws a TypeError.
SymbolThrows a TypeError when converted implicitly. String(symbol) works, as a special case.

The object and array rows of the number table, and the object row here, depend on the same step, called ToPrimitive. Asked for a primitive, an object tries valueOf() and then toString(), and uses the first one that returns a primitive. The default valueOf() returns the object itself, which does not count, so for arrays and plain objects toString() decides. (When a string is what's wanted, the order is reversed. For these objects the result is the same.)

This playground runs the tables. Each line calls an explicit conversion, which applies the same rules that coercion does, and JSON.stringify keeps the quotes visible on strings.

4. The + operator, and the others

You should get familiar with this use of +, because it serves both as addition and as string concatenation, and that double job makes it different from every other arithmetic operator. Here is what the specification does for a + b.

  1. Convert each side to a primitive, so objects and arrays become strings.
  2. If either primitive is a string, convert both to strings and concatenate them.
  3. Otherwise, convert both to numeric values (number or BigInt) and add them.

That answers the question from the previous page. In 0 + '5' the right side is a string, so the 0 becomes '0', and the result is '05'. A form field's value is always a string, so adding one to a running total joins them. Convert the value to a number first, as section 8 shows.

The third rule means that with no string in sight, + is numeric, even when neither operand is a number. true + true is 2, and null + 1 is 1. Objects do reach concatenation, through the first rule, because they usually become strings.

The steps of the plus operator beside those of the minus operatorOn the left, plus converts each side to a primitive, then asks whether either is a string. If so it converts both to strings and concatenates. If not it converts both to numeric values and adds. On the right, minus converts both sides to numeric values and subtracts. The same applies to times, divide and remainder.a + beach side to a primitiveeither one a string?yesnoboth to strings,concatenate0 + '5' is '05'both to numeric,addtrue + true is 2a - bboth to numericno stringbranchsubtract0 - '5' is -5
Only + has a string branch. -, *, / and % always work on numeric values.

Subtraction has no such choice. If either of the two values isn't numeric, it converts them to numeric values (numbers, unless BigInt is involved) and subtracts. The multiplication, division and remainder operators all behave the same way as subtraction. So the same two operands can give a string from + and a number from anything else. These operations can lead to some initially funky situations, so be careful with how you are using them, and test often.

The last two lines depend on order. + runs left to right, so 1 + 2 + '3' adds first and then joins, and '1' + 2 + 3 joins twice.

One Wat example needs a note on parsing. Written as an expression, [] + {} and {} + [] both give '[object Object]'. Typed at the start of a statement in a console, {} + [] can give 0, because the parser reads {} as an empty block, which leaves the unary expression +[]. Conversion is doing what the tables say. The difference comes from the grammar.

For building text, a template string avoids the question, since it has no second meaning. The two lines below give the same string.

let great = 'great!'; // String operator example console.log('JavaScript' + ' is ' + great); // logs: JavaScript is great! // With template strings instead console.log(`JavaScript is ${great}`); // logs: JavaScript is great!

5. What &&, || and ?? return

Careful: these operators do not work the way you might believe. Try 5 && 10. In theory, && would return true if both the left and right expressions are true, but this is only somewhat true. The result here is 10.

Given expr1 && expr2, if expr1 can be converted to true, it returns expr2. Otherwise, it returns expr1. Given expr1 || expr2, if expr1 can be converted to true, it returns expr1. Otherwise, it returns expr2. Note that each returns the value of the expression, not the truthy value. The conversion to boolean is used for the decision and then thrown away. MDN has the detail under logical AND and logical OR.

Both also short-circuit. If the first expression of && is falsy, the second is never run at all, and similarly if the first expression of || is truthy. All of this is fine in an if() statement, since the if() will evaluate the returned expression on its own. It gets tricky when doing assignments, where the operand itself lands in the variable.

Which operand each logical operator returnsThree rows. a and-and b tests whether a is truthy: if yes it returns b, if no it returns a. a or-or b tests whether a is truthy: if yes it returns a, if no it returns b. a nullish-coalescing b tests whether a is null or undefined: if yes it returns b, if no it returns a. In each row, when a is returned, b never runs.expression and its testtest passestest failsa && b: a truthy?returns breturns aa || b: a truthy?returns areturns ba ?? b: a null or undefined?returns breturns a
Each operator returns one of its operands, never a new boolean. Whenever it returns a, b does not run.

The line with [] || ['1'] keeps the empty array, because an array is an object and every object is truthy. The older idiom for a default value, name = name || 'Anonymous', has a gap for the same kind of reason. It replaces every falsy value, so an empty string, a 0 or a false that someone set on purpose is lost.

The nullish coalescing operator

The nullish coalescing operator ?? fills that gap. It returns the first value if it !== null and !== undefined, and otherwise returns the second value. That is useful when you want to guarantee that a variable has a defined value. It is not to be confused with ||, which returns the first value if it is not falsy. MDN lists ?? as available across browsers since July 2020.

let username, currUser; currUser = username ?? 'Anonymous'; console.log(JSON.stringify(currUser)); // logs: "Anonymous" currUser = username || 'Anonymous'; console.log(JSON.stringify(currUser)); // logs: "Anonymous" username = ''; currUser = username ?? 'Anonymous'; console.log(JSON.stringify(currUser)); // logs: "" currUser = username || 'Anonymous'; console.log(JSON.stringify(currUser)); // logs: "Anonymous"

Mixing ?? directly with && or || is a SyntaxError. Add parentheses to say which you mean, as in (a || b) ?? c.

The third logical operator, !, is the plain one. It converts its operand to a boolean and inverts it, so it always returns a boolean.

6. == and ===

JavaScript is illogical, it would seem. In the sample below, 2 is not equal to true, and yet an if treats 2 as true.

if (2 == true) { console.log('2 is true'); } else { console.log('2 is false'); // logs: 2 is false } if (2) { console.log('2 is true'); // logs: 2 is true } else { console.log('2 is false'); }

JavaScript has logic, but operators can get a bit hairy if you don't know how they compare things. An if uses the boolean table. == does not, and it has its own algorithm in which a boolean becomes a number, so 2 == true is really 2 == 1. Reading the spec from time to time is good for your health, if only mentally.

The == algorithm

To spare you some time, here is the specification's IsLooselyEqual, in order. != works the same way, with the return value logically notted.

The double-equals algorithm, as a list of rules tried in orderRules tried from the top. Same type: compare as triple-equals does. null against undefined: true. Number against string: the string becomes a number, then start again. BigInt against string: the string is parsed as a BigInt, then start again. Either side a boolean: the boolean becomes a number, then start again. Object against a string, number, BigInt or symbol: the object becomes a primitive, then start again. BigInt against number: compare by mathematical value. Anything else, such as null against 0 or a symbol against a number: false.when the two sides arethenthe same typecompare as === doesnull and undefinedtruea number and a stringstring to number, start againa BigInt and a stringparse as BigInt: no parse is false, else restarta boolean on either sideboolean to number, start againan object, and not null or undefinedobject to primitive, start againa BigInt and a numbercompare mathematical valueanything elsefalse
Rules are tried from the top. A conversion restarts the list with the new pair, so one comparison can make several conversions.

In plainer terms:

  1. If they are both objects, return true only if they reference the same object. Two arrays with the same contents are not equal.
  2. When null and undefined are compared, return true. Neither one equals anything else. This is why null == 0 is false, although Number(null) is 0: no rule converts null.
  3. If they are of different types and one is a number, convert the other to a number, for a string or a boolean. A boolean is converted even when the other side is not a number.
  4. If an object is compared with a primitive, convert the object to a primitive using valueOf() or toString().
  5. Primitives of the same type are compared the way you would expect, except that if NaN is involved it is always false, and +0 and -0 are treated as the same.

Walk through [] == false, which looks as if it should be false twice over, since [] is truthy. The right side is a boolean, so it becomes 0, leaving [] == 0. The left side is an object, so it becomes the primitive '', leaving '' == 0. That is a string against a number, so the string becomes 0, leaving 0 == 0, which is true. At no point did the algorithm ask whether [] was truthy, and it does not ask that about the 2 in 2 == true either.

The Type explorer below shows these steps for any pair from a list of awkward values, for ==, ===, +, -, &&, || and ??. It also has a matrix of == across every pair, and typeof, Number(), String() and Boolean() for a single value. JavaScript computes each result as you choose. It is also available as a page of its own in Resources › Tools.

=== and !==

You may also come across equality operators that have an extra equals sign on the end. This signifies a strict evaluation. The only difference is that strict evaluation doesn't perform any type conversions. Outside of that it follows the same comparison, and in fact the first rule of == hands a same-typed pair straight to it. Different types give false, every time.

This course's position: write === and !==, because you can read them without running the algorithm in your head. Convert explicitly first if the types may differ. The one == you will often see on purpose is value == null, which is true for both null and undefined and for nothing else.

Comparisons with < and >

The comparison operators >, <, >= and <= convert as well, by their own rule, and they have no strict form. Two strings compare as text, character by character. Otherwise both sides become numbers. So '10' < '9' is true and '10' < 9 is false. They also convert null to 0, where == does not, which gives an odd trio of results.

console.log('10' < '9'); // logs: true console.log('10' < 9); // logs: false console.log(null >= 0); // logs: true console.log(null > 0); // logs: false console.log(null == 0); // logs: false

7. NaN and Infinity

The number type has two special values that come out of arithmetic where a language like Java would throw.

NaN

The value of NaN (Not a Number) can seem a bit odd. Using a value that is not a number in arithmetic can result in this value. Don't compare NaN, though, because it is not equal to anything, itself included. Use isNaN() instead. And yes, its type is number.

The last group shows why two versions exist. The global isNaN() converts its argument to a number first, so it reports true for the string 'hello', which is not the value NaN. Number.isNaN() converts nothing, and it is true only for NaN itself. Prefer it when you are asking about a value that has already been through a calculation.

Infinity

Getting to Infinity is pretty easy in JavaScript: just divide by zero. You also can get there pretty quickly by exceeding bounds. Once you are at Infinity it is toxic, with no turning back through ordinary addition or subtraction.

// Getting to Infinity is pretty easy in JavaScript: just divide by zero let result = 2585 / 0; console.log(`let result = 2585 / 0 = ${result}`); // logs: let result = 2585 / 0 = Infinity // You also can get there pretty quickly by exceeding bounds result = 999343999993999 ** 35; // The ** operator is shorthand for Math.pow() console.log(`999343999993999 ** 35 = ${result}`); // logs: 999343999993999 ** 35 = Infinity // Once you are at Infinity it is toxic console.log(`result - 1 = ${result - 1}`); // logs: result - 1 = Infinity

There are exceptions to "toxic". Dividing by it gives 0, and Infinity - Infinity is NaN. Although not bounded, Infinity is a number, as typeof verifies. Unlike NaN, you can compare Infinity and -Infinity, and they work as expected. Since Infinity is still like an error case, a built-in function, isFinite(), checks that a value is finite. Note that it is false for NaN too. Like isNaN(), it converts first, and Number.isFinite() is the version that does not.

let result = 2585 / 0; console.log(`typeof Infinity = ${typeof Infinity}`); // logs: typeof Infinity = number console.log(`result == Infinity is ${result == Infinity}`); // logs: result == Infinity is true console.log(`result === Infinity is ${result === Infinity}`); // logs: result === Infinity is true console.log(`Infinity == Infinity is ${Infinity == Infinity}`); // logs: Infinity == Infinity is true console.log(`Infinity === Infinity is ${Infinity === Infinity}`); // logs: Infinity === Infinity is true console.log(`-1 / 0 is ${-1 / 0}`); // logs: -1 / 0 is -Infinity console.log(`isFinite(result) is ${isFinite(result)}`); // logs: isFinite(result) is false console.log(`isFinite(NaN) is ${isFinite(NaN)}`); // logs: isFinite(NaN) is false console.log(`1 / Infinity is ${1 / Infinity}`); // logs: 1 / Infinity is 0 console.log(`Infinity - Infinity is ${Infinity - Infinity}`); // logs: Infinity - Infinity is NaN

8. Converting on purpose

While JavaScript will go ahead and convert the values for you with no hassle, converting them manually can lead to more predictable development. You will see all of these in other people's code, so learn to read them.

SchemeExplanation
+xUsed as a unary operator, + converts a value to a number. It throws on a BigInt.
Number(x)Converts a value to a number if possible, following the table in section 3. It also accepts a BigInt.
parseInt(s), parseFloat(s)Parse an integer or a float out of the start of a string. They ignore trailing characters.
Boolean(x)Converts a value to a boolean, following the table in section 3.
!!xThe first not converts the value to a boolean and inverts it. The second not inverts the first.
x + ''Given what + does with a string, this often serves to convert a value to a string. It throws on a symbol.
x.toString()A method that returns a string form of the value. For objects you may see '[object Object]' or a user- or host-defined value. It throws on null and undefined, which have no methods.
String(x)Converts any value to a string, including null, undefined and symbols.

The difference between Number() and parseInt() affects form input most. Number() takes the whole string or gives up, except that an empty string is 0. parseInt() reads as far as it can and stops, and it reports nothing to read as NaN. So an empty field looks like a zero to one and a failure to the other, and neither is the right answer until you have decided what an empty field means in your program.

let total = 0; let input = '5'; total = total + Number(input); console.log(total); // logs: 5 console.log(Number('12px')); // logs: NaN console.log(parseInt('12px', 10)); // logs: 12 console.log(parseFloat('3.14abc')); // logs: 3.14 console.log(parseInt('101', 2)); // logs: 5 console.log(Number('')); // logs: 0 console.log(parseInt('', 10)); // logs: NaN console.log(+'5'); // logs: 5 console.log(!!'0'); // logs: true console.log(!!0); // logs: false console.log(JSON.stringify(5 + '')); // logs: "5" console.log(JSON.stringify(String(null))); // logs: "null" console.log(null.toString()); // throws: TypeError

The second argument to parseInt() is the radix. Pass 10 unless you mean another base. Since conversion is so much of what can go wrong in a weakly typed language, a check of the type before you act on a value is a normal thing to write.

// You can also use the typeof operator to figure out type let x = '5'; console.log(typeof x); // logs: string // Be aware that using operators like equality (or even +) may not produce expected results x = 5; let y = '5'; console.log(x == y); // logs: true console.log(x === y); // logs: false

9. The operators, and their order

Expressions are made with operators, and for most of them only the conversion rules above are unusual. Here is the set, for reference.

GroupOperators
Basic arithmetic+ addition, - subtraction and unary negation, / division, * multiplication, % remainder, ** exponent
Increment and decrement++ add one, -- subtract one
Comparison>, <, >=, <=, != not equal, == equal, !== strict inequality, === strict equality
Logical&& and, || or, ! not, ?? nullish coalescing
Bitwise&, |, ^, ~, and the shifts >>, >>> and <<
String+ concatenation

% is often called modulus, but MDN is clear that it is a remainder. The result takes the sign of the dividend, so -13 % 5 is -3, where a true modulo would give 2. Watch for this if you index into something with a value that can go negative.

The bitwise and shift operators are not commonly used in JavaScript traditionally, but with the rise of 3D and memory access they could be more common. A number is converted to a 32-bit integer for these operations, which is different from anything else in the language.

console.log(-13 % 5); // logs: -3 console.log(5 & 3); // logs: 1 console.log(5 | 3); // logs: 7 console.log(5 ^ 3); // logs: 6 console.log(1 << 4); // logs: 16 console.log(2 ** 32 | 0); // logs: 0

Precedence

Just like in math, JavaScript has an order of operations for its operators, and parentheses are the highest.

console.log(4 + 5 * 8); // logs: 44 console.log((4 + 5) * 8); // logs: 72

MDN's precedence table shows every operator and where it falls. It currently numbers its levels from 18, for grouping, down to 1, for the comma. Those numbers belong to the table, and the language's specification has no such numbering, since its grammar implies the order. So learn the relative order and do not memorize a count. The spread syntax is typically not regarded as an operator, and MDN includes it in the table anyway to show how tightly it binds.

A few relative positions come up often. Multiplication comes before addition. Both come before the comparisons, and those come before ==. Then && comes before ||. Assignment is near the bottom, so the right side is worked out before it is stored. Most operators of the same level run left to right, as the 1 + 2 + '3' example showed. ** and assignment run right to left.

console.log(true || false && false); // logs: true console.log((true || false) && false); // logs: false console.log(2 ** 3 ** 2); // logs: 512 let a; let b; a = b = 5; console.log(a, b); // logs: 5 5

You will not hold the whole table in your head, and a later reader won't either. Where the order isn't obvious, add parentheses.

10. Summary

ConceptKey points
Primitive typesundefined, null, boolean, number, BigInt, string, Symbol. Everything else is an object.
typeofReturns the type as a string. typeof null is 'object', a historical quirk. Arrays also report 'object'.
Weak typingVariables have no declared type, and operators convert their operands instead of failing.
Falsy valuesfalse, 0, -0, 0n, NaN, '', null, undefined. All objects are truthy, including [].
To number'' and whitespace are 0, other non-numeric strings are NaN, null is 0, undefined is NaN. Arrays go through a string first.
+Each side to a primitive. If either is a string, concatenate. Otherwise add as numbers. 0 + '5' is '05', and true + true is 2.
-, *, /, %Always convert to numeric values. % is a remainder, with the sign of the dividend.
&&, ||, ??Return an operand, not a boolean, and skip the right side when the left settles it. ?? tests only for null and undefined.
==A list of rules that convert until the types match. A boolean becomes a number, an object a primitive. null equals only null and undefined. Truthiness is never consulted.
===No conversion. Different types are never equal. This course's choice.
NaNNot equal to itself. Test with Number.isNaN(). Its type is number.
InfinityComes from dividing by zero or overflowing. It compares normally. Test with isFinite().
Explicit conversionNumber() takes the whole string, parseInt() reads what it can. Use String() and Boolean(), and read +x, !!x and x + ''.
PrecedenceLearn the relative order, look up the rest on MDN, and use parentheses where the order isn't obvious.