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 資料可用序列化;其他需求則應採用明確設計的比對或深層相等策略。