== | null | undefined | "null" | "undefined" | false | -0 | 0 | 0n | "-0" | "0" | [0] | "" | [] | "false" | true | 1 | 1.0 | 1n | "1" | [1] | "1.0" | NaN |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
null | true | true | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false |
undefined | true | true | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false |
"null" | false | false | true | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false |
"undefined" | false | false | false | true | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false |
false | false | false | false | false | true | true | true | true | true | true | true | true | true | false | false | false | false | false | false | false | false | false |
-0 | false | false | false | false | true | true | true | true | true | true | true | true | true | false | false | false | false | false | false | false | false | false |
0 | false | false | false | false | true | true | true | true | true | true | true | true | true | false | false | false | false | false | false | false | false | false |
0n | false | false | false | false | true | true | true | true | true | true | true | true | true | false | false | false | false | false | false | false | false | false |
"-0" | false | false | false | false | true | true | true | true | true | false | false | false | false | false | false | false | false | false | false | false | false | false |
"0" | false | false | false | false | true | true | true | true | false | true | true | false | false | false | false | false | false | false | false | false | false | false |
[0] | false | false | false | false | true | true | true | true | false | true | true | false | false | false | false | false | false | false | false | false | false | false |
"" | false | false | false | false | true | true | true | true | false | false | false | true | true | false | false | false | false | false | false | false | false | false |
[] | false | false | false | false | true | true | true | true | false | false | false | true | true | false | false | false | false | false | false | false | false | false |
"false" | false | false | false | false | false | false | false | false | false | false | false | false | false | true | false | false | false | false | false | false | false | false |
true | false | false | false | false | false | false | false | false | false | false | false | false | false | false | true | true | true | true | true | true | true | false |
1 | false | false | false | false | false | false | false | false | false | false | false | false | false | false | true | true | true | true | true | true | true | false |
1.0 | false | false | false | false | false | false | false | false | false | false | false | false | false | false | true | true | true | true | true | true | true | false |
1n | false | false | false | false | false | false | false | false | false | false | false | false | false | false | true | true | true | true | true | true | false | false |
"1" | false | false | false | false | false | false | false | false | false | false | false | false | false | false | true | true | true | true | true | true | false | false |
[1] | false | false | false | false | false | false | false | false | false | false | false | false | false | false | true | true | true | true | true | true | false | false |
"1.0" | false | false | false | false | false | false | false | false | false | false | false | false | false | false | true | true | true | false | false | false | true | false |
NaN | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false | false |
typeof x | "object" | "undefined" | "string" | "string" | "boolean" | "number" | "number" | "bigint" | "string" | "string" | "object" | "string" | "object" | "string" | "boolean" | "number" | "number" | "bigint" | "string" | "object" | "string" | "number" |
Boolean(x) | false | false | true | true | false | false | false | false | true | true | true | false | true | true | true | true | true | true | true | true | true | false |
Number(x) | 0 | NaN | NaN | NaN | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | NaN | 1 | 1 | 1 | 1 | 1 | 1 | 1 | NaN |
String(x) | "null" | "undefined" | "null" | "undefined" | "false" | "0" | "0" | "0" | "-0" | "0" | "0" | "" | "" | "false" | "true" | "1" | "1" | "1" | "1" | "1" | "1.0" | "NaN" |
Object(x) | [object Object] | [object Object] | null | undefined | false | 0 | 0 | 0 | -0 | 0 | [0] | [] | false | true | 1 | 1 | 1 | 1 | [1] | 1.0 | NaN | |
x.constructor.name | TypeError: x is null | TypeError: x is undefined | "String" | "String" | "Boolean" | "Number" | "Number" | "BigInt" | "String" | "String" | "Array" | "String" | "Array" | "String" | "Boolean" | "Number" | "Number" | "BigInt" | "String" | "Array" | "String" | "Number" |
x.valueOf() | TypeError: x is null | TypeError: x is undefined | "null" | "undefined" | false | 0 | 0 | 0n | "-0" | "0" | [0] | "" | [] | "false" | true | 1 | 1 | 1n | "1" | [1] | "1.0" | NaN |
x.toString() | TypeError: x is null | TypeError: x is undefined | "null" | "undefined" | "false" | "0" | "0" | "0" | "-0" | "0" | "0" | "" | "" | "false" | "true" | "1" | "1" | "1" | "1" | "1" | "1.0" | "NaN" |
JavaScript is required to use this interactively.
Keep in mind that anything entered into these fields will be evaluated as JavaScript code. Do not enter suspicious code from outside sources.
false true boolean number bigint string symbol object function other throws exception
==
Definition of ==
in the specification
First of all, as defined in the specification, there are eight different types: undefined, null, boolean, string, symbol (values that can be used as attribute names but aren't strings), number (yes, NaN is a number), bigint, and object (which includes arrays, functions, dates, regular expressions, etc.). This definition differs from what the typeof
operator uses, in that in this definition null
is its own type (rather than being of type "object"
), and that in this definition functions are a type of object (rather than being their own type). I'm going to use this definition of type, because the rules for equality are simpler that way.
Second, there are two different equality operators, ==
and ===
. (!=
and !==
are the negation of ==
and ===
, respectively.) If the two values being compared are the same type, these operators give the same result. If the two values being compared are different types, ===
always returns false
, whereas ==
will generally perform some kind of type conversion. ==
has been in the language since the beginning, whereas ===
was added later. Some behaviors shared by both operators:
"á"
("a\u0301"
) is not equal to "á"
("\u00e1"
).
Type conversions for ==
are summarized in the following table:
undefined | null | boolean | number | string | bigint | symbol | object | |
---|---|---|---|---|---|---|---|---|
undefined | true | true | false | false | ||||
null | true | true | ||||||
boolean | false | Compare value | Convert to number | Convert to numbers | Convert to bigint | false | Convert to primitive | |
number | false | Convert to number | Compare value | Convert to number | Convert to ℝ | false | Convert to primitive | |
string | Convert to numbers | Convert to number | Compare value | Convert to bigint | ||||
bigint | Convert to bigint | Convert to ℝ | Convert to bigint | Compare value | ||||
symbol | false | false | Compare value | |||||
object | Convert to primitive | Convert to primitive | Compare reference |
That is:
undefined
and null
compare equal to each other, but not to anything else. (One exception)
true
and false
become 1 and 0. Converting a string to a number is different than calling parseFloat
: parseFloat
allows text after the number, which conversion to number doesn't allow; conversion to number handles hexadecimal values (starting with 0x
) which parseFloat doesn't; and conversion to number treats the empty string as 0, whereas parseFloat returns NaN. You can explicitly convert a string to a number using Number(str)
(this is different from new Number(str)
) or +str
. Converting to a number returns NaN if it fails, which is not equal to anything (even itself), so a string that can't be converted to a number does not compare equal to any number.
==
it just returns false
.
Number.MAX_SAFE_INTEGER
only compares equal to exactly one bigint (it doesn't try to round the bigint to a floating point number).
==
, which might perform further conversions. (If both are objects, then it compares references.)
For object == primitive and primitive == object comparisons, the value depends on what valueOf
returns. Unless you're doing something weird, the following table describes what happens (where obj
is the object and val
is the primitive value; +val
converts a boolean or string into a number, as described above):
valueOf returns | Examples | Result | Notes |
---|---|---|---|
null or undefined | (no standard objects do this) | false | |
boolean, number, string, bigint, or symbol | Boolean , Number , String , BigInt and Symbol objects | obj.valueOf() == val | See the table above |
object | Object , Array , Function , RegExp | obj.toString() == val | Might still compare equal to a number if toString returns a string that can be parsed as a number (including "" )
|
Date objects (special case) | obj.toString() == val |
That is, conversion to primitive uses valueOf
unless valueOf
returns an object, in which case it uses toString
. The default implementation of valueOf
is basically return this;
, so objects that don't define valueOf
use toString
.
Of the standard object types that don't implement valueOf
, there's one that's likely to return a string that can convert into number, and that is Array
. toString
on an array converts each element to a string and puts commas between them, but doesn't put any sort of brackets around the array. For instance, ["hi","there"].toString()
is "hi,there"
, and [2,3,4].toString()
is "2,3,4"
. If you have an array of one element, which is either a number (like [5]
) or a string containing a number (like ["5"]
), then converting the array to a string will result in a string that can successfully be converted into a number. Because of this, [5] == 5
and ["5"] == 5
both return true
. An empty array ([]
) or an array containing only an empty string ([""]
) converts to ""
, which is considered equal to 0.
The actual algorithm it uses: When the browser converts an object into a primitive type, it first tries the valueOf
function. If valueOf
returns undefined
or null
, then the values are not equal. If valueOf
returns a primitive value, then it's compared to the other value using ==
comparison (i.e., it may be further converted to a different type). If valueOf
is not a function or returns an object, then it tries toString
instead, using the same rules that it uses for valueOf
. If toString
is not a function or returns an object, then it throws TypeError
. However, there are two special cases:
Date
objects, it uses the same algorithm, except it tries toString
first and then valueOf
.
Symbol
objects, it bypasses valueOf and just directly uses the symbol value stored in the object internally. Since that's the same thing that valueOf
returns, it normally doesn't make a difference for ==
, unless you change the valueOf
function, in which case it will affect comparisons for Boolean
, Number
, and String
objects, but not Symbol
objects.
Additionally, there's one other weird exception for browsers: document.all == undefined
and null
(but not ===
), and typeof document.all
is "undefined"
. This is to keep compatibility with old webpages that use document.all
, but also to keep compatibility with old webpages that try to detect certain older browsers by checking if document.all
exists.
In general, if JavaScript wants to implicitly convert a value from one type to another, these are the rules it uses:
to boolean | to number | to bigint | to numeric | to string | to object | |
---|---|---|---|---|---|---|
from undefined
| false
| NaN | throw exception | NaN | "undefined"
| throw exception* |
from null
| false
| 0 | throw exception | 0 | "null"
| throw exception* |
from boolean | (no conversion) | 0 or 1 | 0n or 1n | 0 or 1 | "false" or "true"
| Boolean object
|
from number | false if ±0 or NaN
| (no conversion) | throw exception* | (no conversion) | string representation | Number object
|
from bigint | false if 0
| throw exception* | (no conversion) | (no conversion) | string representation (no "n") | BigInt object
|
from string | false if empty
| parse number (NaN if fail) | parse bigint (exception if fail) | parse number (NaN if fail) | (no conversion) | String object
|
from symbol | true
| throw exception | throw exception | throw exception | throw exception* | Symbol object
|
from object | true (except document.all )
| prefer valueOf
| prefer valueOf
| prefer valueOf
| prefer toString
| (no conversion) |
For conversions from objects, it tries valueOf
and toString
, using the same algorithm as for ==
, except there isn't an exception for Date
s, and conversion to string tries toString
first (except for Symbol
objects). After it converts the value to a primitive, it then tries to convert whatever primitive value it gets to the right type.
* Usually if you call Boolean
, Number
, etc. as a function, it behaves the same as an implicit type conversion. However, there are a few cases where it behaves differently:
Object(null)
and Object(undefined)
both create a new object, rather than throwing an exception.
BigInt
and Number
can convert between bigints and numbers, but implicit conversion can't. Passing a number with a fraction part or a number that isn't finite to BigInt
causes it to throw an exception, though. In earlier versions of JavaScript, +x
was the same as Number(x)
, but now +x
performs an implicit conversion to number, which means that it throws an exception for bigints. (This is different from other arithmetic operators, which return a bigint if they're given a bigint. This is because there's code that uses unary +
as an optimization hint that a value is a number.)
String
can convert symbols to strings, but only if it isn't used with new
, and only if the value is a primitive symbol and not an object that converts to a symbol. (BigInt
and Number
don't have those restrictions.)
Symbol
makes a new symbol instead.
<
, >
, <=
, >=
), if either or both of the operands is an object, they first try to convert it to a primitive with valueOf
, then toString
. If both operands (after conversion) are strings, then it does string comparison; if one argument is a string and the other is a bigint, then it converts the string to a bigint; otherwise, it converts non-bigint operands to numbers and does numeric comparison. Like ==
, bigints and numbers can be compared with each other without converting either.
Z
is less than lowercase a
and z
is less than (precomposed) accented á
, but also astral plane characters (above U+FFFF) are all less than U+E000.
false
.
false
. (This means e.g. "0.5" < 1
returns false
.)
Date
's valueOf
function returns the number of milliseconds since January 1, 1970 midnight UTC, which means that you can compare two dates with <
, >
, <=
, or >=
(but not ==
or !=
, since those compare references).
+
operator (x + y
), if either or both of the operands is an object, they first try to convert it to a primitive with valueOf
, then toString
, except for Date
s, which use toString
instead. If either operand (after conversion) is a string, then it does string concatenation; otherwise, it converts anything other than a bigint to a number and does addition. Trying to add a bigint and a number (or a bigint and a boolean/null
/undefined
) throws an exception.
-x
, ~x
, **
, *
, /
, %
, +
, -
, <<
, >>
, >>>
, &
, ^
, |
), it first tries to convert objects into primitives using valueOf
, then toString
. If neither operand is a bigint, then it converts both operands to numbers; if both operands are bigints, then it just performs the operation; if one operand is a bigint and the other isn't (and isn't an object whose valueOf
returns a bigint), then it throws an exception.
~
, both operands of &
, ^
, and |
, and the left operand of <<
and >>
, it converts to signed integers; for the right operand of <<
and >>
and both operands of >>>
, it converts to unsigned integers.
Date
s gives the number of milliseconds between the dates. Other operations are possible but not as useful (although dividing a date by 1000 gives Unix time, and taking a date mod 86400000 gives the milliseconds since the start of the day UTC).
Summary:
Operator | Date
| string and string | string and bigint | string and other | bigint and number/boolean/null
|
---|---|---|---|---|---|
== , !=
| string representation | string compare | convert to bigint | convert to number (except null )
| compare |
< , > , <= , >=
| milliseconds since 1970 | string compare | convert to bigint | convert to number | compare |
x + y
| string representation | string concat | string concat | string concat | exception |
Other arithmetic | milliseconds since 1970 | convert to number | exception | convert to number | exception |
Operator | Date
| string/other | bigint |
---|---|---|---|
+x
| milliseconds since 1970 | convert to number | exception |
Number(x)
| milliseconds since 1970 | convert to number | convert to number |
-x , ~x
| milliseconds since 1970 | convert to number | perform bigint operation |
For everything shown in the tables above, objects other than Date
first try valueOf
and then try toString
if that doesn't work. For everything except ==
in the tables above, using a symbol as an operand throws an exception.
==
One of the main complains about ==
is that it isn't transitive; that is, x == y
and y == z
does not imply that x == z
. The way this was actually solved with ===
was to not do type conversions, meaning that in some cases where ==
returns true
, ===
would return false
(and this is probably the right way to do things), but it could be somewhat interesting to think, what if they went the other way? What if they instead defined an operator that still did the type conversions, was true
everywhere ==
is true, but was also true
in other situations in order to be transitive?
Cases where x == y
and y == z
but x != z
:
y
is null
or undefined
: then x
and z
are both null
, undefined
, or document.all
, and therefore already equal to each other
y
is a boolean: then x
and z
, converted to primitive then converted to number, both equal 0 or 1, so this is taken care of by case 3
y
is a number: three cases:
x
nor z
is a bigint: then x
and z
, converted to primitive and then converted to number, equal the same number, and that number isn't NaN.
x
is bigint, z
is not bigint (or vice versa): then z
, when converted to primitive and then converted to number, is exactly equal to x
x
and z
are bigints: then both x
and z
equal the value of the number, and therefore are already equal
y
is a string: cases:
x == y
and a bigint in y == z
(or vice versa), in which case the bigint converted to a string and then a number is equal to the non-bigint converted to a number
x == y
(or y == z
), in which case String(x) === y
so String(x) == z
y
is a bigint: I'm pretty sure all the possibilities are taken care of by case 3
y
is a symbol: then x
and z
, converted to a primitive, both convert to the same symbol
y
is an object:
x
or z
(or both)
document.all
, x
is null
or undefined
, and z
is the string representation of document.all
(or vice versa)
Note that this also means that bigints that are approximated by the same number have to be equivalent, since e.g. 9007199254740993n == "9007199254740993" == 9007199254740992 == 9007199254740992n
.
There isn't really a good way to take into consideration objects where valueOf
or toString
is not referentially transparent (e.g., {valueOf:function(){return Math.random();}}
).
This is what I came up with:
function toPrimitive(obj) {
if(obj == null || (typeof obj != 'object' && typeof obj != 'function'))
return obj;
if(obj.valueOf != null && Object.prototype.toString.apply(obj) != '[object Date]') {
var v = obj.valueOf();
if(v == null || (typeof v != 'object' && typeof v != 'function'))
return v;
}
if(obj.toString != null) {
var v = obj.toString();
if(v == null || (typeof v != 'object' && typeof v != 'function'))
return v;
}
// my implementation doesn't handle case where a Date object doesn't
// have a toString method that returns a string, nor where a Symbol
// object has a modified valueOf method
throw new TypeError();
}
// transitive closure of ==
function transitiveEquals(x, y) {
if(x == y) return true;
// case 7c
if((x == String(document.all) && y == null)) ||
(x == null && y == String(document.all))
return true;
// otherwise, null and undefined are only equal to each other
if(x == null || y == null)
return false;
// is there a value z such that x == z and z == y?
// x and y convert to the same primitive value (cases 3c, 4d, and 6):
var x_prim = toPrimitive(x), y_prim = toPrimitive(y);
if(x_prim == null || y_prim == null)
return false;
if(x_prim == y_prim)
return true;
// z is number: x == z iff toPrimitive(z) is not a symbol and
// Number(toPrimitive(x)) == z
// cases 3a, 3b, 4a, 4b, 4c
if(typeof x_prim != 'symbol' && typeof y_prim != 'symbol' &&
(typeof x_prim == 'bigint' ? +String(x_prim) : +x_prim) ==
(typeof y_prim == 'bigint' ? +String(y_prim) : +y_prim))
return true;
return false;
}