I didn't really understood from Rayon answer how valueOf
and toString
come into play when converting an object to a primitive value; so I dug into the ECMAScript 2015 specifications.
Warning: Long answer.
We want to check the expression 1 == [1]
.
Starting from the 12.10 Equality Operators we see that, after retrieving the expressions values, the last step is
- Return the result of performing Abstract Equality Comparison rval == lval
Abstract Equality Comparison is defined at chapter 7.2.12 Abstract Equality Comparison.
7.2.12 Abstract Equality Comparison
The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:
- ReturnIfAbrupt(x).
- ReturnIfAbrupt(y).
- 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, then
return the result of the comparison x == ToPrimitive(y).
- If Type(x) is Object and Type(y) is either String, Number, or Symbol, then return the result of the comparison ToPrimitive(x) == y.
- Return false.
The expression 1 == [1]
falls under case 10.
So basically, as expected, the array [1]
is converted into a value of primitive type.
ToPrimitive is define at 7.1.1 ToPrimitive ( input [, PreferredType] )
The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType. The
abstract operation ToPrimitive converts its input argument to a non-Object type.
I haven't included the full quotation since the only interesting, for this example, parts are:
- The PreferredType argument (actually an hint var) is converted from "default" (since it is not passed) to "number".
OrdinaryToPrimitive
is called with the same arguments.
E now the interesting part, OrdinaryToPrimitive do the following:
- Assert: Type(O) is Object
- Assert: Type(hint) is String and its value is either "string" or "number".
- If hint is "string", then
a. Let methodNames be «"toString", "valueOf"».
- Else,
a. Let methodNames be «"valueOf", "toString"».
- For each name in methodNames in List order, do
a. Let method be Get(O, name).
b. ReturnIfAbrupt(method).
c. If IsCallable(method) is true, then
... i. Let result be Call(method, O).
... ii. ReturnIfAbrupt(result).
... iii. **If Type(result) is not Object, return result. **
- Throw a TypeError exception
So in order to convert [1]
to a primitive value, the runtime try first to call valueOf
. This method returns the array itself, which is an object so by 5.c.iii the method toString
is called next.
This method returns the elements of the array as a comma-separated list, so it just returns the string "1"
.
So we are reduced comparing 1 == "1"
which by the rules of Abstract Equality Comparison, point 6, means to convert "1"
into the number 1
and than performing the trivial comparison 1 = 1
.
The dubious reader is invited to check how Strict Equality Comparison is actually defined in the standard.
You can play with this conversions for better understanding them, here a sample playground HTML file
<html>
<head><title>title</title></head>
<body>
<script>
var old_valueOf = Array.prototype.valueOf;
var old_toString = Array.prototype.toString;
Array.prototype.valueOf = function(){ console.log("Array::valueOf"); return old_valueOf.apply(this); };
Array.prototype.toString = function(){ console.log("Array::toString"); return old_toString.apply(this); };
console.log(1 == [1]); //Array::valueOf, Array::toString, true
Array.prototype.valueOf = function(){ console.log("Array::valueOf"); return 2; };
console.log(1 == [1]); //Array::valueOf, false
Array.prototype.valueOf = function(){ console.log("Array::valueOf"); return {}; };
Array.prototype.toString = function(){ console.log("Array::toString"); return {} };
console.log(1 == [1]); //Array::valueOf, Array::toString, Uncaught TypeError: Cannot convert object to primitive value
</script>
</body>
</html>