我并没有从 Rayon 那里真正了解在将对象转换为原始值时 valueOf 和 toString 是如何发挥作用的;所以我深入研究了ECMAScript 2015 规范。
警告:答案很长。
我们要检查表达式1 == [1]。
从 12.10 等式运算符开始,我们看到,在检索表达式值之后,最后一步是
- 返回执行抽象等式比较的结果 rval == lval
抽象等式比较在7.2.12 抽象等式比较一章中定义。
7.2.12 抽象等式比较
比较 x == y,其中 x 和 y 是值,产生真或假。这样的比较是这样进行的:
- ReturnIfAbrupt(x)。
- ReturnIfAbrupt(y)。
- 如果 Type(x) 与 Type(y) 相同,则
一种。返回执行严格等式比较 x === y 的结果。
- 如果 x 为空且 y 未定义,则返回 true。
- 如果 x 未定义且 y 为空,则返回 true。
- 如果 Type(x) 是 Number 并且 Type(y) 是 String,则返回比较结果 x == ToNumber(y)。
- 如果 Type(x) 是 String 且 Type(y) 是 Number,则返回比较结果 ToNumber(x) == y。
- 如果 Type(x) 是 Boolean,则返回 ToNumber(x) == y 的比较结果。
- 如果 Type(y) 是 Boolean,则返回比较结果 x == ToNumber(y)。
-
如果 Type(x) 是 String、Number 或 Symbol 并且 Type(y) 是 Object,那么
返回比较结果 x == ToPrimitive(y)。
- 如果 Type(x) 是 Object 并且 Type(y) 是 String、Number 或 Symbol,则返回 ToPrimitive(x) == y 的比较结果。
- 返回 false。
表达式1 == [1] 属于case 10。
所以基本上,正如预期的那样,数组[1] 被转换为原始类型的值。
ToPrimitive 定义在 7.1.1 ToPrimitive ( input [, PreferredType] )
抽象操作 ToPrimitive 接受一个输入参数和一个可选参数 PreferredType。这
抽象操作 ToPrimitive 将其输入参数转换为非对象类型。
我没有包含完整的引用,因为对于这个例子,唯一有趣的部分是:
-
PreferredType 参数(实际上是一个 hint 变量)从“默认”(因为未传递)转换为“数字”。
-
OrdinaryToPrimitive 使用相同的参数调用。
现在是有趣的部分,OrdinaryToPrimitive 执行以下操作:
- 断言:类型(O)是对象
- 断言:类型(提示)是字符串,其值是“字符串”或“数字”。
- 如果提示是“字符串”,则
一种。让 methodNames 为 «"toString", "valueOf"»。
- 否则,
一种。 让 methodNames 为 «"valueOf", "toString"»。
- 对于方法名称中的每个名称,按列表顺序执行
一种。让方法为 Get(O, name)。
湾。 ReturnIfAbrupt(方法)。
C。如果 IsCallable(method) 为真,则
... 一世。让结果为 Call(method, O)。
... 二。 ReturnIfAbrupt(结果)。
... 三。 **如果 Type(result) 不是 Object,则返回结果。 **
- 抛出 TypeError 异常
因此,为了将[1] 转换为原始值,运行时首先尝试调用valueOf。此方法返回数组本身,它是一个对象,因此 5.c.iii 方法 toString 被调用。
此方法以逗号分隔列表的形式返回数组元素,因此它只返回字符串"1"。
所以我们减少了比较1 == "1",根据抽象平等比较的规则,第 6 点意味着将"1" 转换为数字1,而不是执行琐碎的比较@987654335 @。
请可疑的读者检查严格相等比较在标准中的实际定义。
您可以使用这些转换来更好地理解它们,这里是一个示例游乐场 HTML 文件
<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>