Monday, July 5, 2010

JavaScript conditional oversights and solutions

While this is nothing new or particularly noteworthy, I felt this post would be good to share after fixing a number of conditionals in various JavaScript code - as well as having received some interesting and surprising feedback after suggesting the same fixes to others.

The code in question looked something like this:

var x;
// …
if(x == ""){
  // …
}

While the exact validity of this code depends on the exact requirements being implemented, there are a few potential issues in what is otherwise a very simple block of code:

  • null is not checked for, which is often desirable as - while accepted as a best programming practice or not - null and an empty string ("") are usually treated (or expected to be treated) the same.
  • Looking from the other side of things, the if block will also be entered if x is any of the following (not guaranteed to be a complete list): 0 (a number), "0" (a string representation of the number), or false (a boolean).

Stated another way, when using the standard equals operator (==) in JavaScript, all of ["", 0, "0", false] are considered equal. The same applies with a separate set of [null, (undefined)], though no elements between sets are considered equal.

Verify the results for yourself, by running this code in your browser:

log(false == false);
log("" == false);
log(0 == false);
log("0" == false);

var x = {};
// x.y is undefined:
log(x.y == null);
// Another way is to obtain an undefined result is to use the void operator:
log(void(0) == null);

// Show that items between these 2 groups are not considered equal:
log(null != false);

My recommendation to all JavaScript developers is to instead use one of the following approaches:

  • To really test against an empty string (""), and only an empty string, use the strict equals operator (===). For example:
    var x;
    // …
    if(x === ""){
      // …
    }
    The same also applies for testing against 0, "0", or false.
  • From the other side of things, don't use an equals operator at all. Just use the variable in a conditional without an explicit operator or comparison, which is equivalent to wrapping with the Boolean(…) function. To test for a defined, non-null, and non-"false" value, the following 4 statements are all functionally identical, with the first and highlighted version being the preferred version:
    var x;
    // …
    if(x){
      // …
    }
    if(Boolean(x)){ /*…*/ }
    if(x != null && x == true){ /*…*/ }
    if(x !== null && typeof(x) != "undefined" && x !== "" && x !== 0 && x !== "0" && x !== false){ /*…*/ }
    Or for the reverse, by using the negated form:
    if(!x){ /*…*/ }
    if(!Boolean(x)){ /*…*/ }
    if(x == null || x == false || isNaN(x)){ /*…*/ }
    if(x === null || typeof(x) == "undefined" || x === "" || x === 0 || x === "0" || x === false || isNaN(x)){ /*…*/ }
    Assuming one of these code blocks meet the functional requirement, the first line forms are the preferred usage.

1 comment:

Anonymous said...

I'm just curious about the "interesting and surprising" feedback you've got. Might we hear some of these stories to keep us awake at night, please :-D?

We do some php at work, as well as js, and (pun unintended ^_^) things are equal:

"0001e-10000" == 0,
"0X000000000" == 0,
"0001e-10000" and "0X000000000" are != in JS and == in php,
...
"8" == 010,
"010" != 010,
"010" == 10.
...
and so on

(btw, the last three are not only about booleans, but also about the stupid C notation for octals -- why 0x was a good idea for heX notation at the time C was created, but 0c or 0t weren't good candidates for oCTal, is something that amazes me).

And yet, some of my colleagues just don't get why this kind of pitfalls are dangerous...