JavaScript is required for the demo |
Keep in mind that anything entered into these fields will be evaluated as JavaScript code. Do not enter suspicious code from outside sources.
Definition of == in the specification
First of all, as defined in the specification, there are seven different types: undefined, null, boolean, string, symbol (fairly new; these are values that can be used as attribute names but aren't strings), number (yes, NaN is a number), 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:
Type conversions for == are summarized in the following table:
undefined | null | boolean | number | string | symbol | object | |
---|---|---|---|---|---|---|---|
undefined | true | true | false | false | |||
null | true | true | |||||
boolean | false | Compare value | Convert to number | Convert to numbers | false | Convert object to primitive | |
number | false | Convert to number | Compare value | Convert to number | false | Convert object to primitive | |
string | Convert to numbers | Convert to number | Compare value | ||||
symbol | false | false | Compare value | ||||
object | Convert to primitive | Convert to primitive | Compare reference |
That is:
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.
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 | val is boolean | val is number | val is string | val is symbol |
---|---|---|---|---|---|
null or undefined | (none) | false | |||
boolean | Boolean | obj.valueOf() === val | +obj.valueOf() === val | +obj.valueOf() === +val | false |
number | Number | obj.valueOf() === +val | obj.valueOf() === val | obj.valueOf() === +val | false |
string | String | +obj.valueOf() === +val | +obj.valueOf() === val | obj.valueOf() === val | false |
symbol | Symbol | false | false | false | obj.valueOf() === val |
object | Object, Array, Function, RegExp | +obj.toString() === +val | +obj.toString() === val | obj.toString() === val | false |
Date objects (special case) | false | false | obj.toString() === val | false |
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;
(and therefore returns an object).
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.
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:
Source: ECMAScript 2015 Language Specification
See also: My original, non-interactive demo; Perl equality table (not mine)
== isn't transitive (that is, x == y and y == z does not imply that x == z), but what if it were? I mean, one way to do it is just to not do type conversion (like ===), but what if it did all the type conversion it currently does and still was transitive, what would that look like?
Cases:
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 && obj.constructor != 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 constructor
// was assigned explicitly, 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;
// 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:
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
// +toPrimitive(x) == z
if(typeof x_prim != 'symbol' && typeof y_prim != 'symbol' &&
+x_prim == +y_prim)
return true;
return false;
}