The abstract equality (==) and inequality (!=) operators rely on the same algorithm internally, which is responsible for determining whether two values can be considered equal. In this section, our examples will only explore ==, but rest assured that != will always simply be the opposite of whatever == is.
Where both operands, the left-side and the right-side, are of the same type, then the mechanism is quite simple—the operator will check whether the two operands are identical values:
100 == 100; // => true
null == null; // => true
'abc' == 'abc'; // => true
123n == 123n; // => true
Since all non-primitives in JavaScript are of the same type (Object), abstract equality ( ==) will always return false if you try to compare two non-primitives (two objects) that don't refer to the exact same object:
[123] == [123]; // => false
/123/ == /123/; // => false
({}) == ({}); // => false
However, where both operands are of different types, for example, where you are comparing a Number type to a String type or an Object type to a Boolean type, the exact behavior of abstract equality will depend on the operands themselves.
If either operand is Number, and the other is String, then the a == b operation is equivalent to the following:
Number(a) === Number(b)
Here are some examples of this in action:
123 == '123'; // => true
'123' == 123; // => true
'1e3' == 1000; // => true
Continuing down the rabbit hole—if only one operand to the == operator is Boolean, then the operation is, once again, equivalent to Number(a) === Number(b):
false == ''; // => true
// Explanation: Number(false) is `0` and Number('') is `0`
true == '1'; // => true
// Explanation: Number(true) is `1` and Number('1') is `1`
true == 'hello'; // => false
// Explanation: Number(true) is `1` and Number('hello') is `NaN`
false == 'hello'; // => false
// Explanation: Number(false) is `0` and Number('hello') is `NaN`
Finally, if previous conditions are not met, and if either operand is Object (not a primitive), then it will compare the primitive representation of that object to the other operand. As discussed in the last chapter, in the Conversion to a primitive section, this will attempt to call the [Symbol.toPrimitive](), valueOf(), and then toString() methods to establish the primitive. We can see this in action here:
new Number(1) == 1; // => true
new Number().valueOf(); // => 1
({ valueOf() { return 555; }) == 555; // => true
Due to their complicated coercive behaviors, the abstract equality and inequality operators are best avoided. Anyone reading code littered with these operators won't be able to have a good level of confidence in the conditions and control flow of the program because there are simply too many odd edge cases where abstract equality can bite.