Truth, Equality and JavaScript (original) (raw)
You don’t have to be a JavaScript novice to get confused by this…
if ([0]) { console.log([0] == true); //false console.log(!![0]); //true }
or this…
if ("potato") { console.log("potato" == false); //false console.log("potato" == true); //false }
The good news is that there is a standard and all browsers follow it. Some authors will tell you to fear coercion and and code against it. I hope to persuade you that coercion is a feature to be leveraged (or at the very least understood), not avoided…
Is x true? Does x equal y? Questions of truth and equality at the kernel of three major areas of JavaScript: conditional statements and operators (if, ternaries, &&, || etc.), the equals operator (==), and the strict equals operator (===). Lets see what happens in each case…
Conditionals
In JavaScript, all conditional statements and operators follow the same coercion paradigm. We’ll use the if
statement by way of example.
The construct if
( Expression ) Statement will coerce the result of evaluating the Expression to a boolean using the abstract method ToBoolean for which the ES5 spec defines the following algorithm:
Argument Type | Result |
---|---|
Undefined | false |
Null | false |
Boolean | The result equals the input argument (no conversion). |
Number | The result is false if the argument is +0, −0, or NaN;otherwise the result is true. |
String | The result is false if the argument is the empty String (its length is zero);otherwise the result is true. |
Object | true. |
This is the formula JavaScript uses to classify values as truthy (true
, "potato"
, 36
, [1,2,4]
and {a:16}
) or falsey (false
, 0
, ""
, null
and undefined
).
Now we can see why, in the introductory example, if ([0])
allows entry to the subsequent block: an array is an object and all objects coerce to true
.
Here’s a few more examples. Some results may be surprising but they always adhere to the simple rules specified above:
var trutheyTester = function(expr) { return expr ? "truthey" : "falsey"; }
trutheyTester({}); //truthey (an object is always true)
trutheyTester(false); //falsey trutheyTester(new Boolean(false)); //truthey (an object!)
trutheyTester(""); //falsey trutheyTester(new String("")); //truthey (an object!)
trutheyTester(NaN); //falsey trutheyTester(new Number(NaN)); //truthey (an object!)
The Equals Operator (==)
The == version of equality is quite liberal. Values may be considered equal even if they are different types, since the operator will force coercion of one or both operators into a single type (usually a number) before performing a comparison. Many developers find this a little scary, no doubt egged on by at least one well-known JavaScript guru who recommends avoiding the == operator altogether.
The avoidance strategy bothers me because you can’t master a language until you know it inside out – and fear and evasion are the enemies of knowledge. Moreover pretending == does not exist will not let you off the hook when it comes to understanding coercion because in JavaScript coercion is everywhere! Its in conditional expressions (as we’ve just seen), its in array indexing, its in concatenation and more. What’s more coercion, when used safely, can be an instrument of concise, elegant and readable code.
Anyway, rant over, lets take a look at the way ECMA defines how == works. Its really not so intimidating. Just remember that undefined
and null
equal each other (and nothing else) and most other types get coerced to a number to facilitate comparison:
Type(x) | Type(y) | Result |
---|---|---|
x and y are the same type | See Strict Equality (===) Algorithm | |
null | Undefined | true |
Undefined | null | true |
Number | String | x == toNumber(y) |
String | Number | toNumber(x) == y |
Boolean | (any) | toNumber(x) == y |
(any) | Boolean | x == toNumber(y) |
String or Number | Object | x == toPrimitive(y) |
Object | String or Number | toPrimitive(x) == y |
otherwise… | false |
Where the result is an expression the algorithm is reapplied until the result is a boolean. toNumber and toPrimitive are internal methods which convert their arguments according to the following rules:
ToNumber
Argument Type | Result |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | The result is 1 if the argument is true.The result is +0 if the argument is false. |
Number | The result equals the input argument (no conversion). |
String | In effect evaluates Number(string)“abc” -> NaN“123” -> 123 |
Object | Apply the following steps:1. Let primValue be ToPrimitive(input argument, hint Number).2. Return ToNumber(primValue). |
ToPrimitive
Argument Type | Result |
---|---|
Object | (in the case of equality operator coercion) if valueOf returns a primitive, return it. Otherwise if toString returns a primitive return it. Otherwise throw an error |
otherwise… | The result equals the input argument (no conversion). |
Here are some examples – I’ll use pseudo code to demonstrate step-by-step how the coercion algorithm is applied:
[0] == true;
//EQUALITY CHECK... [0] == true;
//HOW IT WORKS... //convert boolean using toNumber [0] == 1; //convert object using toPrimitive //[0].valueOf() is not a primitive so use... //[0].toString() -> "0" "0" == 1; //convert string using toNumber 0 == 1; //false!
“potato” == true;
//EQUALITY CHECK... "potato" == true;
//HOW IT WORKS... //convert boolean using toNumber "potato" == 1; //convert string using toNumber NaN == 1; //false!
“potato” == false;
//EQUALITY CHECK... "potato" == false;
//HOW IT WORKS... //convert boolean using toNumber "potato" == 0; //convert string using toNumber NaN == 0; //false!
object with valueOf
//EQUALITY CHECK... crazyNumeric = new Number(1); crazyNumeric.toString = function() {return "2"}; crazyNumeric == 1;
//HOW IT WORKS... //convert object using toPrimitive //valueOf returns a primitive so use it 1 == 1; //true!
object with toString
//EQUALITY CHECK... var crazyObj = { toString: function() {return "2"} } crazyObj == 1;
//HOW IT WORKS... //convert object using toPrimitive //valueOf returns an object so use toString "2" == 1; //convert string using toNumber 2 == 1; //false!
The Strict Equals Operator (===)
This one’s easy. If the operands are of different types the answer is always false. If they are of the same type an intuitive equality test is applied: object identifiers must reference the same object, strings must contain identical character sets, other primitives must share the same value. NaN
, null
and undefined
will never === another type. NaN
does not even === itself.
Type(x) | Values | Result |
---|---|---|
Type(x) different from Type(y) | false | |
Undefined or Null | true | |
Number | x same value as y (but not NaN) | true |
String | x and y are identical characters | true |
Boolean | x and y are both true or both false | true |
Object | x and y reference same object | true |
otherwise… | false |
Common Examples of Equality Overkill
//unnecessary if (typeof myVar === "function");
//better if (typeof myVar == "function");
..since typeOf
returns a string, this operation will always compare two strings. Therefore == is 100% coercion-proof.
//unnecessary var missing = (myVar === undefined || myVar === null);
//better var missing = (myVar == null);
…null and undefined are == to themselves and each other.
Note: because of the (very minor) risk that the undefined
variable might get redefined, equating to null is slightly safer.
//unnecessary if (myArray.length === 3) {//..}
//better if (myArray.length == 3) {//..}
…enough said 😉
Further Reading
Peter van der Zee: JavaScript coercion tool
A nice summation of the equality coercion process, replete with an impressive automated tutorial
Andrea Giammarchi: JavaScript Coercion Demystified
ECMA-262 5th Edition
11.9.3 The Abstract Equality Comparison Algorithm
11.9.6 The Strict Equality Comparison Algorithm
9.1 toPrimitive
9.2 toBoolean
9.3 toNumber