I’m currently reading Kyle Simpson’s You Don’t Know JS (YDKJS), the eighth-most starred project on GitHub, often considered the best JavaScript series ever written, and it is the best JavaScript series I’ve ever read. If you’re going to really work with JavaScript, or Node.js, or TypeScript, please just read this summary to see everything you’re missing out on.

But although the explanations are extremely comprehensive (yet mysteriously brief), there’s no end-of-chapter problems for me to solidify my understanding. If you’ve read that summary, you’ll think that Kyle’s favourite word is “perhaps” and his favourite hobby is debunking common misconceptions, demystifying widespread myths, tackling persistent mistruths, and overthrowing conventional wisdom about JavaScript. His eternal enemies are all the commoners who pause at a WTF moment, shrug their shoulders, and say resignedly, “Just JavaScript, eh?”

So what better way to test my newfound knowledge than by addressing all these WTFs?

The format of this post is slightly awkward, since I can’t link to sections within each chapter of YDKJS, but I’ll generally address each WTF with a direct quote from YDKJS. The first three words of the quote will be a link to the relevant chapter, so just Ctrl+F in the chapter until you find the context of the quote.

WTFJS

Brian Leroux’s WTFJS was the original “Weird Things From JavaScript” talk, before everybody got tired of the same nits about type coercion and IEEE 754 floating-point numbers over and over again. YDKJS only has two rules about type coercion, and I’ll add a third:

  1. If either side of the comparison can have true or false values, don’t ever, EVER use ==.
  2. If either side of the comparison can have [], "", or 0 values, seriously consider not using ==.
  3. Otherwise, use == to allow type coercion, and === to forbid it.

Now, on to the play-by-play.

0:00: Introduction.

1:08: The so-called “falsy” values list is: undefined, null, false, +0, -0, NaN, and "".

1:50: Rule 1. null != false makes sense, but the others are two of the seven items on the “bad list”. Don’t do this!

2:30: Brian explains the next few WTFs.

3:30: Rule 2. 0 == "0" actually kinda makes sense, but 0 == "" definitely doesn’t.

3:45: null and undefined can be treated as indistinguishable for comparison purposes, if you use the == loose equality operator to allow their mutual implicit coercion.

4:00: There is a String (capital S) object wrapper form that pairs with the primitive string type. Here, 'I am a string' is a primitive string (after all, typeof 'asdf' == 'string'), so it’s obviously not an instance of any object.

4:20: Chapter 4 and chapter 3 of Types & Grammar cover coercion (String('a')) and natives (new String('a')) respectively. Coercion gives a primitive string, and natives give the String object wrapper. Brian explains how == coerced the object wrapper into its string representation, giving us the result we expect by Rule 3. It’s not so weird after all!

5:31: Brian explains this.

5:48: The typeof operator inspects the type of the given value, and always returns one of seven string values. So typeof anything that’s not a string, number, boolean, undefined, function, or symbol is object.

6:07: Values that are typeof "object" (such as an array) are additionally tagged with an internal [[Class]] property. This property can generally be revealed indirectly by borrowing the default Object.prototype.toString(..) method called against the value.

6:40: Brian explains the next few WTFs.

9:00: If either operand to + is a string, the operation will be string concatenation. But the - operator is defined only for numeric subtraction, so a - 0 forces a’s value to be coerced to a number.

9:27: If the first character was '0', the guess was that you wanted to interpret the string as an octal (base-8) number. But as of ES5, parseInt(..) no longer guesses octal, and you’d have to do parseInt('0o8') instead.

9:49: This was the only WTF in this talk that wasn’t covered in YDKJS. Going straight to the specifications for Number, parseFloat and parseInt, we see that Number and parseFloat explicitly check for a StrDecimalLiteralInfinity—while parseInt does not. This makes sense because IEEE 754 has representations for infinity, but infinity is not an integer.

10:21: The trailing portion of a decimal value after the ., if 0, is optional. And it’s kinda pretty!

10:54: Some developers use the double tilde ~~ to truncate the decimal part of a number. This short section in YDKJS explains everything about ~~x and why its behaviour differs from Math.round(x), Math.floor(x), and x | 0, so you can give that a quick read.

11:13: Like most modern languages, including practically all scripting languages, the implementation of JavaScript’s numbers is based on the “IEEE 754” standard. All this behaviour is specified in that standard.

12:18: Brian explains this.

12:50: The actual error is Uncaught TypeError: Right-hand side of 'instanceof' is not callable, which makes sense.

13:03: NaN literally stands for “not a number”, though this label/description is very poor and misleading, as we’ll see shortly. It would be much more accurate to think of NaN as being “invalid number,” “failed number,” or even “bad number,” than to think of it as “not a number.” But NaN is a primitive number, so again, it’s not an instance of any object.

13:20: Brian explains this, but it’s worth noting that the isNaN(..) utility has a fatal flaw, so read that to find out why.

13:43: It’s kinda fun. This is just handy.

14:00: Brian explains this rather well, But remember Rule 2!

14:55: Rule 3 gives us the result we expect.

15:08: This is a long-standing bug in JS, but one that is likely never going to be fixed. This is also the only real WTF in the entire talk.

15:22: Brian explains this rather quickly. "constructor" is a string. "constructor".constructor is the function that gets called when you run new String(). "constructor".constructor.constructor is the constructor of that function, which is the function that gets called when you run new Function(). And new Function(..)() returns a new function and calls it.

16:15: This is just a hack for people who forget to write foo = new Foo() instead of foo = Foo(). If you use the new ES6 class syntax, you’ll get an Uncaught TypeError: Class constructor Foo cannot be invoked without 'new', so you’ll never forget it.

17:12: Conclusion.

Javascript sucks and it doesn’t matter

Rob Ashton gave this talk a month before Brian Leroux, which is primarily about “it doesn’t matter” rather than “JavaScript sucks”. But the first few minutes does have a few WTFs, and it’s the third most-viewed WTFJS video on YouTube, so it’s worth a look.

0:00: Introduction.

1:38: What do we know about the ! unary operator? It explicitly coerces to a boolean using the ToBoolean rules (and it also flips the parity). So before [0] == ![0] is even processed, it’s actually already translated to [0] == false […] the right-hand side [0] will go through a ToPrimitive coercion […] which then is ToNumber coerced to 0 for the right-hand side value, which is falsy.

2:14: This WTF wasn’t covered in YDKJS, so we jump to the specs for Array.prototype.toString(), which calls Array.prototype.join(), which in step 7c says: “If element is undefined or null, let next be the empty String; otherwise, let next be ? ToString(element).” [].toString() comes from step 5 and 8: “Let R be the empty String. Return R.” That makes sense, but the special check for undefined or null… WTF?!

2:54: Brian Leroux explains this at 12:19 of his talk.

3:16: WTF, JSFuck!?

3:40: Scopes. This. Classes. Sugar. Tools. As Rob elaborates in the rest of his talk, JavaScript had them all, even back in 2012!

WAT

Gary Bernhardt gave this talk ten months before the previous, so maybe this is really the original WTFJS talk, before all the obscenity crept in.

0:00: Ruby.

1:21: If either operand to + is a string (or becomes one with the above steps!), the operation will be string concatenation.

1:51: Another commonly cited JS gotcha is this exact example.

2:26: {} is interpreted as a standalone {} empty block (which does nothing). Finally, + {} is an expression that explicitly coerces (see Chapter 4) the {} to a number, which is NaN.

3:03: “This is actually the only line in this entire presentation that’s reasonable.”

3:33: The - operator is defined only for numeric subtraction, so a - 0 forces a’s value to be coerced to a number, which is NaN.

3:54: Conclusion.

wtfjs.com

Because this post is apparently in reverse chronological order now, turns out that the original original WTFJS was Brian Leroux’s wtfjs.com. In fact, we’ve seen most of the WTFs already in the three videos above, respectively titled “Brian”, “Rob”, and “Gary”:

What the… JavaScript?

It’s 4:30 am, so let’s hastily conclude with a link to a popular wtfjs repo, and the most popular WTFJS video on YouTube, by Kyle Simpson himself: