ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

30-seconds-of-code:JavaScript 对象比较完全指南——从引用相等、JSON 序列化到递归深度相等

30-seconds-of-code:JavaScript 对象比较完全指南——从引用相等、JSON 序列化到递归深度相等 教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载导读在 JavaScript 中两个对象是否相等远没有初看起来那么简单即使两个对象拥有完全相同的属性和值与也会返回false用JSON.stringify()比较序列化字符串又暗藏多个陷阱真正可靠的方案通常需要借助递归实现深度相等比较。本文基于 30-seconds-of-code 仓库中的 object-comparison.md 展开逐层讲解引用比较、序列化比较与深度相等三种方案的原理、代码实现与边界情况并关联仓库内js/comparison系列的其他文档与源码级佐证帮助你写出可复用的对象比较工具。为什么两个内容相同的对象不相等JavaScript 中对象与数组都是引用类型reference type其相等性判断基于引用reference而非内容。这意味着只要两个对象在内存中不是同一个实例无论其属性如何相似与都会判定它们不相等const a { name: John, age: 26 }; const b { name: John, age: 26 }; a b; // false这与原始值primitive的按值比较截然不同。仓库中的姊妹篇 equality.md 详细解释了双等号宽松相等与三等号严格相等的差异会先进行类型强制转换type coercion再比较值而同时比较类型与值行为更可预测因此日常开发中应优先使用。此外还存在第三个选择Object.is()它与行为基本一致唯一的例外是NaN与0/-0的处理详见 object-is-triple-equals.md{} {}; // false Object.is({}, {}); // false对象依旧按引用比较 0 -0; // true Object.is(0, -0); // false NaN NaN; // false Object.is(NaN, NaN); // true不过要注意Object.is()同样只解决引用是否相同的问题无法比较两个内容相同但引用不同的对象。这正是下面两种方案试图解决的问题。JSON.stringify() 比较法及其陷阱JSON.stringify()经常被当作对象比较的快捷方案把两个对象序列化成字符串后再比较字符串是否相等。const equals (a, b) JSON.stringify(a) JSON.stringify(b); const a { name: John, age: 26 }; const b { name: John, age: 26 }; equals(a, b); // true const c { name: John }; const d { name: John, age: undefined }; equals(c, d); // true但按语义应该为 false上面的例子揭示了该类方案最常见的陷阱相似但不相等的值可能序列化出完全相同的字符串。undefined属性在序列化时会被直接丢弃导致{ name: John }与{ name: John, age: undefined }的序列化结果一致。除此以外JSON.stringify()比较法还存在其他已知限制键的顺序序列化结果依赖键的插入顺序数字键另有排序规则属性顺序不同但内容相同的对象会得到不同字符串函数、Symbol 等特殊值会被序列化流程跳过或处理为特殊值导致误判循环引用遇到自引用对象会抛出TypeError: Converting circular structure to JSON仓库中的 deep-clone-structured-clone.md 同样提到了这一点。因此JSON.stringify()只适合少数恰好没有上述边界情况的场景不能作为通用对象比较方案。真正的解决方案是下面这种递归深度相等比较。深度相等比较手写递归 equals()深度相等比较deep equality通过递归逐层比较两个对象每个嵌套层级的值能够覆盖空值、特殊类型、多层级嵌套等大多数场景。这是社区中各类 shallow/deep equality 工具函数普遍采用的思路。逐步拆解实现逻辑实现equals(a, b)需要依次处理以下场景步骤顺序即代码顺序第一步恒等判断identical先检查a b。这一步同时覆盖了原始值的相等判断以及两个引用指向同一个对象的情况。if (a b) return true;第二步Date 对象特殊处理如果双方都是Date实例用Date.prototype.getTime()比较其时间戳毫秒值。两个表示同一时刻但不同实例的Date对象在下不相等但时间值相等时语义上应当相等。if (a instanceof Date b instanceof Date) return a.getTime() b.getTime();第三步空值与原始值如果任意一方是null/undefined即 falsy或者双方都不是对象原始值则回退到严格比较a b。这样能正确处理原始值相等、以及null/undefined与对象混合的情况。if (!a || !b || (typeof a ! object typeof b ! object)) return a b;第四步原型检查如果两者原型prototype不同直接返回false。这一步防止把具有相同形状但来源不同的对象误判为相等。if (a.prototype ! b.prototype) return false;第五步键数量与递归使用Object.keys()获取双方的可枚举键先比较键的数量是否一致再用Array.prototype.every()遍历a的每个键确认该键在b中存在并递归调用equals()比较对应值。const keys Object.keys(a); if (keys.length ! Object.keys(b).length) return false; return keys.every(k equals(a[k], b[k]));完整实现与运行验证将上述五步组合起来就得到了一份完整的深度相等比较函数const equals (a, b) { if (a b) return true; if (a instanceof Date b instanceof Date) return a.getTime() b.getTime(); if (!a || !b || (typeof a ! object typeof b ! object)) return a b; if (a.prototype ! b.prototype) return false; const keys Object.keys(a); if (keys.length ! Object.keys(b).length) return false; return keys.every(k equals(a[k], b[k])); }; const a { name: John, age: 26 }; const b { name: John, age: 26 }; equals(a, b); // true内容相同 const c { name: John }; const d { name: John, age: undefined }; equals(c, d); // false键数量不同正确区分了 JSON.stringify 会误判的场景 const e [1, 2, 3]; const f { 0: 1, 1: 2, 2: 3 }; equals(e, f); // true数组与数字键对象在此实现下被视为相等从源码验证边界行为最后这个例子值得单独说明equals([1, 2, 3], { 0: 1, 1: 2, 2: 3 })返回true。从该实现看数组与数字索引对象拥有相同的键集合[0, 1, 2]且对应值相等Object.keys()无法区分二者因此被判定为相等。这正是从代码结构可以推断的行为特征该实现更关注键与值的结构一致性而非容器类型本身。如果你的业务要求严格区分数组与普通对象可以在递归前增加Array.isArray()的类型一致性检查。另一个值得留意的边界是原型链上的属性Object.keys()只返回对象自身的可枚举键因此该实现只比较自有属性继承属性不会参与比较。这与仓库中 object-has-value-or-key.md 提到的键检查只针对自身可枚举属性的约定一致。补充方案单向匹配与对称匹配深度相等是双向、全量的比较但很多业务场景只需要部分匹配例如表单校验、配置核对。仓库中的 match-object-properties.md 提供了另一组工具函数可作为深度相等的补充单向匹配判断第一个对象的键是否为第二个对象键的超集且对应值相等。只遍历source的键配合Object.prototype.hasOwnProperty()与const matches (obj, source) Object.keys(source).every( key obj.hasOwnProperty(key) obj[key] source[key] ); matches( { age: 25, hair: long, beard: true }, { hair: long, beard: true } ); // true对称匹配双向比较两个对象的键集合完全一致且值相等利用Set汇总双方键并校验集合大小const matchesSymmetric (a, b) { const keysA Object.keys(a), keysB Object.keys(b); const keys new Set([...Object.keys(a), ...Object.keys(b)]); if (keys.size ! keysA.length || keys.size ! keysB.length) return false; return [...keys].every( key a.hasOwnProperty(key) b.hasOwnProperty(key) a[key] b[key] ); }; matchesSymmetric( { hair: long, beard: true }, { age: 25, hair: long, beard: true } ); // false键集合大小不一致值得注意的是matches/matchesSymmetric默认用做值比较即浅层值比较而equals()用递归做深层值比较两者在嵌套对象场景下行为不同应根据数据深度选择。此外matchesWith/matchesSymmetricWith还支持传入自定义比较函数用于正则、忽略大小写等特殊匹配规则。关联比较与克隆、数组比较理解对象比较还需要把它放入整个js/comparison系列中看待。仓库的 comparison.yaml 将该主题组织为一条完整的知识链包含js/s/equality、js/s/object-comparison、js/s/array-comparison、js/s/date-comparison、js/s/object-is-triple-equals、js/s/match-object-properties、js/s/arrays-have-same-contents等条目。其中与对象比较最密切的两个关联主题是克隆对象按引用传递的机制决定了复制需要克隆。浅克隆只复制外层嵌套对象仍是引用深克隆递归或structuredClone()才能得到完全独立的副本。参见 shallow-deep-clone-object.md 与 deep-clone-structured-clone.md。需要注意的是克隆与比较是两个方向的问题——深度相等比较是判断两个对象内容是否一致而深克隆是生成一个内容一致但独立的对象二者常配合使用例如先克隆再比较或在不可变数据流中校验结果。数组比较数组是对象的一种特殊形态arrays-have-same-contents.md 讨论了忽略顺序、只看元素与频次的比较策略它使用Array.prototype.every()与Set统计唯一值思路与equals()的递归结构互相印证——都是先核对规模长度/键数量再逐项确认。总结与选型建议回顾三种方案可以给出如下的选型建议方案适用场景主要局限//Object.is()引用相等、原始值比较无法比较两个内容相同但引用不同的对象JSON.stringify()比较无undefined、无函数、无循环引用的扁平对象序列化会丢弃/改写特殊值键顺序影响结果递归深度相等equals()嵌套对象、Date、多层级结构的通用比较需要自行维护边界情况且对数组与数字键对象不区分实际工程中推荐优先采用递归深度相等这类显式实现或社区成熟的 deep-equal 类库并在其中显式处理业务相关的边界类型Date、Map、Set、RegExp、循环引用等。如果比较只是验证某个对象是否包含另一对象的属性子集matches系列是更轻量的选择。将 object-comparison.md 与js/comparison系列的其他文档结合阅读你就能系统掌握 JavaScript 值比较的全貌。赞分享教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载相关推荐30 Seconds of CodeJavaScript 中两个 Date 对象的比较相等、先后与区间判断完整指南30 Seconds of CodeJavaScript 中两个 Date 对象的比较相等、先后与区间判断完整指南 JavaScript 的 Date 对教程文档30 seconds of codeJavaScript 中 CSV 与数组、对象、JSON 的相互转换实战指南30 seconds of codeJavaScript 中 CSV 与数组、对象、JSON 的相互转换实战指南 CSV逗号分隔值 是一种以纯文本存储表格教程文档30 seconds of codeJavaScript 数组比较的完整实战指南30 seconds of codeJavaScript 数组比较的完整实战指南 导读 在 JavaScript 中比较两个数组是否相等是开发中最高频也教程文档上一篇pi-mono的可扩展性设计应对业务增长的架构考量下一篇ALVR有线串流实战指南突破Wi-Fi限制实现稳定低延迟VR体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表