You Don't Know JS Yet
Key Takeaways
This is a summary of my favourite points discussed in the course Deep JavaScript Foundations V3 presented by Kyle Simpson on Frontend Masters. I found this course to be invaluable at reexamining key JavaScript concepts that I've used for many years but never explored deep under the hood.
TL:DR Read the JavaScript Specification as it can be an excellent guide to understanding the underlying algorithms that determines JavaScript behaviour
There are occasional "gotchas" in the language to be aware of as they could develop potential bugs in the code. I'll preface those sections with a π.
JavaScript has a Falsy Values table and everything else coerces to true. Falsy Values Table
Typeof
The typeof operator returns a string indicating the type of the VALUE of a variable. Be careful when evaluated null:
var v = null;
typeof v; // βobjectβ
typeof []; // βobjectβ
π Perhaps the language should have set typeof null == "null"
, be careful when performing the following operation so that you don't unintentionally include null or array such as:
typeof v == βobjectβ
π If a variable has never been declared in any scope, JavaScript will coerce it to undefined. Remember that "undefined" represents a variable that has been declared but doesn't have a value at this moment.
typeof a; // undefined
let a;
typeof a; // "undefined"
NaN
"Not a Number" IEEE 754 spec. A special value that indicates an invalid number.
Number("n/a") // NaN
π NaN is the only value that does not adhere to the identity property (it is never equal to itself).
NaN === NaN // false
What about isNan?
isNaN(NaN) // true
isNaN("hello how are you!"); // true ... wait what!?
The reason this happens is that isNan() will first coerce the value to a number first which produces NaN:
Number("hello how are you!") // NaN
π It's important to think of NaN as an invalid number rather than "Not a Number"
typeof NaN // βnumberβ
Interesting language quirk: indexOf() returns -1 because back in C, they could only return integers so it needed some way of returning an invalid value and did so with negative 1 and out of tradition this carried over into JavaScript.
Negative Zero ( -0 )
π In order to comply with IEEE 754, JavaScript needed to implement +0 and -0 as it uses Floating Point arithmetic, but JS actively tries to hide -0 and can cause some really unexpected behaviour:
var trendRate = -0;
trendRate === -0 // true
trendRate.toString(); // β0β OOPS!
trendRate === 0 // true OOPS!
trendRate < 0 // false OOPS!
trendRate > 0 // false
Math.sign(-0) // -0 OOPS! (We would expect -1 as Math.sign() returns -1 or 1 for all other valid numbers)
Possible solution, use the ES6 Object.is():
Object.is(trendRate, -0) // true
Object.is(trendRate, 0) // false
Coercion
When you create a literal string, let s2 = String(5) // '5'
, and then call a method on it, JS will auto-box which converts the string to the wrapper object of String to enable access to all the supporting methods on strings. Try to keep it as String literals as you'll get faster random access speed (it only contains a pointer to a raw memory reference) rather than a robust String object.
π Be careful around using toString() as it might have some unexpected behaviours:
[null, undefined] => β,β // ignores null and undefined values
[1,2,3].toString() => β1,2,3β // removes array brackets
{}.toString() => β[object Object]β // `toString()` on objects is an inherited method from Object, and should be overridden. If not, it will default to outputting: [object <type>]
POP Quiz! Why does Number([null]) === 0
?
When JS first tries to apply Number() on an object, it will invoke toPrimitive() with a number hint which in turn consults the valueOf() and then use toString().
[null].toString() => ββ
Number("") => 0
Boolean() has a pretty simple algorithm, anything that doesn't match a value in the Falsy table (see above), will coerce as true.
π A lot of potential bugs can be avoided by addressing the cases where a value coerces to 0:
Number( ββ ) => 0
Number( null ) => 0
Be careful around if-checks with multiple operators such as when JS evaluates: if (1 < 2 < 3) {...}
if (1 < 2 < 3) {...}
(1 < 2) => true
(true) < 3 => true // 'true' is getting coerces to a number because of the '<' operator
1 < 3 => true // only coincidence that this returns true
Now what about:
if (3 > 2 > 1) {...}
(3 > 2) => true => 1
if (1 > 1) {...} // FALSE!
Good reminder: A quality JavaScript program embraces coercions, making sure the types involved in every operation are clear and you can safely manage corner cases.