此前在微博上无意中看到有人问“为什么alert([] == ![])会是true?”,
刚看到这个问题我也说不上来究竟是什么原因,只知道这个肯定又是和==操作相关的类型转换问题。
于是,就翻开了“葵花宝典(ECMA-262-5th)”,你懂的。
在宝典的帮助下,我尝试着来解释下该问题的原因:
- 首先看看==这个操作内部是如何工作的
宝典中的关于==操作的工作描述如下(11.9.1):
The production EqualityExpression: EqualityExpression == RelationalExpression is evaluated as follows:
- Let lref be the result of evaluating EqualityExpression
- Let lval be GetValue(lref)
- Let rref be the result of evaluating RelationalExpression
- Let rval be GetValue(rref)
- Return the result of performing abstract equality comparison rval==lval
- 根据上面的步骤,我们来对问题作如下解析:
- 先求GetValue([])
- 再求GetValue(![])
- 最后求 GetValue([]) == GetValue(![])
先要搞清楚GetValue方法是干嘛的,继续看宝典关于GetValue的描述(8.7.1):
- If Type(V) is not Reference, return V
- ….
对于解释我们的问题,看到这里就足够了,因为[]和![]都不属于Reference,所以,GetValue([])和GetValue(![])都返回自身。
这里关于什么是Reference不想再赘述了,要详细了解的可以看宝典(8.7)。
那么,上述问题进一步转化成了如下问题:
- GetValue([])为 []
- GetValue(![])为 false, (这里!会使得[]强制转化为Boolean类型)
- 这里就成了求 [] == false的问题
也就是说: [] == ![] 现在转化为了 [] == false。
- 根据”abstract equality comparison”算法来求结果:
宝典中第五步就提到了根据”abstract equality comparison”来求最后的结果。
现在先来看看[]和false的类型,两者类型显而易见,前者是Object,后者Boolean。
然后,我们进一步来看看这个算法是如何的(11.9.3),以下只列出了和我们这个问题相关的算法步骤,其中有这么一条:
The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:
- If Type(y) is Boolean, return the result of the comparison x == ToNumber(y)
这句话很容易理解,就是要把y转化类型为数值,也就是说false变为0。
这样以来,问题有变成了求: [] == 0
继续看宝典中这个算法(11.9.3),其中有这么一条:
The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:
- If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x) == y.
于是,问题有变成了求: ToPrimitive([]) == 0。
- 查看ToPrimitive的工作机制(9.1)
其中对于Object有这种转换描述:
Return a default value for the Object. The default value of an object is retrieved by calling the [[DefaultValue]] internal method of the object,
passing the optional hint PreferredType. The behaviour of the
[[DefaultValue]] internal method is defined by this specification for all native
ECMAScript objects in 8.12.8.
继续顺藤摸瓜,看[[DefaultValue]](hint),我们的例子中hint是Number,因为它是和0去做比较。
根据宝典(8.12.8)描述:
When the [[DefaultValue]] internal method of O is called with hint Number, the following steps are taken:
- Let valueOf be the result of calling the [[Get]] internal method of object O with argument “valueOf”.
- If IsCallable(valueOf) is true then
a. Let val be the result of calling the [[Call]] internal method of valueOf, with O as the this value and an empty argument list.
b. If val is a primitive value, return val.- Let toString be the result of calling the [[Get]] internal method of object O with argument “toString”.
a. Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.
b. If str is a primitive value, return str.
…
这里不对valueOf再去做赘述了,MDN上面有很简短的说明,大致意思如下:
默认,每个对象都有从Object继承下来的valueOf方法。其中每个内置的核心对象都会重载该方法来返回正确的值,对于没有基础类型值的对象,则返回对象自身。
那么,对于我们的情况来说,进入了算法中的2,但是,因为val是对象不是基础类型,所以继续进入第3步,这个时候关键来了:
开始调用[]的toString方法,这个时候会返回””,一个空的字符串,因此ToPrimitive([])为””
因此,问题又转化成了:
“” == 0
现在答案就很明显了,根据宝典的==工作原理如下描述(11.9.3):
The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:
- If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
好了,根据算法,会将””转化为数值类型,那么自然就变成了0,于是 0 == 0 是很自然而然的。
总结:
最终问题就从: [] == ![] 变成了 0 == 0。答案自然是true了。
其实遇到这种语言层面的问题,直接看宝典即可。
说明:
以上诸如 11.9.3 这样的数字均表示葵花宝典中的章节。