JavaScript比较数组

原文:Compare Arrays in JavaScript

JavaScript比较两个数组的一些技巧。

JavaScript中的数组本质是对象,因此用===比较,只有在数组是相同引用时才会返回true

1
2
3
4
5
const a = [1, 2, 3];
const b = [1, 2, 3];

a === a; // true
a === b; // false

如何判断两个数组相等?相等是个棘手的问题:JavaScript标准定义了4种不同的方法,用于检查两个值是否“相等”,但都没有考虑到深入对象内部的相等。

在某些场景下,你需要尽可能精确的定义“相等”。在软件工程中,提出正确的问题往往能够得到明显的答案。

基于这些原则,本文提供了3种数组相等的定义,以及如何判断相等。

相同长度、每个值相等

比较ab的一种方式,是检查a的每个值是否严格等于b的对应值。当数组元素都是原始类型而非对象时很容易实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
const a = [1, 2, 3];
const b = [4, 5, 6];
const c = [1, 2, 3];

function arrayEquals(a, b) {
return Array.isArray(a) &&
Array.isArray(b) &&
a.length === b.length &&
a.every((val, index) => val === b[index]);
}

arrayEquals(a, b); // false
arrayEquals(a, c); // true

POJO的深度相等

上文的arrayEquals()函数对原始类型值运行正常,但想要比较对象数组就麻烦了。

1
2
3
4
5
const a = [{ answer: 42 }, { powerLevel: 9001 }];
const b = [{ answer: 42 }, { powerLevel: 9001 }];

// false, 因为 { answer: 42 } !== { answer: 42 }, 引用不同
arrayEquals(a, b);

考虑到对象数组的比较,一种简单的方式是用JSON.stringify()

1
2
3
4
5
6
const a = [{ answer: 42 }, { powerLevel: 9001 }];
const b = [{ answer: 42 }, { powerLevel: 9001 }];
const c = [{ answer: 42 }, { password: 'taco' }];

JSON.stringify(a) === JSON.stringify(b); // true
JSON.stringify(a) === JSON.stringify(c); // false

这种方式很方便,它需要很少的代码而且不会引用到外部库。但是比较JSON.stringify()的输出,根据使用的不同,边界情况下可能会发生意外。由于undefined不是有效的JSON值,以下数组具有相同的JSON.stringify()输出,因为JSON.stringify()undefined转换为null

1
2
const a = [undefined];
const b = [null];

使用lodash的isEqual()

除了nullundefined的问题,比较JSON.stringify()的输出也没有考虑对象类型。一个对象,带有一个返回42toJSON()函数,它的JSON.stringify()输出和数值42完全相同。

1
2
3
4
5
const a = [{ toJSON: () => 42 }];
const b = [42];

JSON.stringify(a); // '[42]'
JSON.stringify(b); // '[42]'

相似的,自定义对象和POJO的JSON.stringify()也是相同的:

1
2
3
4
5
6
7
8
9
10
class MyClass {
constructor(obj) {
Object.assign(this, obj);
}
}

const a = [new MyClass({ answer: 42 })];
const b = [{ answer: 42 }];

JSON.stringify(a) === JSON.stringify(b); // true

JSON.stringify()不同,lodash的isEqual()函数考虑了以上所有情况。

1
2
3
4
5
6
7
8
9
10
11
12
const _ = require('lodash');

class MyClass {
constructor(obj) {
Object.assign(this, obj);
}
}

const a = [new MyClass({ answer: 42 })];
const b = [{ answer: 42 }];

_.isEqual(a, b); // false

如果你需要比较类的对象或者其他花里胡哨的东西,lodash的isEqual()函数是正确的方式。JSON.stringify()对POJO而言是可用的,但是一定要考虑到undefinednull,而且只对可信的数据使用——toJSON()可能造成安全漏洞。