Skip to content

Instantly share code, notes, and snippets.

@Zarel
Last active November 16, 2023 10:30
Show Gist options
  • Save Zarel/a425272a5f7ef6484658 to your computer and use it in GitHub Desktop.
Save Zarel/a425272a5f7ef6484658 to your computer and use it in GitHub Desktop.
Understanding the JavaScript in the Wat talk

The Wat talk has been floating around for a long time:

https://www.destroyallsoftware.com/talks/wat

There are probably lots of articles explaining exactly what's going on in this video, but I'll add this to the mix, since JavaScript typecasting is actually a lot simpler than most people think.

Useful background knowledge

To understand typecasting in JavaScript, you actually only need to know three rules:

  1. If you try to do something that doesn't make sense because the types are wrong (like use a math function on a string), JavaScript will do type conversions until it makes sense. This means first converting non-primitives (objects, including arrays) to primitives (strings, by default), and if that's not enough, converting primitives to numbers.

    (Objects will by default convert to strings, the only exception is if you override valueOf: {valueOf: () => 5} will convert to a number rather than a string.)

  2. The + operator is special: if one side is a string, the other side will be converted to a string. Otherwise, conversions work like normal (so boolean + boolean will convert both sides to a number.)

  3. "" when cast to number is 0, but other non-number strings when cast to number become NaN. So Number("1e1") is 10 and Number("1f1") is NaN.

Once you know those three things, JavaScript typecasting will never trip you up, which includes most pitfalls involving ==.

Other == pitfalls: JavaScript follows IEEE 754, or whatever standard it was that says NaN != NaN. Also, null == undefined and undefined == null, but neither of them equal anything else. Otherwise, just do the conversions I listed above and you'll get the correct answer.

Other JavaScript pitfalls: You shouldn't start a line with {}, since it'll be interpreted as an empty block, not an empty object literal. This only matters in a REPL, since you'd pretty much never start a line with {} in any other context. Use ({}) if you need to start a line with an empty object literal in a REPL.

Converting an array to string will get you array.join(',') (i.e. the string representation of the array's elements, separated by ,). Converting an object to string when it doesn't have a different toString function defined (like array does) will get you the string '[object Object]'.

Note

In reality, you should never need to know any of this for writing your own code, because you shouldn't use any implicit type conversions at all (besides the idiomatic typecasts mentioned below).

The exceptions (idiomatic typecasts) are '' + x as a shorter way to write String(x), !!x as a shorter way to write Boolean(x), and +x as a shorter way to write Number(x). Again, DO NOT use implicit typecasts for anything else; most code styles and linters ban them. The best way to check equality is === (just never use ==). === will be false if the types don't match, and work like == otherwise (NaN === NaN is still false, you'll need to use isNaN, but remember isNaN will implicitly convert to number; you can use ES5's Number.isNaN or you can just use typeof x === 'number' && isNaN(x) if you're not sure if x is a number).

On the other hand, this is pretty much everything you need to answer those "do you REALLY know JavaScript?" quizzes with all those trick questions.

Wat doesn't use it, but other trick questions will use ,. In JavaScript, like in C, , outside of variable declarations is basically a higher-precedence version of ; that only works for expressions. (so a(),b() is basically the same as a();b(); except it's an expression that evaluates to the value of b(), like for instance 1 + (a(),b()) means you run a() and then get 1 + b()). You should never intentionally use it.

The Wat talk

So now we know enough to tell us what's going on here:

[] + []
-> String([]) + String([])
-> "" + ""
-> ""

[] gets cast into a string "", so you get "" + "" which concatenates to "".

[] + {}
-> String([]) + String({})
-> "" + "[object Object]"
-> "[object Object]"

[] and {} get cast into strings and concatenated

{} + []
-> +[]
-> Number([])
-> Number(String([]))
-> Number("")
-> 0

{} is interpreted as an empty code block. +[] is a cast to number. Casting an object to number generally involves casting it to string first, since that's the only primitive most support being converted to.

{} + {}
-> +{}
-> Number({})
-> Number(String({}))
-> Number("[object Object]")
-> NaN

Same thing going on, but with {} instead of [].

"wat" + 1
-> "wat" + String(1)
-> "wat" + "1"
-> "wat1"

"wat" - 1
-> Number("wat") - 1
-> NaN - 1
-> NaN

Pretty straightforward.

And now you have it! You've learned pretty much nothing you need to know to do actual JavaScript coding, but plenty you need to not being confused by people intentionally trying to confuse you! I hope this at least helps when you encounter a weird error some day because you forgot to use a linter!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment