false == wat?

I came across an interesting blog post the other day laying out the results from comparing false in JavaScript to various values using ==. The results were rather interesting but the author didn’t explain the odd results. I recommend checking out the post I linked to above, but I’ll copy and paste the list of comparisons here and then explain why JavaScript produces these seemingly odd results:

false == 0 //true

false == '0' //true

false == '' //true

false == [] //true

false == [[]] //true

false == [0] //true

false == 000 //true

false == '000' //true

false == false //true

false == null //false

false == undefined //false

This results in some fun quirks like:

false == '0' // true

false == '' // true

// And yet…

'0' == '' // false!

It’s due to unexpected results like this that JavaScript experts often recommend using the === comparison instead of ==. But why?

The answer is that when you use the == comparison, JavaScript attempts to convert both sides of the comparison to the same type (this also happens with !=, >, <, >=, and <= comparisons). With the === (and !==) comparison, the comparison will automatically return false if both sides are of different types.

So how does JavaScript do this exactly? How is it that false == [0]?

In JavaScript, there are 4 types (basically). We have Objects (which also includes arrays and functions), Strings, Booleans, and Numbers (JavaScript doesn’t have an integer type, all numbers are treated as signed floats).

JavaScript has built-in rules for comparing the three primitive types (strings, numbers, and booleans).

How one type of primitive becomes another

Strings, numbers, and booleans are the three primitive types of JavaScript. For the sake of this article, this means that JavaScript knows all about these types and can automatically compare them to each other, converting them when necessary.

The rules for comparing the three primitives:

Strings turn into numbers via Number(str). The rules are that empty strings become 0, numeric characters get converted the way you might expect, and NaN (Not A Number) is returned if there are any non-numeric characters (with the exception of a minus or plus sign at the front or a single period in the middle).

Number('') // 0, parseFloat would've returned NaN

Number('0') // 0

Number('000') // 0

Number('-1.1') // -1.1

Number('0hi') // NaN, parseFloat would've returned 0

Number('4') // 4

When comparing numbers to booleans, it’s the booleans that turn into numbers, with false becoming 0 and true becoming 1.

false == 0 // true

false == 0.1 // false

true == 1 // true

true == 4 // false

false == 4 // false

Now what about strings being compared to booleans? The rule is that a string first turns into a number and the boolean is also turned into a number, and then the two numbers are compared. Basically, the number is the lingua franca of types in JavaScript.

false == '' // true, since '' becomes 0 and so does false

false == '000' // true, since '000' becomes 0

true == '1' // true, since '1' and true both become 1

false == 'hi' // false, since 'hi' becomes NaN and false becomes 0

true == 'hi' // false, since true becomes 1

The valueOf() toString()

When comparing objects and functions with each other, the result is true if the two values point to the same object (and false otherwise), it’s that simple (again, arrays are also objects and follow the same rule).

obj1a = obj1b = {}

obj2 = {}

obj1a == obj1b // true

obj1a == obj2 // false

[] == [] // false

When comparing an object with a primitive value (string/number/boolean) JavaScript will first attempt to call .valueOf() on the object. If it gets back a primitive type, it will compare that to the other value.

({ valueOf: function () { return 1 } }) == 1 // true

({ valueOf: function () { return 1 } }) == 0 // false

({ valueOf: function () { return '1' } }) == true // true

By default, an object’s valueOf() method will just return the object. If that happens, then the toString() will be attempted, which is provided by all objects by default, including functions. In fact, for functions, it returns the entire implementation of the function as a string!

({ toString: function () { return 4 } }) == 4 // true

({

toString: function () { return 1 },

valueOf: function () { return 0 }

}) == 0 // true, this shows that valueOf is tried first

func = function (){ /* hi */ }

func.toString() // 'function (){ /* hi */ }'

func == 'function (){ /* hi */ }' // true

[0].toString() // '0', arrays have a default .toString() that automatically does a .join()

0 == [0] // true, [0] becomes '0' which becomes 0

false == [0] // true, false becomes 0 too

[] == [] // false, since the two objects are different instances

[] == +[] // true, since +[] becomes '' which becomes 0, then the other array is forced to convert to a string the same way

But keep in mind that this only works when comparing an object/function with a string/number/boolean. If you compare two different objects, even with identical toString() or valueOf() methods, the result will be false since no conversion will occur.

null == undefined

These are the weird ones. You pretty much need to memorize that they equal each other, but nothing else.

null == null // true

undefined == undefined // true

null == undefined // true

null == false // false

null == true // false

They deserve each other.

But… why?!

While we now know how the two values on either side of a comparison are converted, why are they converted at all? Most similar dynamically languages (e.g. python + ruby) don’t convert the two values before comparing them.

I’ve googled for an answer to this, but came up empty. My best guess is that since JavaScript was initially created to enable client-side form validation, the auto-type conversion makes it easy to compare user input from forms (which is always a string), to numbers. For example:

form = document.getElementById('user_form')

if (form.age < 18) return showError();

But this still strikes me as a bit sloppy since the user might enter non-numeric characters.

If you have evidence of the true answer, please comment here!