Sunday, June 07, 2009

JavaScript equality cheat sheet

JavaScript is not a strongly-typed language, but it does have types and there is a difference between 42 and "42", though the language sometimes does its best to obscure this by resolving the differences between types according to its own rules. This is why the following expression evaluates as true:

if("42" == 42) alert('Spoon!');

Implicit string to numeric conversions like this are rather straightforward. However, it soon gets murky. The following expression is also true:

if(" " == 0) alert('What the?');

It gets worse. You might conclude from this that other values that equate to false like null or undefined variables are equal. If two things evaluate to false, and false is equal to itself, then this isn’t much of a stretch. Sadly this isn’t the case. undefined is not equal to 0, or false, or the empty string, but it is equal to null, even though all of them evaluate to false on their own.

Are you ready to curl up into a foetal position and cry yourself to sleep yet? Fortunately there is another way: the === operator. For the uninitiated, it follows the natural progression that one might expect:

!= Not equal == Equal === Really goddamn equal

The last operator takes JavaScript’s types (undefined, object, string, number, boolean and function) into account. So now there are two degrees of equality for each type. It’s the sort of thing that demands a cheat sheet to keep track of. To that end, and to the point of this blog, I have selected a set of values that evaluate to false and span the full set of JavaScript types except for function.

Note that the last column (" ") can be a whitespace string of any length, including newlines, spaces and tabs.

Table 1: Evaluation grid for ==
undefnullfalse0"0"""" "
undeftruetruefalsefalsefalsefalsefalse
nulltruetruefalsefalsefalsefalsefalse
falsefalsefalsetruetruetruetruetrue
0falsefalsetruetruetruetruetrue
"0"falsefalsetruetruetruefalsefalse
""falsefalsetruetruefalsetruefalse
" "falsefalsetruetruefalsefalsetrue
Table 2: Evaluation grid for ===
undefnullfalse0"0"""" "
undeftruefalsefalsefalsefalsefalsefalse
nullfalsetruefalsefalsefalsefalsefalse
falsefalsefalsetruefalsefalsefalsefalse
0falsefalsefalsetruefalsefalsefalse
"0"falsefalsefalsefalsetruefalsefalse
""falsefalsefalsefalsefalsetruefalse
" "falsefalsefalsefalsefalsefalsetrue

The most immediate pattern is that under “really goddamn equal”, all values presented here are equal only to themselves. Under conventional equality testing, null and undefined are off by themselves, while everything else is lumped together with the odd exception here and there.

The complications here stem from the common approach of most dynamically-typed languages of trying to derive the mode of comparison (lexical or numeric) from the type of the operands involved.

JavaScript, when faced with comparing 0.0 and "0", has to make a decision about whether to compare the values as strings or numbers. As numbers they are equal, as strings they are not. Programmers loathe this kind of uncertainty. It can be addressed by the the === operator or functions like parseInt/parseFloat, but you either lose the flexibility of a loosely-typed language or introduce cumbersome and error-prone code.

Another approach, used by Perl, is to have the operator drive the type of the values being compared by using a different set of operators for numeric and lexical comparisons. This retains the flexibility of a dynamically typed language but gives control back to the programmer.

Addendum: JavaScript Arrays

The behaviour of arrays in JavaScript is peculiar enough to warrant its own mention. There seemed no way to include arrays in the above table without compromising its readability, so I present these observations in simple list form:

  1. The empty array almost deserves a table of its own. When declared either as [] or new Array(), it is equal to 0, false and "", but not undefined, null or "0". It is almost as if the comparison is performed on the array’s length property.
  2. The same evaluations hold true if the array contains a single element that evaluates to false (e.g., 0, "0", null, etc.), but not if there are two or more such elements. It is as if single-element arrays are converted to their element’s value for the comparison.
  3. Thereafter (at least one non-false element, or two or more elements of any kind) and none of the previous observations hold. It is likely therefore that neither points 1 or 2 are strictly correct, but that the actual process mimics this behaviour either by accident or design.

No comments:

Post a Comment