专业编程教程与实战项目分享平台

网站首页 > 技术文章 正文

比较 JavaScript 对象的四种方式「实践」

ins518 2024-12-18 14:53:56 技术文章 52 ℃ 0 评论


作者:疯狂的技术宅

转发链接:https://mp.weixin.qq.com/s/7jBWuhrBXbzdsC7CHmSxHQ

前言

比较 JavaScript 中的值非常简单,只需用相等运算符即可,例如严格相等运算符:

'a' === 'c'; // => false
1   === 1;   // => true

但是对象却有结构化的数据,所以比较起来比较困难。在本文中,你将学习如何正确比较 JavaScript 中的对象。

1. 引用比较

JavaScript 提供了 3 种方法来对值进行比较:

  • 严格相等运算符 ===
  • 宽松相等运算符 ==
  • Object.is() 函数

当使用上述任何一种方法比较对象时,只有在比较的值引用了相同的对象实例时,比较的结果为 true。这就是参照相等性。

让我们定义对象 hero1 和 hero2 ,并查看实际中的参照相等性:

const hero1 = {
   name: 'Batman'
 };
 const hero2 = {
   name: 'Batman'
 };
 
 hero1 === hero1; // => true
 hero1 === hero2; // => false

hero1 == hero1; // => true
hero1 == hero2; // => false

Object.is(hero1, hero1); // => true
Object.is(hero1, hero2); // => false

hero1 === hero1 的计算结果为 true,因为两个操作数均指向了同一个对象实例hero1。

另一方面,hero1 === hero2 的计算结果为 false,因为 hero1 和 hero2 是不同的对象实例。

有意思的是,hero1 和 hero2 对象的内容是相同的:两个对象都有一个 name 属性,它的其值是 'Batman'。尽管如此,即使比较相同结构的对象,hero1 === hero2 的结果也是 false。

当你想比较对象引用而不是它们的内容时,引用相等是很有用的。但是在更多的情况之下,你都想针对对象的实际内容进行比较:例如属性及它们的值。

接下来看看如何通过对象的内容比较对象是否相等。

2. 手动比较

按内容比较对象最直接的方法是读取属性并手动比较它们。

例如,让我们编写一个特殊的函数 isHeroEqual() 来比较两个 hero 对象:

 function isHeroEqual(object1, object2) {
   return object1.name === object2.name;
 }
 
 const hero1 = {
   name: 'Batman'
 };
 const hero2 = {
   name: 'Batman'
};
const hero3 = {
  name: 'Joker'
};

isHeroEqual(hero1, hero2); // => true
isHeroEqual(hero1, hero3); // => false

isHeroEqual() 访问两个对象的属性 name 并比较它们的值。

如果被比较的对象具有一些属性,我更喜欢编写诸如 isHeroEqual() 之类的比较函数。这类函数具有良好的性能:在比较中只会涉及少数几个属性访问器和相等运算符。

手动比较需要手动提取属性,对于简单对象来说,这不是问题。但是,要对较大的对象(或结构未知的对象)进行比较,就不方便了,因为它需要大量的样板代码。

那么让我们来看看对象的浅层比较能提供哪些帮助。

3. 浅层比较

如果用浅层比较检查对象,你必须获取两个对象的属性列表(使用Object.keys()),然后检查它们的属性值是否相等。

下面的代码是浅层比较的一种实现方式:

 function shallowEqual(object1, object2) {
   const keys1 = Object.keys(object1);
   const keys2 = Object.keys(object2);
 
   if (keys1.length !== keys2.length) {
     return false;
   }
 
   for (let index = 0; index < keys1.length; index++) {
    const val1 = object1[keys1[index]];
    const val2 = object2[keys2[index]];
    if (val1 !== val2) {
      return false;
    }
  }

  return true;
}

在函数内部,keys1 和 keys2 是分别包含 object1 和 object2 属性名称的数组。

用 for 循环遍历键,并比较 object1 和 object2 的每个属性。

使用浅层比较,你可以轻松对有着许多属性的对象进行相等性检查:

 const hero1 = {
  name: 'Batman',
  realName: 'Bruce Wayne'
 };
 const hero2 = {
   name: 'Batman',
   realName: 'Bruce Wayne'
 };
 const hero3 = {
  name: 'Joker'
};

shallowEqual(hero1, hero2); // => true
shallowEqual(hero1, hero3); // => false

shallowEqual(hero1, hero2) 返回 true,因为对象 hero1 和 hero2 具有相同的属性(name 和 realName),并且值也相同。

另一方面,由于 hero1 和 hero3 具有不同的属性,所以 shallowEqual(hero1, hero3) 将会返回 false。

但是 JavaScript 中的对象是可以嵌套的。在这种情况下,浅层比较并不能很好地发挥作用。

下面对具有嵌套对象的对象执行浅层比较检查:

 const hero1 = {
  name: 'Batman',
 address: {
   city: 'Gotham'
  }
};
const hero2 = {
 name: 'Batman',
  address: {
    city: 'Gotham'
  }
};

shallowEqual(hero1, hero2); // => false

这次,即使两个对象 hero1 和 hero2 具有相同的内容,shallowEqual(hero1, hero2) 也将会返回 false。

发生这种情况是因为嵌套对象 hero1.address 和 hero2.address 是不同的对象实例。因此,浅层比较认为 hero1.address 和 hero2.address 是两个不同的值。

解决嵌套对象的问题需要进行深层比较。

4. 深层比较

深层比较与浅层比较相似,不同之处在于,当属性中包含对象时,将对嵌套对象执行递归浅层比较。

看一下深层比较的实现:

 function deepEqual(object1, object2) {
  const keys1 = Object.keys(object1);
 const keys2 = Object.keys(object2);

   if (keys1.length !== keys2.length) {
    return false;
  }
 
   for (let index = 0; index < keys1.length; index++) {
    const val1 = object1[keys1[index]];
    const val2 = object2[keys2[index]];
    const areObjects = isObject(val1) && isObject(val2);
    if (areObjects && !deepEqual(val1, val2) || 
        !areObjects && val1 !== val2) {
      return false;
    }
  }

  return true;
}

function isObject(object) {
  return object != null && typeof object === 'object';
}

第 13 行的 areObjects && !deepEqual(val1, val2) 一旦检查到的属性是对象,则递归调用将会开始验证嵌套对象是否也相等。

现在用 deepEquality() 比较具有嵌套对象的对象:

 const hero1 = {
   name: 'Batman',
   address: {
     city: 'Gotham'
  }
 };
 const hero2 = {
  name: 'Batman',
   address: {
    city: 'Gotham'
  }
};

deepEqual(hero1, hero2); // => true

深度比较函数能够正确地确定 hero1 和 hero2 是否具有相同的属性和值,包括嵌套对象 hero1.address 和 hero2.address 的相等性。

为了深入比较对象,我建议使用Node内置util模块的 isDeepStrictEqual(object1, object2)(https://nodejs.org/api/util.html#util_util_isdeepstrictequal_val1_val2) 或lodash 库的_.isEqual(object1, object2)(https://lodash.com/docs/4.17.15#isEqual) 。

5. 总结

引用相等性(使用 ===、 == 或 Object.is())用来确定操作数是否为同一个对象实例。

手动检查对象是否相等,需要对属性值进行手动比较。尽管这类检查需要手动编码来对属性进行比较,但由于很简单,所以这种方法很方便。

当被比较的对象有很多属性或在运行时确定对象的结构时,更好的方法是使用浅层检查。

如果比较的对象具有嵌套对象,则应该进行深度比较检查。

推荐JavaScript经典实例学习资料文章

使用Service Worker让你的 Web 应用如虎添翼(上)「干货」

使用Service Worker让你的 Web 应用如虎添翼(中)「干货」

使用Service Worker让你的 Web 应用如虎添翼(下)「干货」

前端如何一次性处理10万条数据「进阶篇」

推荐三款正则可视化工具「JS篇」

如何让用户选择是否离开当前页面?「JS篇」

JavaScript开发人员更喜欢Deno的五大原因

仅用18行JavaScript实现一个倒数计时器

图文细说JavaScript 的运行机制

一个轻量级 JavaScript 全文搜索库,轻松实现站内离线搜索

推荐Web程序员常用的15个源代码编辑器

10个实用的JS技巧「值得收藏」

细品269个JavaScript小函数,让你少加班熬夜(一)「值得收藏」

细品269个JavaScript小函数,让你少加班熬夜(二)「值得收藏」

细品269个JavaScript小函数,让你少加班熬夜(三)「值得收藏」

细品269个JavaScript小函数,让你少加班熬夜(四)「值得收藏」

细品269个JavaScript小函数,让你少加班熬夜(五)「值得收藏」

细品269个JavaScript小函数,让你少加班熬夜(六)「值得收藏」

深入JavaScript教你内存泄漏如何防范

手把手教你7个有趣的JavaScript 项目-上「附源码」

手把手教你7个有趣的JavaScript 项目-下「附源码」

JavaScript 使用 mediaDevices API 访问摄像头自拍

手把手教你前端代码如何做错误上报「JS篇」

一文让你彻底搞懂移动前端和Web 前端区别在哪里

63个JavaScript 正则大礼包「值得收藏」

提高你的 JavaScript 技能10 个问答题

JavaScript图表库的5个首选

一文彻底搞懂JavaScript 中Object.freeze与Object.seal的用法

可视化的 JS:动态图演示 - 事件循环 Event Loop的过程

教你如何用动态规划和贪心算法实现前端瀑布流布局「实践」

可视化的 js:动态图演示 Promises & Async/Await 的过程

原生JS封装拖动验证滑块你会吗?「实践」

如何实现高性能的在线 PDF 预览

细说使用字体库加密数据-仿58同城

Node.js要完了吗?

Pug 3.0.0正式发布,不再支持 Node.js 6/8

纯JS手写轮播图(代码逻辑清晰,通俗易懂)

JavaScript 20 年 中文版之创立标准

值得收藏的前端常用60余种工具方法「JS篇」

箭头函数和常规函数之间的 5 个区别

通过发布/订阅的设计模式搞懂 Node.js 核心模块 Events

「前端篇」不再为正则烦恼

「速围」Node.js V14.3.0 发布支持顶级 Await 和 REPL 增强功能

深入细品浏览器原理「流程图」

JavaScript 已进入第三个时代,未来将何去何从?

前端上传前预览文件 image、text、json、video、audio「实践」

深入细品 EventLoop 和浏览器渲染、帧动画、空闲回调的关系

推荐13个有用的JavaScript数组技巧「值得收藏」

前端必备基础知识:window.location 详解

不要再依赖CommonJS了

犀牛书作者:最该忘记的JavaScript特性

36个工作中常用的JavaScript函数片段「值得收藏」

Node + H5 实现大文件分片上传、断点续传

一文了解文件上传全过程(1.8w字深度解析)「前端进阶必备」

【实践总结】关于小程序挣脱枷锁实现批量上传

手把手教你前端的各种文件上传攻略和大文件断点续传

字节跳动面试官:请你实现一个大文件上传和断点续传

谈谈前端关于文件上传下载那些事【实践】

手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件

最全的 JavaScript 模块化方案和工具

「前端进阶」JS中的内存管理

JavaScript正则深入以及10个非常有意思的正则实战

前端面试者经常忽视的一道JavaScript 面试题

一行JS代码实现一个简单的模板字符串替换「实践」

JS代码是如何被压缩的「前端高级进阶」

前端开发规范:命名规范、html规范、css规范、js规范

【规范篇】前端团队代码规范最佳实践

100个原生JavaScript代码片段知识点详细汇总【实践】

关于前端174道 JavaScript知识点汇总(一)

关于前端174道 JavaScript知识点汇总(二)

关于前端174道 JavaScript知识点汇总(三)

几个非常有意思的javascript知识点总结【实践】

都2020年了,你还不会JavaScript 装饰器?

JavaScript实现图片合成下载

70个JavaScript知识点详细总结(上)【实践】

70个JavaScript知识点详细总结(下)【实践】

开源了一个 JavaScript 版敏感词过滤库

送你 43 道 JavaScript 面试题

3个很棒的小众JavaScript库,你值得拥有

手把手教你深入巩固JavaScript知识体系【思维导图】

推荐7个很棒的JavaScript产品步骤引导库

Echa哥教你彻底弄懂 JavaScript 执行机制

一个合格的中级前端工程师需要掌握的 28 个 JavaScript 技巧

深入解析高频项目中运用到的知识点汇总【JS篇】

JavaScript 工具函数大全【新】

从JavaScript中看设计模式(总结)

身份证号码的正则表达式及验证详解(JavaScript,Regex)

浏览器中实现JavaScript计时器的4种创新方式

Three.js 动效方案

手把手教你常用的59个JS类方法

127个常用的JS代码片段,每段代码花30秒就能看懂-【上】

深入浅出讲解 js 深拷贝 vs 浅拷贝

手把手教你JS开发H5游戏【消灭星星】

深入浅出讲解JS中this/apply/call/bind巧妙用法【实践】

手把手教你全方位解读JS中this真正含义【实践】

书到用时方恨少,一大波JS开发工具函数来了

干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)

手把手教你JS 异步编程六种方案【实践】

让你减少加班的15条高效JS技巧知识点汇总【实践】

手把手教你JS开发H5游戏【黄金矿工】

手把手教你JS实现监控浏览器上下左右滚动

JS 经典实例知识点整理汇总【实践】

2.6万字JS干货分享,带你领略前端魅力【基础篇】

2.6万字JS干货分享,带你领略前端魅力【实践篇】

简单几步让你的 JS 写得更漂亮

恭喜你获得治疗JS this的详细药方

谈谈前端关于文件上传下载那些事【实践】

面试中教你绕过关于 JavaScript 作用域的 5 个坑

Jquery插件(常用的插件库)

【JS】如何防止重复发送ajax请求

JavaScript+Canvas实现自定义画板

Continuation 在 JS 中的应用「前端篇」

作者:疯狂的技术宅

转发链接:https://mp.weixin.qq.com/s/7jBWuhrBXbzdsC7CHmSxHQ

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表