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>