found drama

get oblique

the pleasures and perils of JavaScript’s promiscuous comparison operator

by Rob Friesel

Spoiler alert: toString has its mitts in everything.

This all started when we saw a candidate put the following into an exercise we had given him:

if (arrayToTest == '')

Our first thought was well that seems wrong… And though it seems like something that shouldn’t work, it did–when the array contained a single item, and that single item was an empty string1.

This realization led to one of those eye-popping moments where you find it necessary to poke and prod at JavaScript’s inner machinations, to sort through the apparent inconsistencies, to make a best-effort at getting your mind around the myriad ways that JavaScript’s loose typing is both a blessing and a curse. Thus:

The first comparison ought to make sense2. Though the arrays have the same contents, they are different arrays because they are different array literals. So far so good, but now the exciting quirks begin.

In the second example, we might think: OK, the == operator is popping off that first (and only) item and comparing it against the right-hand value. Which is close but not quite right (and more on that in a moment). But let’s assume for a moment that that is true (which it’s not), that == is popping off the that first/only item. If that were true, then the third example (with the ===) ought to work; but it doesn’t. And that should not be a surprise; === requires the compared items to be the same type, and an array and a number are definitely not the same type. But that leads us to the fourth example and an important hint about what’s going on.

In that fourth example, it starts to become clear. Is == popping off the only element in the array and then coercing/casting it from a number to a string in much the same way as 1 == '1'? We’re getting warmer (but we already know that the answer is “no”). The == allows such a promiscuous comparison that it will coerce twice if necessary to get a truthy value. In light of that, consider that this (fourth) example actually has only one type coercion: valueOf()3 is called on the array to give back (effectively) '1'. Compare that with the second example where the array (on the left-hand side) is cast via valueOf() and then the number 1 (on the right) is cast with toString() as well.

Need more convincing? Check out the fifth and sixth examples.

But then things get weird when you start comparing with Boolean literals.

Given the previous examples, one might expect [true] == true to evaluate to true; the array will fall victim to that promiscuous valueOf(), and the true literal will get caught by toString(). Right? Not so fast. That evaluates to false; sanity check true == true if you want — but bear in mind that that is not the same comparison. But neither is [true] == 'true' — in that example, valueOf() plays on the array and the comparison is a success.

By now you’re probably asking: what the hell is going on?

Another quirk. Whereas basically all of the other types are coerced/cast via toString() or the one-two punch of valueOf()/toString(), other rules apply to the Booleans. Booleans are cast as follows: true becomes 1 and false becomes 0 — and neither are cast/coerced any further.

All terribly interesting trivia. And it is good knowledge to have when you run into this sort of esotetic thing. But really… if you’re intentionally comparing dissimilar types like that: you’re doing it wrong4.

  1. Which (of course) was the whole point of that if in the first place. []
  2. If it does not, do yourself a favor and review JavaScript’s reference types. The devil is in those details. []
  3. For all intents and purposes, valueOf() on an array is effectively wrapping toString(). []
  4. And by that of course I mean: Don’t Be Too Clever. []

About Rob Friesel

Software engineer by day, science fiction writer by night. Author of The PhantomJS Cookbook and a short story in Please Do Not Remove. View all posts by Rob Friesel →

3 Responses to the pleasures and perils of JavaScript’s promiscuous comparison operator

Patrick Fisher says:

Null is another oddity — it’s not truey or falsey, it’s null. It basically forces a strict comparison.

>>> false == null
false
>>> ” == null
false
>>> 0 == null
false
>>> ‘null’ == null
false

found_drama says:

@Patrick–

null isn’t such a wily beast once you get a handle on it; if you embrace “the Crockford equals” then the only time null might still trip you up is on typeof. But this business with the arrays? Talk about JavaScript’s warty parts.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*