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
- ECMA262 5th edition
- JavaScript Coercion Tool
- JavaScript Coercion Demystified
- truth, equality, and JavaScript
…
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

