JavaScript: Hitting Bottom #3 – The Crucible of Truthiness

Some programming languages judge truth by the nature of non-zero values (C); some by non-false, non-nil values (Ruby). JavaScript follows it’s gut. If it seems true it’s usually true, if it seems false it’s usually false… usually.

There are two kinds of truth in JavaScript, things that take the `if` branch in `if-else` expressions; and things that == false.

Let’s start with things that == false:

    test("false idols", function() {
      equals(0 == false, true);
      equals('' == false, true);
      equals(' ' == false, true);
      equals('0' == false, true);
      equals([] == false, true);
 
      equals(null == false, false, "!");
      equals(undefined == false, false, "!");
    });

The first thing to note is that empty strings and strings that can be converted to the integer zero are == to false. The second thing to note is that `null` and `undefined` are not == to false. A lot of things in JavaScript == false, actually infinitely many. A lot of different arrays for example:

    test("false Arrays", function() {
      equals([0] == false, true);
      equals([''] == false, true);
      equals([' '] == false, true);
      equals(['0'] == false, true);
      equals([[]] == false, true);
      equals([false] != false, true, "!!! The `toString()` is 'false'");
      equals([null] == false, true);
      equals([undefined] == false, true);
 
      // It goes on like that, the toString ends up as '0'
      equals([[[[[[[[[0]]]]]]]]] == false, true);
 
      // This adds weight to the toString hypothesis
      equals([{toString: function() {return '';}}] == false, true);
      equals([{toString: function() {return '0';}}] == false, true);
    });

Some Objects with particular `toString` methods also == false:

    test("Objects with toString methods defined that are equivalent to false", function() {
      equals({toString: function(){return false}} == false, true, "!!");
      equals({toString: function(){return null;}} == false, true, "!!");
      equals({toString: function(){return '';}} == false, true, "!!");
      equals({toString: function(){return '0'}} == false, true, "!!");
      equals({toString: function(){return 'false'}} != false, true, "!!");
      equals({toString: function(){return undefined}} != false, true, "!!");
    });

Also, as mentioned previously, any strings that convert to the number zero such as:

    test("false Strings", function() {
      equals('                        ' == false, true);
      equals('            0.          ' == false, true);
      equals('0.0000000000000000000000' == false, true);
    });

Sounds like it would be hard to keep track of everything that evaluates to false… but don’t worry it has no effect on if-else statements. For if-else statements only things that are cast to the primitive `false` by Boolean matter. Wouldn’t Boolean cast everything that == false to be `false`? Not on your life!

    test("Boolean Goolean", function() {
      equals(Boolean(false) == false, true);
      equals(Boolean(0) == false, true);
      equals(Boolean('') == false, true);
      equals(Boolean(null) == false, true, "!!");
      equals(Boolean(undefined) == false, true, "!!");
      equals(Boolean(NaN) == false, true, "!!");
      equals(Boolean(' ') != false, true, "!!");
      equals(Boolean('0') != false, true, "!!");
      equals(Boolean([]) != false, true, "!!");
      equals(Boolean(new Boolean(false)) == true, true, "!!!");
    });

The exclamations are included for the ones that == false is different for. The only six things that evaluate to false in an if expression are: false, 0, null, undefined, NaN, and ”. Few enough to count on one hand! Aside from Boolean casting things that == false to `true` and casting things that != false to `false`, the one big thing to watch out for is that all Boolean objects are cast to `true` especially `new Boolean(false)` so don’t use it in your if statements!

Here’s a long boring series of tests to illustrate that the if-else branching does indeed match how Boolean casts things and not whether or not they == false. Exclamations where it’ll getcha. I’ll leave it as an exercise for the reader to skim to the bottom, say “hmmm…..” and then start browsing Reddit (or Hacker News).

    test("if statements", function() {
      var x;
      var ifX;
 
      x = undefined;
      if(x) { ifX = true; } else { ifX = false; }
      equals(ifX, false);
      if(x != false) { ifX = true; } else { ifX = false; }
      equals(ifX, true, "!!");
 
      x = null;
      if(x) { ifX = true; } else { ifX = false; }
      equals(ifX, false);
      if(x != false) { ifX = true; } else { ifX = false; }
      equals(ifX, true, "!!");
 
      x = false;
      if(x) { ifX = true; } else { ifX = false; }
      equals(ifX, false);
 
      x = true;
      if(x) { ifX = true; } else { ifX = false; }
      equals(ifX, true)
 
      x = 0;
      if(x) { ifX = true; } else { ifX = false; }
      equals(ifX, false);
 
      x = NaN;
      if(x) { ifX = true; } else { ifX = false; }
      equals(ifX, false);
      if(x != false) { ifX = true; } else { ifX = false; }
      equals(ifX, true, "!!");
 
      x = '';
      if(x) { ifX = true; } else { ifX = false; }
      equals(ifX, false);
 
      x = ' ';
      if(x) { ifX = true; } else { ifX = false; }
      equals(ifX, true);
      if(x != false) { ifX = true; } else { ifX = false; }
      equals(ifX, false, "!!");
 
      x = '0';
      if(x) { ifX = true; } else { ifX = false; }
      equals(ifX, true, "!");
      if(x != false) { ifX = true; } else { ifX = false; }
      equals(ifX, false, "!!");
 
      x = [];
      if(x) { ifX = true; } else { ifX = false; }
      equals(ifX, true, "!");
      if(x != false) { ifX = true; } else { ifX = false; }
      equals(ifX, false, "!!");
 
      x = {};
      if(x) { ifX = true; } else { ifX = false; }
      equals(ifX, true);
 
      x = {toString: function(){return '';}};
      if(x) { ifX = true; } else { ifX = false; }
      equals(ifX, true);
 
      x = new Boolean(false);
      if(x) { ifX = true; } else { ifX = false; }
      equals(ifX, true, "!!!")
      if(x != false) { ifX = true; } else { ifX = false; }
      equals(ifX, false, "!")
    });

JavaScript: Hitting Bottom #2 – FUNdefined

What better way to understand a language than to understand the basic types? And what can be more basic than `undefined`?

    /**
     * Undefined is generally well behaved.
     */
    test("FUNdefined", function() {
      equals(undefined > 0, false);
      equals(undefined < 0, false);
      equals(undefined >= 0, false);
      equals(undefined <= 0, false);
      equals(undefined == 0, false);
      equals(undefined === 0, false);
 
      equals(undefined == null, true);
      equals(undefined == false, false);
      equals(undefined == true, false);
 
      equals(undefined === null, false);
      equals(undefined === false, false);
      equals(undefined === true, false);
 
      equals(undefined == undefined, true);
      equals(undefined >= undefined, false, "!");
      equals(undefined <= undefined, false, "!");
    });

Pretty much no surprises there. The only thing to watch out for is that `(undefined >= undefined) == false` which makes some sense mathematically, but it might make more sense for it to equal `undefined`.

But where’s the FUN? I wouldn’t have a reputation as a professional ranter about JavaScript if I didn’t rant about JavaScript…

    /**
     * null is pretty weird. It's almost like zero but not equal to false.
     */
    test("null", function() {
      equals(null > 0, false);
      equals(null < 0, false);
      equals(null == 0, false);
      equals(null >= 0, true, "!!!");
      equals(null <= 0, true, "!!!");
      equals(null === 0, false);
 
      equals(null == false, false, "!!");
      equals(null >= false, true, "!!!");
      equals(null <= false, true, "!!!");
 
      equals(null > true, false);
      equals(null < true, true, "!!!");
      equals(null == true, false);
      equals(null >= true, false);
      equals(null <= true, true, "!!!");
 
      equals(null == undefined, true);
      equals(null >= undefined, false, "!");
      equals(null <= undefined, false, "!");
 
      equals(null == null, true);
      equals(null >= null, true);
      equals(null <= null, true);
    });

Legend

  • “!”: “note this!”
  • “!!”: ‘hmm… strange.”
  • “!!!”: “WTF!?!?!”

This set of tests for the behavior of null has a ratio of 22/20 !s/line. That means that there is significant unexpected behavior around `null`. The `undefined` exploratory test only had 2/15 !s/line, much less crazy. Let me repeat the most insane part:

      equals(null == false, false, "!!");
      equals(null >= false, true, "!!!");
      equals(null <= false, true, "!!!");

I’ve basically given up on comprehension of this particular behavior and just settled for explanation and documentation instead.

So, what’s the point? Am I just picking on poor little old JavaScript? Partly, but my real aim is to document the myriad of strange and tantalizing (and infuriating) edge cases.

The moral of the story is to return `undefined` from methods where the result may be involved in a numeric comparison rather than null. Additionally `undefined` is the result returned by the empty function:

    equals((function(){})() === undefined, true);

so it’s more natural. The other recommendation is to always use the strict equality operator, it cuts out the most common weird edge cases. Tools like JSLint will point out situations where some subtle bugs can arise, use them!

The roller-coaster whirlwind-tour continues tomorrow with “The Crucible of Truthiness”. Stay tuned!

JavaScript: Hitting Bottom #1 – Constructors

Welcome to the first installment in an infinite series on JavaScript I like to call “Hitting Bottom”. JavaScript is an amazing language. Amazingly brutal and amazingly misunderstood. If you’re feeling “pretty confident” with your grasp on the JavaScript language then this series will destroy that confidence.

I was feeling pretty confident, I remembered feeling pretty confident about JavaScript a couple years ago too and having that confidence dashed upon learning more. Now that confidence is dashed again, but I’m building it up… slowly. And maybe I can bring you along with me this time, to spare you from waking up under an overpass on the information super highway, covered in CSS classes and your own vomit, pulling angle brackets out of your hair and scrounging around in your pocket for enough $() to purchase one more bottle of #B0053.

So, onwards.

Constructors, what are they? They are the functions that construct objects. Check it:

      equals({}.constructor, Object, "{}.constructor");
      equals([].constructor, Array, "[].constructor");
      equals(''.constructor, String, "''.constructor");
      equals(true.constructor, Boolean, "true.constructor");
      equals(false.constructor, Boolean, "false.constructor");
      equals((function(){}).constructor, Function, "anonymous function constructor");

Pretty cool. And if we were to make our own constructor JavaScript would be totally chill with that.

      function A() { }
      var a = new A();
      equals(a.constructor, A, "a.constructor");

That’s the wonderful thing about JavaScript, if you are doing regular things then everything works as expected.

      function B() { }
      var b = new B();
      equals(b.constructor, B, "b.constructor");
 
      B.prototype = new A();
      var b2 = new B();
 
      equals(b.constructor, B, "Constructor for existing object does not change after changing prototypes");
      equals(b2.constructor, A, "Constructor for new objects changes after changing prototypes");

So constructors are defined when the object is created, based on the prototype property inheritance chain. They don’t change for previously created objects when the prototype property changes, but newly created objects will have the new value for the constructor.

      function C() {}
      B.prototype = new C();
      var b3 = new B();
 
      equals(b3.constructor, C, "Constructor for new objects changes after changing prototypes again.");

It just keeps going deeper if you have more prototypes. Maybe there was like half a trick in there. Somehow I don’t think we’ve hit bottom yet.