×

说说为什么 [] == ![] 为true

96
goddyzhao
2015.07.25 10:37* 字数 1338

此前在微博上无意中看到有人问“为什么alert([] == ![])会是true?”
刚看到这个问题我也说不上来究竟是什么原因,只知道这个肯定又是和==操作相关的类型转换问题。
于是,就翻开了“葵花宝典(ECMA-262-5th)”,你懂的。

在宝典的帮助下,我尝试着来解释下该问题的原因:

  • 首先看看==这个操作内部是如何工作的

宝典中的关于==操作的工作描述如下(11.9.1):

The production EqualityExpression: EqualityExpression == RelationalExpression is evaluated as follows:

  1. Let lref be the result of evaluating EqualityExpression
  2. Let lval be GetValue(lref)
  3. Let rref be the result of evaluating RelationalExpression
  4. Let rval be GetValue(rref)
  5. Return the result of performing abstract equality comparison rval==lval
  • 根据上面的步骤,我们来对问题作如下解析:
  1. 先求GetValue([])
  2. 再求GetValue(![])
  3. 最后求 GetValue([]) == GetValue(![])

先要搞清楚GetValue方法是干嘛的,继续看宝典关于GetValue的描述(8.7.1):

  1. If Type(V) is not Reference, return V
  2. ....

对于解释我们的问题,看到这里就足够了,因为[]和![]都不属于Reference,所以,GetValue([])和GetValue(![])都返回自身。
这里关于什么是Reference不想再赘述了,要详细了解的可以看宝典(8.7)。
那么,上述问题进一步转化成了如下问题:

  1. GetValue([])为 []
  2. GetValue(![])为 false, (这里!会使得[]强制转化为Boolean类型)
  3. 这里就成了求 [] == 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:

  1. 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:

  1. 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:

  1. Let valueOf be the result of calling the [[Get]] internal method of object O with argument "valueOf".
  2. 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.
  3. 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:

  1. 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 这样的数字均表示葵花宝典中的章节。

参考资料


前端技术
Web note ad 1