HOWTO · JavaScript

在 JavaScript 中比较对象

我们可以手动比较对象,也可以使用 JavaScript 中的 JSON.stringify() 函数。

本页内容

JavaScript 的严格相等运算符(===)按引用比较对象:分别创建的两个对象即使属性相同,也不相等。若要比较内容,应根据数据和重要字段选择合适的方法。JSON.stringify() 比较序列化文本,手动检查可以只比较指定属性,而 Object.keys() 循环可以比较所有顶层自有、可枚举的字符串键属性。

JavaScript 中对象相等的含义

对于字符串和数字等原始值,=== 比较的是值。对于对象,它检查两个变量是否引用同一个对象。将一个对象赋给另一个变量会保留该引用;另行创建一个属性相同的对象则不会。若比较的目的是判断数据内容,而非对象身份,这一区别就很重要。

使用 JSON.stringify() 比较对象

对于简单且兼容 JSON 的数据,JSON.stringify() 会将每个对象转换为文本,然后可以用 === 比较这些文本。属性及其顺序相同时,序列化文本也相同。当数据结构和属性顺序都受控时,这是一种简便的检查方式。

运行下面这个独立示例:

本节中的代码以及显示的标签或输出仍为英文。

const first = { name: 'Apple', price: 2 };
const same = { name: 'Apple', price: 2 };
const reordered = { price: 2, name: 'Apple' };
console.log(JSON.stringify(first) === JSON.stringify(same));
console.log(JSON.stringify(first) === JSON.stringify(reordered));

这种比较会受到属性顺序影响,也不是通用的深度相等性测试。序列化还有 JSON 特有的行为:例如,值为 undefined 的对象属性会被省略,而循环引用无法序列化。

手动比较指定属性

如果只有特定字段决定对象是否匹配,就直接比较这些字段。这样可以避免让无关数据影响判断,也能明确说明函数采用的比较规则。下面的示例检查 name 和 price;其他属性发生变化不会影响结果。如果还有其他字段也属于相等条件,就将它们加入判断。

本节中的代码以及显示的标签或输出仍为英文。

function sameFruit(a, b) {
  return a.name === b.name && a.price === b.price;
}

const first = { name: 'Apple', price: 2, color: 'red' };
const second = { name: 'Apple', price: 2, color: 'green' };
console.log(sameFruit(first, second));

这是对指定值进行浅层比较。如果某个指定值本身是对象,=== 仍会检查两个嵌套值是否引用同一个对象,不会递归比较它们的内容。

比较所有自有且可枚举的字符串键属性

如果对象的顶层字段集合可能变化,可以比较它们各自拥有的可枚举字符串键属性和值。Object.keys() 不会返回符号键属性,即使这些属性可枚举也一样,因此此方法不会比较它们。先检查键的数量,再确认第一个对象中的每个键都是第二个对象的自有属性,最后比较对应的值。成员检查不可省略:仅比较键的数量,可能会把键名不同但值都为 undefined 的对象误判为匹配。

本节中的代码以及显示的标签或输出仍为英文。

function sameOwnValues(a, b) {
  const keysA = Object.keys(a);
  const keysB = Object.keys(b);
  if (keysA.length !== keysB.length) return false;

  for (const key of keysA) {
    if (!Object.hasOwn(b, key) || a[key] !== b[key]) return false;
  }
  return true;
}

const first = { name: 'Apple', price: 2 };
const second = { price: 2, name: 'Apple' };
const differentKeys = { name: 'Apple', color: undefined };
console.log(sameOwnValues(first, second));
console.log(sameOwnValues({ name: undefined }, differentKeys));

Object.keys() 返回自有、可枚举的字符串键属性;它会忽略继承属性、不可枚举属性以及所有符号键属性。这种方法只比较一层深度的值,因此嵌套对象按引用比较;在严格相等比较下,NaN 等值与自身也不相等。现代 JavaScript 提供了 Object.hasOwn();在较旧的环境中,可以用 Object.prototype.hasOwnProperty.call(b, key) 检查属性是否属于对象本身。

选择合适的比较方法

当数据格式受控、兼容 JSON、属性顺序一致,而且简洁比较已足够时,使用 JSON.stringify()。如果只有一组已知字段决定对象是否匹配,就比较这些指定属性。如果需要比较所有顶层自有、可枚举的字符串键属性,且不受插入顺序影响,就使用逐键比较的循环;可枚举的符号键属性不在其比较范围内。如果嵌套结构、特殊值或应用专用类型需要按值判断,应明确制定相应规则,或使用专门的深度比较实现;不要把这些浅层检查中的任何一种当作通用的对象相等性判断。