JavaScript判断对象相等性的多种方式

 更新时间:2025年12月09日 08:33:10   作者:之恒君  
文章总结了JavaScript中判断对象相等的多种方法,包括相等性比较运算符、深度比较方法、浅比较、使用第三方库、ES6+新特性、特殊对象比较以及性能比较,每种方法都有其特点和适用场景,选择合适的方法可以提高代码的性能和可维护性,需要的朋友可以参考下

在 JavaScript 中,判断对象相等有多种方式,各有不同用途和特点。

1. 相等性比较运算符

1.1 严格相等===和 宽松相等==

const obj1 = { a: 1 };
const obj2 = { a: 1 };
const obj3 = obj1;

console.log(obj1 === obj2);  // false
console.log(obj1 === obj3);  // true
console.log(obj1 == obj2);   // false
console.log(obj1 == obj3);   // true

特点

  • 比较的是引用地址,不是内容
  • 只有指向同一内存地址的对象才相等
  • 适用于判断是否是同一个对象

2. 深度比较方法

2.1 JSON.stringify(简单对象)

function simpleDeepEqual(obj1, obj2) {
  return JSON.stringify(obj1) === JSON.stringify(obj2);
}

const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1, b: 2 };
const obj3 = { b: 2, a: 1 };  // 属性顺序不同
const obj4 = { a: 1, b: null };

console.log(simpleDeepEqual(obj1, obj2));  // true
console.log(simpleDeepEqual(obj1, obj3));  // false!顺序不同
console.log(simpleDeepEqual(obj1, { a: 1, b: 2 }));  // true

局限性

  • 属性顺序敏感
  • 不能处理 undefinedfunctionSymbol循环引用
  • undefined会被忽略,NaN会变成 null
  • 日期对象会转为字符串
// JSON.stringify 的问题
console.log(JSON.stringify({ a: undefined }));  // "{}"
console.log(JSON.stringify({ a: function() {} }));  // "{}"
console.log(JSON.stringify({ a: Symbol() }));  // "{}"
console.log(JSON.stringify({ a: NaN }));  // '{"a":null}'
console.log(JSON.stringify(new Date()));  // 日期字符串

2.2 递归深度比较

function deepEqual(obj1, obj2) {
  // 1. 基本类型比较
  if (obj1 === obj2) return true;
  
  // 2. 检查 null
  if (obj1 == null || obj2 == null) return false;
  
  // 3. 检查类型
  if (typeof obj1 !== typeof obj2) return false;
  if (obj1.constructor !== obj2.constructor) return false;
  
  // 4. 特殊类型处理
  if (obj1 instanceof Date && obj2 instanceof Date) {
    return obj1.getTime() === obj2.getTime();
  }
  
  if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
    return obj1.toString() === obj2.toString();
  }
  
  // 5. 数组比较
  if (Array.isArray(obj1)) {
    if (obj1.length !== obj2.length) return false;
    for (let i = 0; i < obj1.length; i++) {
      if (!deepEqual(obj1[i], obj2[i])) return false;
    }
    return true;
  }
  
  // 6. 对象比较
  if (typeof obj1 === 'object') {
    const keys1 = Object.keys(obj1);
    const keys2 = Object.keys(obj2);
    
    if (keys1.length !== keys2.length) return false;
    
    for (let key of keys1) {
      if (!obj2.hasOwnProperty(key)) return false;
      if (!deepEqual(obj1[key], obj2[key])) return false;
    }
    
    return true;
  }
  
  // 7. 其他类型
  return false;
}

优化版本

function deepEqual(obj1, obj2, visited = new WeakMap()) {
  // 处理循环引用
  if (visited.has(obj1) && visited.get(obj1) === obj2) {
    return true;
  }
  visited.set(obj1, obj2);
  
  // 基本类型
  if (obj1 === obj2) return true;
  if (obj1 == null || obj2 == null) return false;
  if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
    return obj1 === obj2;
  }
  
  // 不同类型
  if (obj1.constructor !== obj2.constructor) return false;
  
  // 特殊对象
  if (obj1 instanceof Date) return obj1.getTime() === obj2.getTime();
  if (obj1 instanceof RegExp) return obj1.toString() === obj2.toString();
  if (obj1 instanceof Map || obj2 instanceof Map) {
    if (obj1.size !== obj2.size) return false;
    for (let [key, val] of obj1) {
      if (!obj2.has(key)) return false;
      if (!deepEqual(val, obj2.get(key), visited)) return false;
    }
    return true;
  }
  if (obj1 instanceof Set || obj2 instanceof Set) {
    if (obj1.size !== obj2.size) return false;
    for (let val of obj1) {
      if (!obj2.has(val)) return false;
    }
    return true;
  }
  
  // 普通对象/数组
  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);
  
  if (keys1.length !== keys2.length) return false;
  
  for (let key of keys1) {
    if (!obj2.hasOwnProperty(key)) return false;
    if (!deepEqual(obj1[key], obj2[key], visited)) return false;
  }
  
  return true;
}

3. 浅比较

3.1 浅比较(只比较第一层)

function shallowEqual(obj1, obj2) {
  if (obj1 === obj2) return true;
  
  if (typeof obj1 !== 'object' || obj1 === null ||
      typeof obj2 !== 'object' || obj2 === null) {
    return false;
  }
  
  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);
  
  if (keys1.length !== keys2.length) return false;
  
  for (let key of keys1) {
    if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {
      return false;
    }
  }
  
  return true;
}

// 使用
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };

console.log(shallowEqual(obj1, obj2));  // false(b 属性引用不同)
console.log(shallowEqual(obj1, obj1));  // true

3.2 React 风格的浅比较

function reactShallowEqual(objA, objB) {
  if (Object.is(objA, objB)) return true;
  
  if (typeof objA !== 'object' || objA === null ||
      typeof objB !== 'object' || objB === null) {
    return false;
  }
  
  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);
  
  if (keysA.length !== keysB.length) return false;
  
  for (let i = 0; i < keysA.length; i++) {
    if (
      !Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
      !Object.is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }
  
  return true;
}

4. 使用第三方库

4.1 Lodash

// 安装: npm install lodash
import _ from 'lodash';

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };

console.log(_.isEqual(obj1, obj2));  // true
console.log(_.isEqualWith(obj1, obj2, customizer));  // 自定义比较

4.2 deep-diff

// 安装: npm install deep-diff
import diff from 'deep-diff';

const differences = diff(obj1, obj2);
if (!differences) {
  console.log('对象相同');
} else {
  console.log('差异:', differences);
}

5. ES6+ 新特性比较

5.1 Object.is

// Object.is 用于比较值
console.log(Object.is(0, -0));       // false
console.log(Object.is(NaN, NaN));    // true
console.log(Object.is({}, {}));      // false(引用比较)

// 与 === 的区别
console.log(+0 === -0);              // true
console.log(NaN === NaN);            // false
console.log(Object.is(+0, -0));      // false
console.log(Object.is(NaN, NaN));    // true

5.2 Map 和 Set

// Set 自动去重
const set1 = new Set([1, 2, 3]);
const set2 = new Set([1, 2, 3]);
console.log(set1 === set2);  // false
console.log(_.isEqual(set1, set2));  // true

// Map
const map1 = new Map([['a', 1]]);
const map2 = new Map([['a', 1]]);
console.log(map1 === map2);  // false
console.log(_.isEqual(map1, map2));  // true

6. 特殊对象比较

6.1 函数比较

function func1() { return 1; }
function func2() { return 1; }
function func3 = func1;

console.log(func1 === func2);  // false
console.log(func1 === func3);  // true
console.log(func1.toString() === func2.toString());  // true(不推荐)

// 函数比较实用方法
function functionsEqual(fn1, fn2) {
  return fn1.toString() === fn2.toString();
}

6.2 类实例比较

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  equals(other) {
    if (!(other instanceof Person)) return false;
    return this.name === other.name && this.age === other.age;
  }
}

const p1 = new Person('Alice', 30);
const p2 = new Person('Alice', 30);
const p3 = new Person('Bob', 25);

console.log(p1 === p2);      // false
console.log(p1.equals(p2));  // true
console.log(p1.equals(p3));  // false

7. 性能比较

function performanceTest() {
  const obj1 = { a: 1, b: 2, c: { d: 3, e: { f: 4 } } };
  const obj2 = { a: 1, b: 2, c: { d: 3, e: { f: 4 } } };
  const obj3 = { a: 1, b: 2, c: { d: 3, e: { f: 5 } } };
  
  const iterations = 10000;
  
  // 1. JSON.stringify
  console.time('JSON.stringify');
  for (let i = 0; i < iterations; i++) {
    JSON.stringify(obj1) === JSON.stringify(obj2);
  }
  console.timeEnd('JSON.stringify');
  
  // 2. 递归深度比较
  console.time('递归深度比较');
  for (let i = 0; i < iterations; i++) {
    deepEqual(obj1, obj2);
  }
  console.timeEnd('递归深度比较');
  
  // 3. Lodash
  console.time('Lodash');
  for (let i = 0; i < iterations; i++) {
    _.isEqual(obj1, obj2);
  }
  console.timeEnd('Lodash');
  
  // 4. 浅比较
  console.time('浅比较');
  for (let i = 0; i < iterations; i++) {
    shallowEqual(obj1, obj2);
  }
  console.timeEnd('浅比较');
}

// 结果通常:
// 浅比较最快
// JSON.stringify 次之
// Lodash 再次
// 递归深度比较最慢

8. 实用工具函数

8.1 通用深度比较

function isDeepEqual(obj1, obj2, options = {}) {
  const {
    strict = true,           // 严格比较
    checkSymbols = false,     // 检查 Symbol
    skipKeys = [],            // 跳过的键
  } = options;
  
  // 快速路径
  if (obj1 === obj2) return true;
  if (obj1 == null || obj2 == null) return false;
  
  const type1 = typeof obj1;
  const type2 = typeof obj2;
  if (type1 !== type2) return false;
  
  // 基本类型
  if (type1 !== 'object' && type1 !== 'function') {
    if (strict) {
      return Object.is(obj1, obj2);
    }
    return obj1 == obj2;
  }
  
  // 构造函数比较
  if (obj1.constructor !== obj2.constructor) return false;
  
  // 特殊对象处理
  if (obj1 instanceof Date) {
    return obj1.getTime() === obj2.getTime();
  }
  
  if (obj1 instanceof RegExp) {
    return obj1.toString() === obj2.toString();
  }
  
  if (obj1 instanceof Map) {
    if (obj1.size !== obj2.size) return false;
    for (let [key, val] of obj1) {
      if (!obj2.has(key) || !isDeepEqual(val, obj2.get(key), options)) {
        return false;
      }
    }
    return true;
  }
  
  if (obj1 instanceof Set) {
    if (obj1.size !== obj2.size) return false;
    for (let val of obj1) {
      if (!obj2.has(val)) return false;
    }
    return true;
  }
  
  // 收集所有键
  const keys1 = [...Object.keys(obj1)];
  const keys2 = [...Object.keys(obj2)];
  
  if (checkSymbols) {
    keys1.push(...Object.getOwnPropertySymbols(obj1));
    keys2.push(...Object.getOwnPropertySymbols(obj2));
  }
  
  // 过滤跳过的键
  const filteredKeys1 = keys1.filter(key => !skipKeys.includes(key));
  const filteredKeys2 = keys2.filter(key => !skipKeys.includes(key));
  
  if (filteredKeys1.length !== filteredKeys2.length) return false;
  
  for (let key of filteredKeys1) {
    if (!obj2.hasOwnProperty(key)) return false;
    if (!isDeepEqual(obj1[key], obj2[key], options)) return false;
  }
  
  return true;
}

8.2 比较指定路径

function compareByPath(obj1, obj2, paths) {
  for (let path of paths) {
    const val1 = get(obj1, path);
    const val2 = get(obj2, path);
    if (!isDeepEqual(val1, val2)) {
      return false;
    }
  }
  return true;
}

function get(obj, path) {
  return path.split('.').reduce((acc, key) => acc && acc[key], obj);
}

// 使用
const obj1 = { a: 1, b: { c: 2, d: 3 }, e: 4 };
const obj2 = { a: 1, b: { c: 2, d: 5 }, e: 4 };

console.log(compareByPath(obj1, obj2, ['a', 'b.c']));  // true
console.log(compareByPath(obj1, obj2, ['a', 'b.d']));  // false

9. 常见场景

9.1 React/PureComponent

// React 的 shouldComponentUpdate
class MyComponent extends React.PureComponent {
  // 自动进行浅比较
}

// 自定义 shouldComponentUpdate
class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return !shallowEqual(this.props, nextProps) ||
           !shallowEqual(this.state, nextState);
  }
}

9.2 Redux

// Redux reducer
function reducer(state = initialState, action) {
  switch (action.type) {
    case 'UPDATE_DATA':
      // 深度比较
      if (_.isEqual(state.data, action.data)) {
        return state;  // 相同,不更新
      }
      return { ...state, data: action.data };
    default:
      return state;
  }
}

9.3 缓存(Memoization)

// 使用深度比较的缓存
function memoize(fn) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    
    for (let [cachedKey, cachedValue] of cache) {
      if (isDeepEqual(JSON.parse(cachedKey), args)) {
        return cachedValue;
      }
    }
    
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
}

10. 最佳实践总结

场景推荐方法原因
比较引用是否相同===Object.is最快,最直接
简单对象深度比较JSON.stringify简单,但有限制
复杂对象深度比较Lodash 的 _.isEqual可靠,功能完整
性能关键,比较第一层浅比较最快,适合不可变数据
自定义比较逻辑自定义比较函数灵活可控
比较类实例实现 equals方法面向对象,语义清晰

选择指南

  1. 引用比较用 ===值比较用深度比较
  2. 性能优先用浅比较精度优先用深度比较
  3. 生产环境用 Lodash简单场景用 JSON.stringify
  4. 特殊对象要特殊处理(Date、RegExp、Map、Set 等)
  5. 注意边界情况(循环引用、Symbol、undefined 等)

根据具体需求选择合适的方法,平衡性能、准确性和可维护性。

以上就是JavaScript判断对象相等性的多种方式的详细内容,更多关于JavaScript对象相等性判断的资料请关注脚本之家其它相关文章!

相关文章

  • 解析浏览器端的AJAX缓存机制

    解析浏览器端的AJAX缓存机制

    这里我们来解析浏览器端的AJAX缓存机制,来共同看一下AJAX缓存与HTTP缓存的关系以及AJAX缓存的刷新时间问题
    2016-06-06
  • uniapp中uni.request(OBJECT)接口请求封装实例代码

    uniapp中uni.request(OBJECT)接口请求封装实例代码

    在开发的时候经常会用到前端请求后端接口,每次的请求都会出现地址不一样,参数不一样,方式不一样等等情况,下面这篇文章主要给大家介绍了关于uniapp中uni.request(OBJECT)接口请求封装的相关资料,需要的朋友可以参考下
    2022-12-12
  • javascript日期比较方法实例分析

    javascript日期比较方法实例分析

    这篇文章主要介绍了javascript日期比较方法,列举了3个实例形式分析了javascript针对日期与时间的相关操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2016-06-06
  • 微信小程序之几种常见的弹框提示信息实现详解

    微信小程序之几种常见的弹框提示信息实现详解

    这篇文章主要介绍了微信小程序之几种常见的弹框提示信息实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • 在百度知道团队中快速审批新成员的js脚本

    在百度知道团队中快速审批新成员的js脚本

    每天都有大量网友申请加入我的团队,于是审核团队新成员成了一个费力气的活儿,在此情况下,我写了个脚本,自动计算他们的回答采纳率,采纳率低于20%的自动打勾 选中,等级太低的人也自动打勾选中
    2014-02-02
  • js、jquery图片动画、动态切换示例代码

    js、jquery图片动画、动态切换示例代码

    这篇文章主要介绍了通过js、jquery实现的图片动画、图片动态切换 ,需要的朋友可以参考下
    2014-06-06
  • Bootstrap风格的zTree右键菜单

    Bootstrap风格的zTree右键菜单

    这篇文章主要介绍了Bootstrap风格的zTree右键菜单功能,实现代码分为html,css和js三部分,代码简单易懂,非常不错,需要的朋友可以参考下
    2017-02-02
  • event.srcElement 用法笔记e.target

    event.srcElement 用法笔记e.target

    event.srcElement 可以捕获当前事件作用的对象,如event.srcElement.tagName可以捕获活动标记名称。
    2009-12-12
  • 微信小程序自定义联系人弹窗

    微信小程序自定义联系人弹窗

    这篇文章主要为大家详细介绍了微信小程序自定义联系人弹窗,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-05-05
  • 在TypeScript项目中进行BDD测试

    在TypeScript项目中进行BDD测试

    这篇文章主要介绍了在TypeScript项目中进行BDD测试,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-04-04

最新评论