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.
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
toString(), other rules apply to the Booleans. Booleans are cast as follows:
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.
- Which (of course) was the whole point of that
ifin the first place. [↩]
- For all intents and purposes,
valueOf()on an array is effectively wrapping
- And by that of course I mean: Don’t Be Too Clever. [↩]
About Rob FrieselSoftware 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 →
Fun read. You’re a good man…and thorough.
Null is another oddity — it’s not truey or falsey, it’s null. It basically forces a strict comparison.
>>> false == null
>>> ” == null
>>> 0 == null
>>> ‘null’ == null
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