Hey, I'm writing a book too :) Click the link below to preorder and save ;)
»» Get the Only Reference You'll "ever" Need on JavaScript Engineering Interviews, Now! ««
April 27th in Basics, Tutorial by .

to Equal or not to Equal, That’s the Problem!

equality

equality

At the end of this tutorial, you’ll be able to explain why the following code produces the associated output below — where the assert function prints “FAIL:” if its first parameter is falsey, and “PASS:” if it’s first parameter is truthy, as described in this Factory Pattern post:

This code…

if ([0]) {
	log('in initial if...');
	assert([0] == true, "[0] == true");
	assert(!![0] == true, "!![0] == true");
}

if ('Ninja') {
	log('in second if...');
	assert('Ninja' == false, "'Ninja' == false");
	assert('Ninja' == true, "'Ninja' == true");
}	

produces the following output:

in initial if...
FAIL: [0] == true
PASS: !![0] == true
in second if...
FAIL: 'Ninja' == false
FAIL: 'Ninja' == true

But how?! — If you don’t know how JavaScript coercing works, it can be pretty mind-boggling. Besides, this output is enough to stun even the most skilled developers for a few seconds the first time they see ;)

You can click here, to get the entire source code for this tutorial (5.09Kb zipped archive) »»

So let us begin our journey by observing the typeof operator:

The typeof Operator

As its name implies, the typeof operator is used to detect JavaScript objects’ types.

However, other than testing whether an object or attribute is defined or not (via typeof test == ‘undefined’), the typeof operator doesn’t have too much practical use. Let’s analyze the following test to understand why:

log('typeof test');
assert(typeof 'Katana' == 'string', "typeof 'Katana' == 'string'");
assert(typeof new String("Katana") == 'object', 'typeof new String("Katana") == "object"');			
assert(typeof 3.14 == 'number', "typeof 3.14 == 'number'");
assert(typeof new Number(3.14) == 'object', "typeof new Number(3.14) == 'object'");
assert(typeof true == 'boolean', "typeof true == 'boolean'");
assert(typeof new Boolean(true) == 'object', "typeof new Boolean(true) == 'object'");
assert(typeof new Date() == 'object', "typeof new Date() == 'object'");
assert(typeof new Error() == 'object', "typeof new Error() == 'object'");
assert(typeof new Array(1,2,3) == 'object', "typeof new Array(1,2,3) == 'object'");
assert(typeof new Function('') == 'object', "typeof new Function('') == 'object'");
assert(typeof new Function('') == 'function', "typeof new Function('') == 'function'");//Firefox
assert(typeof /hello/g == 'object', "typeof /hello/g == 'object'");
assert(typeof /hello/g == 'function', "typeof /hello/g == 'function'");//Nitro/V8
assert(typeof new RegExp('hello') == 'object', "typeof new RegExp('hello') == 'object'");
assert(typeof new RegExp('hello') == 'function', "typeof new RegExp('hello') == 'function'");//Nitro/V8
assert(typeof {} == 'object', "typeof {} == 'object'");
assert(typeof new Object() == 'object', "typeof new Object() == 'object'");

and the outcome of it will be:

PASS: typeof 'Katana' == 'string'
PASS: typeof new String("Katana") == "object"
PASS: typeof 3.14 == 'number'
PASS: typeof new Number(3.14) == 'object'
PASS: typeof true == 'boolean'
PASS: typeof new Boolean(true) == 'object'
PASS: typeof new Date() == 'object'
PASS: typeof new Error() == 'object'
PASS: typeof new Array(1,2,3) == 'object'
FAIL: typeof new Function('') == 'object'
PASS: typeof new Function('') == 'function'
PASS: typeof /hello/g == 'object'
FAIL: typeof /hello/g == 'function'
PASS: typeof new RegExp('hello') == 'object'
FAIL: typeof new RegExp('hello') == 'function'
PASS: typeof {} == 'object'
PASS: typeof new Object() == 'object'

See, most of the non-primitive types in JavaScript are regarded as “object“s, and there are some cross-browser inconsistencies in the edge cases. Having known that a “regular expression” is in fact an “object” has no practical use ;)

So is there a better way to detect objects’ types?
Yes, of course :)

A Better Way to Detect Types

Let’s create another method, to detect a more useful type information, using Object.prototype.toString

/** 
 * Returns the type information of the given object.
 * The type can be any of the following:
 * Array, Boolean, Date, Error, Function, JSON, 
 * Math, Number, Object, RegExp, String, Arguments.
 * @param {Object} obj - the object to check type against.
 * @param {String} type - the type to compare.
 * @return true if the object's type matches the type parameter,
 * false otherwise.
 */
function is(obj, type){
	var klass = Object.prototype.toString.call(obj).slice(8, -1);
	return obj !== undefined && obj !== null && klass === type;				
}

…and then do a new comparison with the same set of data as above, using this “is” function:

log('Object.prototype.toString test:')
assert( is('foo', 'String'), "is('foo', 'String')");			
assert( is(new String('foo'), 'String'), "is(new String('foo'), 'String')");
assert( is(3.14, 'Number'), "is(3.14, 'Number')");
assert( is(new Number(3.14), 'Number'), "is(new Number(3.14), 'Number')");
assert( is(true, 'Boolean'), "is(true, 'Boolean')");
assert( is(new Boolean(true), 'Boolean'), "is(new Boolean(true), 'Boolean')");
assert( is(new Date(), 'Date'), "is(new Date(), 'Date')"); 
assert( is(new Error(), 'Error'), "is(new Error(), 'Error')");
assert( is(new Array(1,2,3), 'Array'), "is(new Array(1,2,3), 'Array')");
assert( is(new Function(''), 'Function'), "is(new Function(''), 'Function')");
assert( is(/hello/g, 'RegExp'), "is(/hello/g, 'RegExp')");
assert( is(new RegExp('hello'), 'RegExp'), "is(new RegExp('hello'), 'RegExp')");
assert( is({}, 'Object'), "is({}, 'Object')");
assert( is(new Object(), 'Object'), "is(new Object(), 'Object')");

The outcome will be more meaningful:

PASS: is('foo', 'String')
PASS: is(new String('foo'), 'String')
PASS: is(3.14, 'Number')
PASS: is(new Number(3.14)
PASS: is(true, 'Boolean')
PASS: is(new Boolean(true), 'Boolean')
PASS: is(new Date(), 'Date')
PASS: is(new Error(), 'Error')
PASS: is(new Array(1,2,3), 'Array')
PASS: is(new Function(''), 'Function')
PASS: is(/hello/g, 'RegExp')
PASS: is(new RegExp('hello'), 'RegExp')
PASS: is({}, 'Object')
PASS: is(new Object(), 'Object')

In order to check the type of an object, use Object.prototype.toString.
Because that’s the only reliable way of doing so ;)

Other than checking whether a variable is defined or not (as in the example below), typeof should be avoided at all costs.

assert(typeof magic == 'undefined', 'typeof magic == "undefined"');
PASS: typeof magic == "undefined"

The instanceof Operator

With Native Types

The instanceof operator is used to test whether an object is a descendant of a certain type. It’s behavior is “complicated”, to say the least, for native JavaScript types.

Let’s see with an example:

log('Using instanceof with native types');
assert(new String('Samurai') instanceof String, "new String('Samurai') instanceof String");
assert(new String('Samurai') instanceof Object, "new String('Samurai') instanceof String");
assert('Samurai' instanceof String, "'Samurai' instanceof String");
assert('Samurai' instanceof Object, "'Samurai' instanceof Object");

And the outcome will be:

PASS: new String('Samurai') instanceof String
PASS: new String('Samurai') instanceof String
FAIL: 'Samurai' instanceof String
FAIL: 'Samurai' instanceof Object

As you see, instanceof is pretty useless when used with native types, because it only works when you use it for testing the objects in constructor form.

With User-Defined Objects

Using instanceof with user-defined objects is another story though:

//Using instanceof with user defined objects
function Samurai() {}
function Ronin() {}
Ronin.prototype = new Samurai();

log('Using instanceof with user-defined types.')
assert(new Ronin() instanceof Ronin, "new Ronin() instanceof Ronin");
assert(new Samurai() instanceof Ronin, "new Samurai() instanceof Ronin");

The outcome will be as you’d expect:

PASS: new Ronin() instanceof Ronin
FAIL: new Samurai() instanceof Ronin

Caveat:
instanceof does not work on objects that
origin from different JavaScript contexts (e.g. different documents in a web browser),
since their constructors will not be the exactly the same object.

The instanceof operator should only be used when dealing with custom made objects.
Just like the typeof operator, every other use of it should be avoided.

Strict Comparison

JavaScript is a loose-typed language, when comparing object with the equality operator ==, the operands will be coerced to the nearest compatible type (we’ll come to that soon).

However, when === and !== operators are used, no type conversion takes place.
The concept can be better understood with an example:

log('comparing objects (by ref):');
var hakinen = {};
assert({} === {}, '{} === {}');
assert(new String('Samurai') === 'Samurai', "new String('Samurai') === 'Samurai'");
assert(new Number(10) === 10, 'new Number(10) === 10');
assert(hakinen === hakinen, 'hakinen === hakinen');
assert("" === "0", '"" === "0"');
assert(0 === "", "0 === ''");
assert(0 === "0", '0 === "0"');
assert(false === "false", 'false === "false"');
assert(false === "0", 'false === "0"');
assert(false === undefined, 'false === undefined');
assert(0 === undefined, '0 === undefined');
assert(' \t\r\n' === 0, "' \\t\\r\\n' === 0");

And the result will be:

FAIL: {} === {}
FAIL: new String('Samurai') === 'Samurai'
FAIL: new Number(10) === 10
PASS: hakinen === hakinen
FAIL: "" === "0"
FAIL: 0 === ''
FAIL: 0 === "0"
FAIL: false === "false"
FAIL: false === "0"
FAIL: false === undefined
FAIL: 0 === undefined
FAIL: ' \t\r\n' === 0

As seen, the === operator compares objects by ref, and requires two objects to be strictly identical.

Type Coercion

Since JavaScript is a loose-typed language, it will apply type coercion wherever possible.

Here are some examples:

log('using type coercion:');
assert("" == "0", '"" == "0"');
assert(0 == "", "0 == ''");
assert(0 == "0", '0 == "0"');
assert(false == "false", 'false == "false"');
assert(false == "0", 'false == "0"');
assert(false == undefined, 'false == undefined');
assert(0 == undefined, '0 == undefined');
assert(' \t\r\n' == 0, "' \\t\\r\\n' == 0");
//object to number
assert(new Number(42) === 42, "new Number(10) === 42");
//number to number
assert(Number(42) === 42, "Number(42) === 42");
//implicit type conversion.
assert(new Number(42) + 0 === 42, "new Number(42) + 0 === 42");

The output will be as follows:

FAIL: "" == "0"
PASS: 0 == ''
PASS: 0 == "0"
FAIL: false == "false"
PASS: false == "0"
FAIL: false == undefined
FAIL: 0 == undefined
PASS: ' \t\r\n' == 0
FAIL: new Number(10) === 42
PASS: Number(42) === 42
PASS: new Number(42) + 0 === 42

There are certain, deterministic and platform-independent rules governing these conversions.
I repeat, when it comes to type coercion, all browsers follow the same sets of rules, that’s a very rare thing when it comes to front-end development ;)

Let’s examine each and every single one of these rules, to gain a crystal-clear understanding of JavaScript type coercion:

Coercion in Conditionals

Conditionals i.e. if(expression) are converted according to the following ruleset:

Expression Result
undefined false
null false
true true
false false
+0, -0 or NaN false
any other number true
” (empty string) false
any other string true

Coercion in Equality (==) Operator

Fear not! Fear is the enemy of knowledge.

The == operator’s coercion rules is a bit more complicated, but they make sense none the less. That’s why I can find at least one JavaScript Guru, who’ll recommend avoiding the == operator alltogether.

Though, I don’t think you should be afraid 1. if you know the ropes, and 2. if you do your own type conversion whenever possible (using parseInt, parseFloat… double negation… and the like) without leaving it to the “guestimation” of the JavaScript interpreter.

Here are JavaScript engine’s type conversion rules for x == y :

x y outcome
same type same type see === comparison rules below
null undefined true
undefined null true
Number String x == ToNumber(y)
String Number ToNumber(x) == y
Boolean * ToNumber(x) == y
* Boolean x == ToNumber(y)
* Object x == ToPrimitive(y)
Object * ToPrimitive(x) == y
otherwise false

The algorithm is repeated until the result comes out to be a boolean.

Where ToNumber operates as follows:

argument result
undefined NaN
null +0
true 1
false +0
Number itself (no conversion)
String evaluate Number(argument)
Object let prim = ToPrimitive(argument, hint Number); return ToNumber(prim)

Where ToPrimitive works as follows:

argument result
Object if argument.valueOf() returns a primitive return it; else if argument.toString() returns a primitive return it; else throw an Error.
otherwise result equals argument (no conversion)

For the sake of completeness, the rules of strict conversion === are as follows:

For x === y:

if x and y have different types; the result is false,
undefined === undefined ; null === null are both true,
Numbers: If x has same value as y (except for NaN), result is true,
String: true if x and y have identical characters,
Boolean: If x and y are both true, or both false; then result is true,
Object: If x and y point to the same object (they are equal by ref), then result is true.

It will be clearer when we go over these rules with examples:

assert([0] == true, "[0] == true");
//this will fail
//convert boolean using toNumber
//[0] == 1;
//convert object using toPrimitive
//[0].valueOf() is not a primitive... pass
//[0].toString() -> "0"
//"0" == 1;
//convert string using toNumber
//0 == 1; //false!

assert('Ninja' == true, "'Ninja' == true");			
//this will fail
//convert boolean using toNumber
//"Ninja" == 1;
//convert string using toNumber
//NaN == 1; //false!

assert('Ninja' == false, "'Ninja' == false");
//this will fail for a similar reason.

assert(null == 0, "null == 0");	
//null converts to +0, and +0 is not equal to 0, fail.

assert(null >= 0, "null >= 0");
//+0 > 0 this will pass.
			
confused = new Number(42);
confused.toString = function() {return "52"};
assert(confused == 42, "confused == 42");			
//this will pass
//convert object using toPrimitive
//valueOf returns a primitive so use it
//42 == 42; //true!
			
var moreConfused  = {
    toString: function() {return "42"}
}
assert(moreConfused == 42, "moreConfused == 42");
//this will pass
//convert object using toPrimitive
//valueOf returns an object so use toString
//"42" == 42;
//convert string using toNumber
//42 == 42; //true!

You see, once you know the rules; it’s pretty easy :)

Some misuses of ==

//unnecessary -- typeof already returns a string.
if (typeof delegate === "function");
//better
if (typeof delegate == "function");
			
//unnecessary -- null == undefined and undefined == null.
var missing =  (batman === undefined ||  batman === null);
//better
var missing = (batman == null);
			
//unnecessary -- length property is a number.
if (myArray.length === 3) {/*...*/}
//better
if (myArray.length == 3) {/*...*/}

Hope you get the point ;)

References & More to Read

Hope this article made you solve the mysteries of equality in JavaScript.
You see, with JavaScript, a seemingly trivial concept like equality turns out to be an ocean to explore. That’s why I’m loving this language! — and that’s why I from time to time, hate it ;)

What do you think?
Isn’t JavaScript great?
I’d love to hear your comments :)

It's me: Volkan! Jack of all Trades, Samurai of JavaScript ;) Since 2003, I’ve been doing front-end development on client-heavy AJAX web applications. Currently, I'm a JavaScript Hacker at "SocialWire". Before that I was a VP of Technology at "GROU.PS", a well-known do-it-yourself social networking platform that allows people to come together and form interactive communities around a shared interest or affiliation. Before that, I was a JavaScript Engineer at "LiveGO", a globally known social mash-up. Before that, I was the CTO of a business network ("cember.net") which was acquired by Xing AG for around 4.2M Euros. See my linkedin profile to find more about me; I also share worth-following bits an pieces on twitter.

VIsit Volkan Özçelik's website

Leave a Reply





o2.js _
Fork Ribbon