HOWTO · JavaScript
如何在 JavaScript 中比较两个数组
本教程介绍了一些在 JavaScript 中比较两个数组的方法。
本页内容
JavaScript 的 === 运算符按引用身份比较数组,因此两个分别创建但内容相同的数组,用这种方式比较也不相等。要比较数组内容,需要先确定顺序和元素值的比较规则。对于顺序相同的原始值,可以先比较长度,再逐一比较对应元素。只有当数据符合序列化规则时,才使用 JSON.stringify();如果顺序无关,则比较排序后的副本。这些基本方法比较嵌套数组和对象时依据的是引用,而非结构。
两个数组相等意味着什么?
首先要明确当前任务中“相等”的含义。有序比较要求数组长度相同,而且每个索引处的值都匹配。无序比较把数组视为集合,但仍须考虑重复值:[1, 1, 2] 和 [1, 2, 2] 并不等价,不能仅因为它们包含相同的不同值就判定相等。
还要决定元素比较采用浅层相等还是结构相等。严格相等适合比较数字、字符串等原始值。对于对象和嵌套数组,严格相等检查的是两个位置是否引用同一个对象;它不会比较对象属性或嵌套内容。
使用循环或 Array.prototype.every() 按顺序比较数组
使用独立函数可以避免修改内置的 Array.prototype。此版本先检查长度,再用严格相等比较对应位置的值。这是一种有序的浅层比较。
运行此示例:
本节中的代码以及显示的标签或输出仍为英文。
function arraysEqual(left, right) {
if (left.length !== right.length) return false;
return left.every((value, index) => value === right[index]);
}
const first = ["red", "green", "blue"];
const second = ["red", "green", "blue"];
console.log(arraysEqual(first, second));
true
该函数也会拒绝值有变化或长度不同的情况。以下额外调用尚未执行:
function arraysEqual(left, right) {
if (left.length !== right.length) return false;
return left.every((value, index) => value === right[index]);
}
console.log(arraysEqual(["red", "green"], ["red", "blue"]));
console.log(arraysEqual(["red", "green"], ["red", "green", "blue"]));
第一次调用会返回 false,因为第二个元素不同;第二次调用会返回 false,因为两个数组长度不同。
如果要自定义每个索引处的比较方式,可以用 for 循环表达相同规则:长度不同就拒绝匹配,遇到第一个不匹配项时返回 false,所有位置都匹配后返回 true。Array.prototype.every() 写法更简洁,并会在回调首次返回 false 时停止。它会跳过稀疏数组中未赋值的空位,因此对于稀疏输入,其结果可能不同于逐个读取每个索引的循环。如果空位差异会影响结果,请使用密集数组,或明确规定如何处理空位。
使用 JSON.stringify() 比较数组
对于包含 JSON 兼容值的数组,比较序列化结果是一种简洁的做法:JSON.stringify(left) === JSON.stringify(right)。这种比较会考虑顺序,因此 [1, 2] 和 [2, 1] 的比较结果不同。序列化还有特定规则:数组中的 undefined 等不受支持的值会序列化为 null,对象中值为 undefined 的属性则会被省略。属性可序列化且等价的不同对象可能生成相同的序列化结果,但这并不构成通用的深层相等定义。属性顺序和自定义序列化行为也可能影响结果。只有当这些规则符合数据约定时才使用此方法;不要把它当作通用的相等检查,也不要假设它对较大的数组更快。
不考虑顺序比较数组
如果顺序无关且元素是原始值,可以先对浅拷贝排序,再比较排序后对应位置的值。直接对输入数组调用 sort() 会修改原数组,因此应先用 slice() 复制。默认排序会比较字符串表示;如果值需要采用不同的顺序(例如数字),请提供比较函数。排序需要额外工作,混合类型则需要明确的排序规则。长度和元素检查仍会保留重复值的数量。
对于原始字符串,可以对副本排序,再用严格相等逐项比较。下面这个完整示例尚未执行;它会记录比较结果和两个原始数组:
本节中的代码以及显示的标签或输出仍为英文。
function arraysHaveSameValues(left, right) {
if (left.length !== right.length) return false;
const sortedLeft = left.slice().sort();
const sortedRight = right.slice().sort();
return sortedLeft.every((value, index) => value === sortedRight[index]);
}
const left = ["pear", "apple", "pear"];
const right = ["pear", "pear", "apple"];
console.log(arraysHaveSameValues(left, right));
console.log(left);
console.log(right);
对于这两个数组,按照该比较规则结果会是 true,而对副本排序不会改变任一输入数组的顺序。对两个副本排序并逐项比较,也能保留重复值的数量:某个值出现次数不同的数组不会匹配。这里的默认排序和严格相等规则适用于字符串;混合值需要针对其类型设计比较函数和相等规则。对于对象,应定义能表示预期相等关系的键,或采用适用于这些值的匹配策略;默认排序本身无法确定对象是否相等。
处理嵌套数组和对象
有序比较和排序示例都对每个元素使用严格相等。因此,两个分别创建的值(例如 [{ id: 1 }]),即使可见属性相同,在该位置仍会比较为不相等。嵌套数组也一样。如果需要结构相等,应使用为每种受支持类型明确定义行为的递归比较,或使用适合应用场景的专用深层相等库。在依赖此类工具前,应明确规定它如何处理日期、映射、集合、原型、循环引用和特殊数值;不同的深层相等工具可能采用不同规则。
对于已知为 JSON 兼容的数据,序列化有时可以提供简洁的结构比较,但仍受上述限制。其他情况下,应明确相等规则,并测试应用真正关心的情况。实际选择时,应采用符合顺序和数值语义要求的最简单方法:有序原始值可用 every() 或循环;受限的 JSON 数据可用序列化;其他需求则应采用明确设计的匹配或深层相等策略。