array1 === array2 ?
To compare arrays in JavaScript, don't assume you can check whether the contents of two JavaScript arrays are the same with ===
or ==
(the strict equality or equality operators). You can't.
const array1 = [1, 2, 3];
const array2 = [1, 2, 3];
array1 === array2 // returns false
The code above is checking if the above two variables refer to the same array instance, and not whether their contents are the same. It would only return true for something like this:
const array1 = [1, 2, 3]
const array2 = array1; // both variables point to the same array instance
array1 === array2 // now returns true
Solutions
JSON.stringify()
One way to solve the problem is to use JSON.stringify(), which converts a value to a JSON string.
const array1 = [1, 2, 3];
const array2 = [1, 2, 3];
JSON.stringify(array1) === JSON.stringify(array2) // returns true
JSON.stringify even works with multiple levels of nesting:
const array1 = [1, 2, 3, {x: 5}, [1, {y: 7}, 3]];
const array2 = [1, 2, 3, {x: 5}, [1, {y: 7}, 3]];
JSON.stringify(array1) === JSON.stringify(array2); // returns true
However, this method is not perfect. It is dependent on the JSON.stringify() method implementation not changing. There are also edge cases that produce strange behavior. Undefined is not a valid JSON value, so JSON.stringify() converts undefined to null, and this method could falsely return true in that case:
const array1 = [null, 2, 3];
const array2 = [undefined, 2, 3];
JSON.stringify(array1) === JSON.stringify(array2)
// returns true, should be false
null === undefined
// returns false
array.every()
Another method uses array.every(). It first checks if the arrays are the same length, and then checks that each value in the first equals the value in the second at the same index:
const isEqual = array1.length === array2.length
&& array1.every((value, index) => value === array2[index])
console.log(isEqual);
However, the array.every() way won't work for arrays nested with additional arrays or objects, which would need to be checked recursively.
Recursive methods
This can be done with the isEqual method from the Lodash library. According to this gist, it is doing something under the hood like the following:
const array1 = [1, [1, [{a: 'b'}]], 3];
const array2 = [1, [1, [{a: 'b'}]], 3];
const isEqual = (first, second) => {
if (first === second) {
return true;
}
if ((first === undefined || second === undefined
|| first === null || second === null) && (first || second)) {
return false;
}
const firstType = first?.constructor.name;
const secondType = second?.constructor.name;
if (firstType !== secondType) {
return false;
}
if (firstType === 'Array') {
if (first.length !== second.length) {
return false;
}
let equal = true;
for (let i = 0; i < first.length; i++) {
if (!isEqual(first[i], second[i])) {
equal = false;
break;
}
}
return equal;
}
if (firstType === 'Object') {
let equal = true;
const fKeys = Object.keys(first);
const sKeys = Object.keys(second);
if (fKeys.length !== sKeys.length) {
return false;
}
for (let i = 0; i < fKeys.length; i++) {
if (first[fKeys[i]] && second[fKeys[i]]) {
if (first[fKeys[i]] === second[fKeys[i]]) {
continue; // eslint-disable-line
}
if (first[fKeys[i]] && (first[fKeys[i]].constructor.name === 'Array'
|| first[fKeys[i]].constructor.name === 'Object')) {
equal = isEqual(first[fKeys[i]], second[fKeys[i]]);
if (!equal) {
break;
}
} else if (first[fKeys[i]] !== second[fKeys[i]]) {
equal = false;
break;
}
} else if ((first[fKeys[i]] && !second[fKeys[i]]) ||
(!first[fKeys[i]] && second[fKeys[i]])) {
equal = false;
break;
}
}
return equal;
}
return first === second;
};
console.log(isEqual(array1, array2)); // returns true