ARTICLE DETAIL

资讯详情

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

jstips 第 37 期:JavaScript 数组去重完整实战指南(原始值、对象与混合类型)

jstips 第 37 期:JavaScript 数组去重完整实战指南(原始值、对象与混合类型) 教程【免费下载链接】jstipsThis is about useful JS tips!项目地址https://gitcode.com/gh_mirrors/js/jstips点击查看免费下载本文基于开源项目 jstipsGitHub 加速计划 / js / jstips仓库中的官方文档 《数组去重》Tip #37展开系统讲解如何移除数组中不同类型元素的重复项从filterindexOf的经典方案到 ES2015 的SetArray.from简洁写法再到针对对象引用比较问题的哈希表 JSON.stringify方案。读完本文你将掌握一套适用于原始值、对象以及混合类型数组的完整去重方法论并能理解其中“值相等”与“引用相等”的底层差异。原始值Primitives去重filter indexOf如果一个数组只包含原始变量primitive values我们可以仅使用filter和indexOf方法将其去重var deduped [ 1, 1, a, a ].filter(function (el, i, arr) { return arr.indexOf(el) i; }); console.log(deduped); // [ 1, a ]这个写法的原理filter回调会为每个元素接收三个参数当前元素el、当前索引i、以及原数组arr。arr.indexOf(el)返回的是该元素首次出现的位置。因此如果indexOf(el) i说明当前元素是它第一次出现应当保留如果indexOf(el) ! i说明前面已经出现过相同的值应当丢弃。正因为去重对象限定在原始值number、string 等indexOf内部使用的严格相等比较才能可靠地判断“值相同”例如1 1、a a均为true。ES2015 进阶箭头函数与 Set我们可以使用箭头函数使写法更简明var deduped [ 1, 1, a, a ].filter( (el, i, arr) arr.indexOf(el) i); console.log(deduped); // [ 1, a ]但是根据 Sets 和from方法的介绍我们可以更简明地实现var deduped Array.from( new Set([ 1, 1, a, a ]) ); console.log(deduped); // [ 1, a ]Set天然不包含重复元素Array.from再把 Set 转回数组整个过程仅一行代码。jstips 仓库中的另一篇文档 《Array 的三个技巧》Tip #64给出了同一思路的展开运算符变体同样简洁高效const arr [...new Set([1, 2, 3, 3])]; // [1, 2, 3]说明无论Set还是filter indexOf对原始值都能正确去重但注意NaN与indexOf的配合存在差异——indexOf无法找到NaN因为NaN ! NaN而Set可以。如果数组中可能包含NaN建议优先选择Set方案。对象Objects去重为什么不能直接复用上述方法当元素为对象Object时我们就不能用这种办法了因为对象存储的是引用而原始变量存储的是值1 1 // true a a // true { a: 1 } { a: 1 } // false即使两个对象的内容完全一致都是{ a: 1 }它们也各自拥有独立的内存引用严格相等比较结果为false。因此无论是indexOf还是Set都无法把“内容相同但引用不同”的对象识别为重复项。解决方案哈希表Hash Table因此我们需要改变一下我们的实现方法使用哈希表function dedup(arr) { var hashTable {}; return arr.filter(function (el) { var key JSON.stringify(el); var match Boolean(hashTable[key]); return (match ? false : hashTable[key] true); }); } var deduped dedup([ { a: 1 }, { a: 1 }, [ 1, 2 ], [ 1, 2 ] ]); console.log(deduped); // [ {a: 1}, [1, 2] ]核心思路是把每个元素用JSON.stringify序列化成字符串作为hashTable的键第一次遇到时键不存在match为false写入true并保留该元素再次遇到相同内容时键已存在直接丢弃。深入原理哈希表的 key 永远是 String因为哈希表在 JavaScript 里是一个简单的Object它的key永远是String类型。这意味着我们不能区分字符串和数字表示的相同的值如1和1var hashTable {}; hashTable[1] true; hashTable[1] true; console.log(hashTable); // { 1: true }数字键1会被隐式转换为字符串键1两次赋值实际写入了同一个键最终只剩一个1: true。JSON.stringify 如何解决键冲突然而因为我们使用的是JSON.stringifyString类型的 key 将会被存储为一个带引号的字符串值这样hashTable的 key 就唯一了var hashTable {}; hashTable[JSON.stringify(1)] true; hashTable[JSON.stringify(1)] true; console.log(hashTable); // { 1: true, \1\: true }JSON.stringify(1)生成键1而JSON.stringify(1)生成带转义引号的键\1\控制台显示为\1\两者不再冲突。这意味着相同的值、但不同类型的元素将以原来的格式保留var deduped dedup([ { a: 1 }, { a: 1 }, [ 1, 2 ], [ 1, 2 ], 1, 1, 1, 1 ]); console.log(deduped); // [ {a: 1}, [1, 2], 1, 1 ]在这个混合类型数组中对象{ a: 1 }的重复项、数组[ 1, 2 ]的重复项、数字1的重复项和字符串1的重复项都被正确移除同时数字1与字符串1作为不同类型的元素被同时保留。边界与注意事项JSON 序列化的局限性JSON.stringify无法序列化undefined、函数、Symbol以及循环引用的对象会抛TypeError。因此dedup方案适用于可 JSON 序列化的数据遇到含undefined属性或函数成员的对象时这些成员会在序列化过程中被忽略可能导致“内容不同却得到相同键”。对象属性顺序问题JSON.stringify({a:1, b:2})与JSON.stringify({b:2, a:1})会得到不同的字符串键按插入顺序输出因此“内容相同但属性书写顺序不同”的对象不会被判为重复需要根据业务需求决定是否先规范化键顺序。复杂度对比filter indexOf方案在每次回调内执行一次indexOf扫描整体时间复杂度为 O(n²)Set方案基于哈希结构整体为 O(n)在大数组上性能更优。对象去重的哈希表方案同样是 O(n)但以JSON.stringify的序列化开销为代价。阅读材料函数filterindexOffromJSON.stringifyES2015箭头函数Set仓库内延伸阅读Array 的三个技巧Tip #64含[...new Set()]去重写法使用 JSON.stringifyTip #40深入讲解序列化的第二个、第三个参数多维数组扁平化Tip #38赞分享教程【免费下载链接】jstipsThis is about useful JS tips!项目地址https://gitcode.com/gh_mirrors/js/jstips点击查看免费下载相关推荐HCCL PR 检视规范PR 描述与测试完备性核查指南HCCL PR 检视规范PR 描述与测试完备性核查指南 导读 本文面向向 CANN / hccl昇腾集合通信库提交代码的开发者与代码检视者系统讲解 HC教程gog meet history 深度解析用 gogcli 在终端列出 Google Meet 会议历史gog meet history 深度解析用 gogcli 在终端列出 Google Meet 会议历史 gog meet history 是 gogcli教程《你不知道的 JSthis 与对象原型》第 4 章精读如何在 JavaScript 中混合搅乱类对象——从类理论到 Mixins 的完整实战指南《你不知道的 JSthis 与对象原型》第 4 章精读如何在 JavaScript 中混合搅乱类对象——从类理论到 Mixins 的完整实战指南教程文档上一篇Agno Workflow 条件分支实战用 Router 构建动态选择的多 Agent 工作流下一篇Sim Helm Chart values.yaml 四层心智模型理解环境变量归属与覆盖优先级创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表