为什么在Javascript中[1,2,3]不等于其本身?

14

今天我在JavaScript中玩弄数组时,注意到了这个小技巧:

alert([1, 2, 3] == [1, 2, 3]); //alerts false

我觉得数组不等于自身很奇怪。

但后来我注意到了更奇怪的事情:

alert([1, 2, 3] == "1,2,3");  //alerts true

?!?!?!?!!!?

为什么 [1, 2, 3] 不等于自身,但却等于字符串呢?

我知道 =====不同的。即便如此,是什么邪恶导致JavaScript先生做出这样奇怪的事情呢?


4
当一个语言按照其规范所说的去做并且这不是恶意的行为时,这是好的。既然你已经知道=====有不同的作用,那么这不就回答了你关于比较数组和字符串的问题吗? - nnnnnn
可能是重复的问题:为什么数组不能使用相等检查? - Bergi
6个回答

23

好的,首先你需要理解JavaScript如何处理程序中的值。你创建的所有变量只是指向内存中存储该对象的位置的引用。因此,当你这样做时:

alert( [1,2,3] == [1,2,3] );

...它做了三件事:

  1. 将一个数组([1,2,3])放入堆中
  2. 再次将一个数组([1,2,3])放入堆中(请注意它将具有不同的内存位置)
  3. 比较这两个引用。它们指向内存中不同位置的不同对象,因此被认为是不相等的。

您可以通过运行此代码来检查一些合理的行为:

var a = [1,2,3];
var b = a;
alert (a == b)   // Result is true. Both point to the same object.

关于你对字符串的问题,现在回答:

当你使用==运算符时,它会尝试将两个操作数转换为相同类型(很恶劣的行为...我知道...)

在这样做时,它决定先将两个操作数都转换为字符串,然后再进行比较(因此结果实际上是"1,2,3" === "1,2,3",这个表达式的值为true。

我不能给你完整的图片,因为了解JavaScript之疯狂的细微差别的人很少,但希望这能够消除一些困惑。


3
==并没有恶意行为,它具有明确定义的行为,在这种情况下, 它的行为比起使用 === 获得的结果来说可能不那么有帮助。或者也可能会更有帮助:这完全取决于你尝试做什么。 - nnnnnn
3
@nnnnnn,它具有违反直觉的行为,因此它是邪恶的。上面的例子揭示了“==”运算符某些邪恶特性,但并非全部。例如,false == undefined是false,false==null也是false,但 null==undefined却是true。这是完全定义明确的,但完全违反直觉(如果传递性是“正确”的基础,那么这些结果就是完全错误的)。 - riwalk
4
@nnnnnn,请等一下,我复制了错误的示例(深夜疏忽了)。打破传递性的示例是:'0'==0为真,'' == 0为真,但'0'==''为假。令人惊讶的反直觉。 - riwalk
== 对于数组的行为并不是真的很反直觉,不是吗?它们是不同的数组,恰好包含相同的元素。 - Dave Newton
这取决于你所编写的内容。但是当你没有运算符重载时,那种功能应该来自方法。 == 的两种用法都很典型;我根本不认为它是违反直觉的。字符串与数字之间的问题与 JS 的奇异类型转换思想有关,而不是 == 运算符。 - Dave Newton
显示剩余2条评论

6

==运算符

[..] 如果其中一个操作数是字符串,如果可能,另一个操作数将被转换为字符串。[..] 如果两个操作数都是对象,则JavaScript比较内部引用,当操作数引用相同的内存中的同一对象时,它们相等。

https://developer.mozilla.org/en/JavaScript/Reference/Operators/Comparison_Operators

也就是说,[1, 2, 3] 被转换为字符串后等于 "1,2,3"。但一个数组对象并不等于另一个数组对象。


4

首先,由于数组本质上就是对象,因此您创建了两个不同的对象。由于您创建了两个对象,它们都是唯一的。


3
因为==只有在必要时才会强制转换为相同类型(例如,仅在操作数的类型不同时)。 在执行比较时,最好使用===,因为它不进行类型强制转换。
alert([1, 2, 3] == [1, 2, 3]); //alerts false

不需要使用强制,两个都是对象。它们不是同一个对象,因此结果为false。人们认为==是“强制”相等运算符,确实如此,但关键在于只有在必要时才进行强制转换。

但执行以下操作:

alert([1, 2, 3] == "1,2,3");  //alerts true

涉及到不同类型的操作数:字符串和对象。因此进行了强制转换。在这种情况下,对象被强制转换为字符串,就像使用String(obj)一样,它调用其默认的toString行为,对于数组来说是.join()join默认使用","作为分隔符,因此生成的字符串与"1,2,3"匹配。(您可以在规范中找到为什么对象被强制转换为字符串的完整逻辑。)


2

第一个比较失败了,因为基本的两个对象比较会检查它们是否是完全相同的引用对象,而不是它们的值是否相同。如果您想比较两个数组,您必须遍历这些值。

function arrayCompare(arr1, arr2) {
  if (arr1.length !== arr2.length) return false;
  for (var i = 0, len = arr1.length; i < len; i++) {
    if (arr1[i] !== arr2[i]) return false;
  }
  return true;
}

请记住,这不是递归比较,所以它只能用于基本值类型的数组。

第二个比较可以成功是因为 == 会尝试强制转换参数的类型,当您将一个数组转换为字符串时,结果就是这样。

[1,2,3].toString() === '1,2,3'

1

这两个Array对象是不同的,因此在使用==比较时不相等。要比较它们,您需要循环检查两者是否具有相同的索引(如果元素也是ArrayObject则进行递归)。

第二个问题是因为Array隐式调用了其toString()方法,返回了'1,2,3'(试一下)。

这是因为根据ECMAScript中==(非严格)比较的规则,左侧操作数被强制转换为一个String==进行类型转换)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接