Comparison Operations in JavaScript
Abstract equality / inequality and type conversion
The Problem
The abstract equality and inequality operators (==
and !=
) convert their operands if the operand types do not match. This type coercion is a common source of confusion about the results of these operators, in particular, these operators aren't always transitive as one would expect.
"" == 0; // true A
0 == "0"; // true A
"" == "0"; // false B
false == 0; // true
false == "0"; // true
"" != 0; // false A
0 != "0"; // false A
"" != "0"; // true B
false != 0; // false
false != "0"; // false
The results start to make sense if you consider how JavaScript converts empty strings to numbers.
Number(""); // 0
Number("0"); // 0
Number(false); // 0
The Solution
In the statement false B
, both the operands are strings (""
and "0"
), hence there will be no type conversion and since ""
and "0"
are not the same value, "" == "0"
is false
as expected.
One way to eliminate unexpected behavior here is making sure that you always compare operands of the same type. For example, if you want the results of numerical comparison use explicit conversion:
var test = (a, b) => Number(a) == Number(b);
test("", 0); // true;
test("0", 0); // true
test("", "0"); // true;
test("abc", "abc"); // false as operands are not numbers
Or, if you want string comparison:
var test = (a, b) => String(a) == String(b);
test("", 0); // false;
test("0", 0); // true
test("", "0"); // false;
Side-note: Number("0")
and new Number("0")
isn't the same thing! While the former performs a type conversion, the latter will create a new object. Objects are compared by reference and not by value which explains the results below.
Number("0") == Number("0"); // true;
new Number("0") == new Number("0"); // false
Finally, you have the option to use strict equality and inequality operators which will not perform any implicit type conversions.
"" === 0; // false
0 === "0"; // false
"" === "0"; // false
Further reference to this topic can be found here:
Which equals operator (== vs ===) should be used in JavaScript comparisons?.
NaN Property of the Global Object
NaN
("Not a Number") is a special value defined by the IEEE Standard for Floating-Point Arithmetic, which is used when a non-numeric value is provided but a number is expected (1 * "two"
), or when a calculation doesn't have a valid number
result (Math.sqrt(-1)
).
Any equality or relational comparisons with NaN
returns false
, even comparing it with itself. Because, NaN
is supposed to denote the result of a nonsensical computation, and as such, it isn’t equal to the result of any other nonsensical computations.
1 * "two" === NaN; //false
NaN === 0; // false
NaN === NaN; // false
Number.NaN === NaN; // false
NaN < 0; // false
NaN > 0; // false
NaN > 0; // false
NaN >= NaN; // false
NaN >= "two"; // false
Non-equal comparisons will always return true
:
NaN !== 0; // true
NaN !== NaN; // true
Checking if a value is NaN
You can test a value or expression for NaN
by using the function Number.isNaN():
Number.isNaN(NaN); // true
Number.isNaN(0 / 0); // true
Number.isNaN("str" - 12); // true
Number.isNaN(24); // false
Number.isNaN("24"); // false
Number.isNaN(1 / 0); // false
Number.isNaN(Infinity); // false
Number.isNaN("str"); // false
Number.isNaN(undefined); // false
Number.isNaN({}); // false
You can check if a value is NaN
by comparing it with itself:
value !== value; // true for NaN, false for any other value
You can use the following polyfill for Number.isNaN()
:
Number.isNaN =
Number.isNaN ||
function (value) {
return value !== value;
};
By contrast, the global function isNaN()
returns true
not only for NaN
, but also for any value or expression that cannot be coerced into a number:
isNaN(NaN); // true
isNaN(0 / 0); // true
isNaN("str" - 12); // true
isNaN(24); // false
isNaN("24"); // false
isNaN(Infinity); // false
isNaN("str"); // true
isNaN(undefined); // true
isNaN({}); // true
ECMAScript defines a “sameness” algorithm called SameValue
which, since ECMAScript 6, can be invoked with Object.is
. Unlike the ==
and ===
comparison, using Object.is()
will treat NaN
as identical with itself (and -0
as not identical with +0
):
Object.is(NaN, NaN); // true
Object.is(+0, 0); // false
(NaN ===
NaN + // false
0) ===
0; // true
You can use the following polyfill for Object.is()
(from MDN):
if (!Object.is) {
Object.is = function (x, y) {
// SameValue algorithm
if (x === y) {
// Steps 1-5, 7-10
// Steps 6.b-6.e: +0 != -0
return x !== 0 || 1 / x === 1 / y;
} else {
// Step 6.a: NaN == NaN
return x !== x && y !== y;
}
};
}
Points to note
NaN itself is a number, meaning that it does not equal to the string "NaN", and most importantly (though perhaps unintuitively):
typeof NaN === "number"; //true
Short-circuiting in boolean operators
The and-operator (&&
) and the or-operator (||
) employ short-circuiting to prevent unnecessary work if the outcome of the operation does not change with the extra work.
In x && y
, y
will not be evaluated if x
evaluates to false
, because the whole expression is guaranteed to be false
.
In x || y
, y
will not be evaluated if x
evaluated to true
, because the whole expression is guaranteed to be true
.
Example with functions
Take the following two functions:
function T() {
// True
console.log("T");
return true;
}
function F() {
// False
console.log("F");
return false;
}
**Example 1**
T() && F(); // false
Output:
'T'
'F'
**Example 2**
F() && T(); // false
Output:
'F'
**Example 3**
T() || F(); // true
Output:
'T'
**Example 4**
F() || T(); // true
Output:
'F'
'T'
Short-circuiting to prevent errors
var obj; // object has value of undefined
if (obj.property) {
} // TypeError: Cannot read property 'property' of undefined
if (obj.property && obj !== undefined) {
} // Line A TypeError: Cannot read property 'property' of undefined
Line A: if you reverse the order the first conditional statement will prevent the error on the second by not executing it if it would throw the error
if (obj !== undefined && obj.property) {
} // no error thrown
But should only be used if you expect undefined
if (typeof obj === "object" && obj.property) {
} // safe option but slower
Short-circuiting to provide a default value
The ||
operator can be used to select either a "truthy" value, or the default value.
For example, this can be used to ensure that a nullable value is converted to a non-nullable value:
var nullableObj = null;
var obj = nullableObj || {}; // this selects {}
var nullableObj2 = { x: 5 };
var obj2 = nullableObj2 || {}; // this selects {x: 5}
Or to return the first truthy value
var truthyValue = { x: 10 };
return truthyValue || {}; // will return {x: 10}
The same can be used to fall back multiple times:
envVariable || configValue || defaultConstValue; // select the first "truthy" of these
Short-circuiting to call an optional function
The &&
operator can be used to evaluate a callback, only if it is passed:
function myMethod(cb) {
// This can be simplified
if (cb) {
cb();
}
// To this
cb && cb();
}
Of course, the test above does not validate that cb
is in fact a function
and not just an Object
/Array
/String
/Number
.
Abstract Equality (==)
Operands of the abstract equality operator are compared after being converted to a common type. How this conversion happens is based on the specification of the operator:
Specification for the ==
operator:
7.2.13 Abstract Equality Comparison
The comparison `x == y`, where `x` and `y` are values, produces `true` or `false`. Such a comparison is performed as follows:- - If `Type(x)` is the same as `Type(y)`, then:
- - **a.** Return the result of performing Strict Equality Comparison `x === y`.
- - If `x` is `null` and `y` is `undefined`, return `true`. - If `x` is `undefined` and `y` is `null`, return `true`. - If `Type(x)` is `Number` and `Type(y)` is `String`, return the result of the comparison `x == ToNumber(y)`. - If `Type(x)` is `String` and `Type(y)` is `Number`, return the result of the comparison `ToNumber(x) == y`. - If `Type(x)` is `Boolean`, return the result of the comparison `ToNumber(x) == y`. - If `Type(y)` is `Boolean`, return the result of the `comparison x == ToNumber(y)`. - If `Type(x)` is either `String`, `Number`, or `Symbol` and `Type(y)` is `Object`, return the result of the comparison `x == ToPrimitive(y)`. - If `Type(x)` is Object and `Type(y)` is either `String`, `Number`, or `Symbol`, return the result of the comparison `ToPrimitive(x) == y`. - Return `false`.
Examples:
1 == 1; // true
1 == true; // true (operand converted to number: true => 1)
1 == "1"; // true (operand converted to number: '1' => 1 )
1 == "1.00"; // true
1 == "1.00000000001"; // false
1 == "1.00000000000000001"; // true (true due to precision loss)
null == undefined; // true (spec #2)
1 == 2; // false
0 == false; // true
0 == undefined; // false
0 == ""; // true
Null and Undefined
The differences between null
and undefined
null
and undefined
share abstract equality ==
but not strict equality ===
,
null == undefined; // true
null === undefined; // false
They represent slightly different things:
undefined
represents the absence of a value, such as before an identifier/Object property has been created or in the period between identifier/Function parameter creation and it's first set, if any.null
represents the **intentional absence of a value** for an identifier or property which has already been created.
They are different types of syntax:
undefined
is a property of the global Object, usually immutable in the global scope. This means anywhere you can define an identifier other than in the global namespace could hideundefined
from that scope (although things can still beundefined
)null
is a word literal, so it's meaning can never be changed and attempting to do so will throw an Error.
The similarities between null
and undefined
null
and undefined
are both falsy.
if (null) console.log("won't be logged");
if (undefined) console.log("won't be logged");
Neither null
or undefined
equal false
(see this question).
false == undefined; // false
false == null; // false
false === undefined; // false
false === null; // false
Using undefined
- If the current scope can't be trusted, use something which evaluates to undefined, for example
void 0;
. - If
undefined
is shadowed by another value, it's just as bad as shadowingArray
orNumber
. - Avoid setting something as
undefined
. If you want to remove a property bar from an Objectfoo
,delete foo.bar;
instead. - Existence testing identifier
foo
againstundefined
could throw a Reference Error, usetypeof foo
against"undefined"
instead.
Logic Operators with Booleans
var x = true,
y = false;
AND
This operator will return true if both of the expressions evaluate to true. This boolean operator will employ short-circuiting and will not evaluate y
if x
evaluates to false
.
x && y;
This will return false, because y
is false.
OR
This operator will return true if one of the two expressions evaluate to true. This boolean operator will employ short-circuiting and y
will not be evaluated if x
evaluates to true
.
x || y;
This will return true, because x
is true.
NOT
This operator will return false if the expression on the right evaluates to true, and return true if the expression on the right evaluates to false.
!x;
This will return false, because x
is true.
Logic Operators with Non-boolean values (boolean coercion)
Logical OR (||
), reading left to right, will evaluate to the first truthy value. If no truthy value is found, the last value is returned.
var a = "hello" || ""; // a = 'hello'
var b = "" || []; // b = []
var c = "" || undefined; // c = undefined
var d = 1 || 5; // d = 1
var e = 0 || {}; // e = {}
var f = 0 || "" || 5; // f = 5
var g = "" || "yay" || "boo"; // g = 'yay'
Logical AND (&&
), reading left to right, will evaluate to the first falsy value. If no falsey value is found, the last value is returned.
var a = "hello" && ""; // a = ''
var b = "" && []; // b = ''
var c = undefined && 0; // c = undefined
var d = 1 && 5; // d = 5
var e = 0 && {}; // e = 0
var f = "hi" && [] && "done"; // f = 'done'
var g = "bye" && undefined && "adios"; // g = undefined
This trick can be used, for example, to set a default value to a function argument (prior to ES6).
var foo = function (val) {
// if val evaluates to falsey, 'default' will be returned instead.
return val || "default";
};
console.log(foo("burger")); // burger
console.log(foo(100)); // 100
console.log(foo([])); // []
console.log(foo(0)); // default
console.log(foo(undefined)); // default
Just keep in mind that for arguments, 0
and (to a lesser extent) the empty string are also often valid values that should be able to be explicitly passed and override a default, which, with this pattern, they won’t (because they are falsy).
Empty Array
/* ToNumber(ToPrimitive([])) == ToNumber(false) */
[] == false; // true
When [].toString()
is executed it calls [].join()
if it exists, or Object.prototype.toString()
otherwise. This comparison is returning true
because [].join()
returns ''
which, coerced into 0
, is equal to false ToNumber.
Beware though, all objects are truthy and Array
is an instance of Object
:
// Internally this is evaluated as ToBoolean([]) === true ? 'truthy' : 'falsy'
[] ? "truthy" : "falsy"; // 'truthy'
Automatic Type Conversions
Beware that numbers can accidentally be converted to strings or NaN (Not a Number).
JavaScript is loosely typed. A variable can contain different data types, and a variable can change its data type:
var x = "Hello"; // typeof x is a string
x = 5; // changes typeof x to a number
When doing mathematical operations, JavaScript can convert numbers to strings:
var x = 5 + 7; // x.valueOf() is 12, typeof x is a number
var x = 5 + "7"; // x.valueOf() is 57, typeof x is a string
var x = "5" + 7; // x.valueOf() is 57, typeof x is a string
var x = 5 - 7; // x.valueOf() is -2, typeof x is a number
var x = 5 - "7"; // x.valueOf() is -2, typeof x is a number
var x = "5" - 7; // x.valueOf() is -2, typeof x is a number
var x = 5 - "x"; // x.valueOf() is NaN, typeof x is a number
Subtracting a string from a string, does not generate an error but returns NaN (Not a Number):
"Hello" - "Dolly"; // returns NaN
Equality comparison operations
JavaScript has four different equality comparison operations.
SameValue
It returns true
if both operands belong to the same Type and are the same value.
Note: the value of an object is a reference.
You can use this comparison algorithm via Object.is
(ECMAScript 6).
Examples:
Object.is(1, 1); // true
Object.is(+0, -0); // false
Object.is(NaN, NaN); // true
Object.is(true, "true"); // false
Object.is(false, 0); // false
Object.is(null, undefined); // false
Object.is(1, "1"); // false
Object.is([], []); // false
This algorithm has the properties of an equivalence relation:
- Reflexivity:
Object.is(x, x)
istrue
, for any valuex
- Symmetry:
Object.is(x, y)
istrue
if, and only if,Object.is(y, x)
istrue
, for any valuesx
andy
. - Transitivity: If
Object.is(x, y)
andObject.is(y, z)
aretrue
, thenObject.is(x, z)
is alsotrue
, for any valuesx
,y
andz
.
SameValueZero
It behaves like SameValue, but considers +0
and -0
to be equal.
You can use this comparison algorithm via Array.prototype.includes
(ECMAScript 7).
Examples:
[1].includes(1); // true
[+0].includes(-0); // true
[NaN].includes(NaN); // true
[true].includes("true"); // false
[false].includes(0); // false
[1].includes("1"); // false
[null].includes(undefined); // false
[[]].includes([]); // false
This algorithm still has the properties of an equivalence relation:
- Reflexivity:
[x].includes(x)
istrue
, for any valuex
- Symmetry:
[x].includes(y)
istrue
if, and only if,[y].includes(x)
istrue
, for any valuesx
andy
. - Transitivity: If
[x].includes(y)
and[y].includes(z)
aretrue
, then[x].includes(z)
is alsotrue
, for any valuesx
,y
andz
.
Strict Equality Comparison
It behaves like SameValue, but
- Considers
+0
and-0
to be equal. - Considers
NaN
different than any value, including itself
You can use this comparison algorithm via the ===
operator (ECMAScript 3).
There is also the !==
operator (ECMAScript 3), which negates the result of ===
.
Examples:
1 === 1; // true
+0 === -0; // true
NaN === NaN; // false
true === "true"; // false
false === 0; // false
1 === "1"; // false
null === undefined; // false
[] === []; // false
This algorithm has the following properties:
- Symmetry:
x === y
istrue
if, and only if, y === xis
true, for any values
xand
y`. - Transitivity: If
x === y
andy === z
aretrue
, thenx === z
is alsotrue
, for any valuesx
,y
andz
.
But is not an equivalence relation because
NaN
is not reflexive:NaN !== NaN
Abstract Equality Comparison
If both operands belong to the same Type, it behaves like the Strict Equality Comparison.
Otherwise, it coerces them as follows:
undefined
andnull
are considered to be equal- When comparing a number with a string, the string is coerced to a number
- When comparing a boolean with something else, the boolean is coerced to a number
- When comparing an object with a number, string or symbol, the object is coerced to a primitive
If there was a coercion, the coerced values are compared recursively. Otherwise the algorithm returns false
.
You can use this comparison algorithm via the ==
operator (ECMAScript 1).
There is also the !=
operator (ECMAScript 1), which negates the result of ==
.
Examples:
1 == 1; // true
+0 == -0; // true
NaN == NaN; // false
true == "true"; // false
false == 0; // true
1 == "1"; // true
null == undefined; // true
[] == []; // false
This algorithm has the following property:
- Symmetry:
x == y
istrue
if, and only if,y == x
istrue
, for any valuesx
andy
.
But is not an equivalence relation because
NaN
is not reflexive:NaN != NaN
- Transitivity does not hold, e.g.
0 == ''
and0 == '0'
, but'' != '0'
Relational operators (<, <=, >, >=)
When both operands are numeric, they are compared normally:
1 < 2; // true
2 <= 2; // true
3 >= 5; // false
true < false; // false (implicitly converted to numbers, 1 > 0)
When both operands are strings, they are compared lexicographically (according to alphabetical order):
"a" < "b"; // true
"1" < "2"; // true
"100" > "12"; // false ('100' is less than '12' lexicographically!)
When one operand is a string and the other is a number, the string is converted to a number before comparison:
"1" < 2; // true
"3" > 2; // true
true > "2"; // false (true implicitly converted to number, 1 < 2)
When the string is non-numeric, numeric conversion returns NaN
(not-a-number). Comparing with NaN
always returns false
:
1 < "abc"; // false
1 > "abc"; // false
But be careful when comparing a numeric value with null
, undefined
or empty strings:
1 > ""; // true
1 < ""; // false
1 > null; // true
1 < null; // false
1 > undefined; // false
1 < undefined; // false
When one operand is a object and the other is a number, the object is converted to a number before comparison.So null
is particular case because Number(null);//0
new Date(2015) < 1479480185280; // true
null >
-1(
//true
{
toString: function () {
return 123;
},
}
) >
122; //true
Inequality
Operator !=
is the inverse of the ==
operator.
Will return true
if the operands aren't equal.
The javascript engine will try and convert both operands to matching types if they aren't of the same type.
Note: if the two operands have different internal references in memory, then false
will be returned.
Sample:
1 != "1"; // false
1 != 2; // true
In the sample above, 1 != '1'
is false
because, a primitive number type is being compared to a char
value. Therefore, the Javascript engine doesn't care about the datatype of the R.H.S value.
Operator: !==
is the inverse of the ===
operator.
Will return true if the operands are not equal or if their types do not match.
Example:
1 !== "1"; // true
1 !== 2; // true
1 !== 1; // false
Grouping multiple logic statements
You can group multiple boolean logic statements within parenthesis in order to create a more complex logic evaluation, especially useful in if statements.
if ((age >= 18 && height >= 5.11) || (status === "royalty" && hasInvitation)) {
console.log("You can enter our club");
}
We could also move the grouped logic to variables to make the statement a bit shorter and descriptive:
var isLegal = age >= 18;
var tall = height >= 5.11;
var suitable = isLegal && tall;
var isRoyalty = status === "royalty";
var specialCase = isRoyalty && hasInvitation;
var canEnterOurBar = suitable || specialCase;
if (canEnterOurBar) console.log("You can enter our club");
Notice that in this particular example (and many others), grouping the statements with parenthesis works the same as if we removed them, just follow a linear logic evaluation and you'll find yourself with the same result. I do prefer using parenthesis as it allows me to understand clearer what I intended and might prevent for logic mistakes.
List of Comparison Operators
Operator | Comparison | Example |
---|---|---|
== | Equal | i == 0 |
=== | Equal Value and Type | i === "5" |
!= | Not Equal | i != 5 |
!== | Not Equal Value or Type | i !== 5 |
> | Greater than | i > 5 |
< | Less than | i < 5 |
>= | Greater than or equal | i >= 5 |
<= | Less than or equal | i <= 5 |
Bit fields to optimise comparison of multi state data
A bit field is a variable that holds various boolean states as individual bits. A bit on would represent true, and off would be false. In the past bit fields were routinely used as they saved memory and reduced processing load. Though the need to use bit field is no longer so important they do offer some benefits that can simplify many processing tasks.
For example user input. When getting input from a keyboard's direction keys up, down, left,right you can encode the various keys into a single variable with each direction assigned a bit.
Example reading keyboard via bitfield
var bitField = 0; // the value to hold the bits
const KEY_BITS = [4, 1, 8, 2]; // left up right down
const KEY_MASKS = [0b1011, 0b1110, 0b0111, 0b1101]; // left up right down
window.onkeydown = window.onkeyup = function (e) {
if (e.keyCode >= 37 && e.keyCode < 41) {
if (e.type === "keydown") {
bitField |= KEY_BITS[e.keyCode - 37];
} else {
bitField &= KEY_MASKS[e.keyCode - 37];
}
}
};
Example reading as an array
var directionState = [false, false, false, false];
window.onkeydown = window.onkeyup = function (e) {
if (e.keyCode >= 37 && e.keyCode < 41) {
directionState[e.keyCode - 37] = e.type === "keydown";
}
};
To turn on a bit use bitwise or |
and the value corresponding to the bit. So if you wish to set the 2nd bit bitField |= 0b10
will turn it on. If you wish to turn a bit off use bitwise and &
with a value that has all by the required bit on. Using 4 bits and turning the 2nd bit off bitfield &= 0b1101;
You may say the above example seems a lot more complex than assigning the various key states to a array. Yes It is a little more complex to set but the advantage comes when interrogating the state.
If you want to test if all keys are up.
// as bit field
if(!bitfield) // no keys are on
// as array test each item in array
if(!(directionState[0] && directionState[1] && directionState[2] && directionState[3])){
You can set some constants to make things easier
// postfix U,D,L,R for Up down left right
const KEY_U = 1;
const KEY_D = 2;
const KEY_L = 4;
const KEY_R = 8;
const KEY_UL = KEY_U + KEY_L; // up left
const KEY_UR = KEY_U + KEY_R; // up Right
const KEY_DL = KEY_D + KEY_L; // down left
const KEY_DR = KEY_D + KEY_R; // down right
You can then quickly test for many various keyboard states
if ((bitfield & KEY_UL) === KEY_UL) { // is UP and LEFT only down
if (bitfield & KEY_UL) { // is Up left down
if ((bitfield & KEY_U) === KEY_U) { // is Up only down
if (bitfield & KEY_U) { // is Up down (any other key may be down)
if (!(bitfield & KEY_U)) { // is Up up (any other key may be down)
if (!bitfield ) { // no keys are down
if (bitfield ) { // any one or more keys are down
The keyboard input is just one example. Bitfields are useful when you have various states that must in combination be acted on. Javascript can use upto 32 bits for a bit field. Using them can offer significant performance increases. They are worth being familiar with.
Remarks
When using boolean coercion, the following values are considered "falsy":
false
0
""
(empty string)null
undefined
NaN
(not a number, e.g.0/0
)document.all
¹ (browser context)
Everything else is considered "truthy".