JavaScript 深入数据结构之 Set和Map

124 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第26天,点击查看活动详情

JavaScript 中自带的主要数据结构也就这两种了,前面我们说到的数据类型不算是数据结构,而对于浸染前端多年的人,这两种结构肯定是必须熟悉并且了解透彻的,对于刚刚入门的来讲,也要知道他们是干嘛的~,如果找工作被问到,当然我会经常问这两个结构,至少不会麻爪哦。接下来咱们就好好聊聊她俩~

Set

一种松散型的集合数据结构,数组属于是紧急结构 有下标,而Set 属于松散型,没有下标,没有键。这里咱们就不介绍 Set 那些方法了,文档上也都有,咱们就聊聊他的应用。

  • 特点
    1. 不接受重复的数据
    2. 按照给的数据的顺序排序
    3. 没有索引
let set = new Set()
let s1 = Symbol()
set.add(1)
set.add(1)
set.add({a:100})
set.add({a:100})
set.add(true)
set.add(true)
set.add(null)
set.add(null)
set.add(undefined)
set.add(undefined)
set.add(Symbol())
set.add(s1)
set.add('1')
set.add('1')

console.log(set);

打印如下

Snipaste_2022-12-15_12-02-22.png
由此可见,其存储唯一值的特性,存储对象的话也相当于是存储的指针,两个相同的指针只会存储一次,简单数据类型都不会重复,而对于Symbol来说,两个Symbol都是不相等的。

去重

  • 数组去重是面试中比较常问的问题了,除了使用遍历,可以用多种方法实现,包括但不限于: index0ffilterreduce等,ES6 之后出现的 Set.
function delRepeat(arr){
    var arr2 = Array.from(new Set(arr))
    // 或 var arr2 = [...new Set(arr)]
    return arr2
}

过滤

  • 在业务当中我们在哪些情况下可以使用 Set 呢?
//测试用例
let a = [1, 2, 3, 4, 5]
let b = [3, 4, 5, 6, 7]
//并集,讲两个数组分别结构到set的构造种
function merge(arr1, arr2) {
    return new Set([...arr1, ...arr2])
}
//交集
function unite(arr1, arr2) {
    return new Set([...arr1].filter(v=>arr2.some(v2=>v2===v)))
}
//差集,某个数组减去交集
function difference(arr1, arr2) {
    let un = merge(arr1,arr2)
    let itsct = unite(arr1, arr2);
    //整体差集
    return new Set([...un].filter(v=>!itsct.has(v)))
    //相对差集
    // return new Set([...arr1].filter(v => !itsct.has(v)))
}
console.log('merge is ', merge(a, b))
console.log('unite is ', unite(a, b));
console.log('difference is ',difference(b,a));

打印如下: Snipaste_2022-12-15_12-08-06.png 我在实际业务当中使用 Set 的频率要明显低于 Map,在项目使用的时候也一般会配合 Map 来进行使用,下面是我之前实际开发中的一个使用,大家可以简单看一下:

Snipaste_2022-12-15_12-20-10.png

Snipaste_2022-12-15_12-21-00.png 这是一个 vue3 的项目 使用的是 pinia 进行的状态管理,业务逻辑就不谈了,这里我使用了一个 set 一个 map 两个结构来组成一个映射的效果,因为需要频繁操作数据,如果使用数组和对象,在性能上实际是比 这种映射来说慢很多,数组我们删除会数组塌陷,对象查找需要遍历 key 再查找等等,最终选择了这种方式,但在开发中,要使用这种方式,肯定是要对这两个结构的方法要了解的,这点我觉得大家看一下文档肯定都能会啦~

Map

Map 是一以字典形式存储的一种数据结构,其数据存取使用的是 hash 碰撞检测算法, 面试中一般说到这里就可以了,知道其原理,但是有的面试官会问 什么是 hash 碰撞检测算法,那这个就可答可不答,因为这是其底层实现,即使看过也很容易忘,直说即可。

  • 我们使用对象的时候,对象的 key 只能存储 字符串 和 Symbol,有的人面试的时候只知道字符串~,而 Map 结构可以让我们存储任意类型
let mp = new Map()
mp.set(1,{a:1})
mp.set(1,{a:100})  // 重复添加 后 一个的值会覆盖掉前一个
mp.set(true,100)
mp.set(null,{b:100})
mp.set(undefined,{b:100})
mp.set('1','123')
mp.set(Symbol(),100)
mp.set(Symbol(),100)

console.log(mp);

打印如下:

Snipaste_2022-12-15_12-51-33.png\

    1. 大家可以看到,其虽然有一个看着像是下标的 0,1,2... 但实际上那并不是下标,只是在控制台打印的时候,浏览器给我们在展示这一步进行了一个排列,这个排列对于我们的代码层面无用,拿不到也用不了,这个排列也是按照我们设置进去的顺序来的。
    1. 其次这里的 重复添加会覆盖上一个。
    1. 对于 Symbol 是和 Set 一样的。
    1. 其存储的是值——值的映射关系,我们存储的 key 对于引用类型而言是存储的指针。
    1. 最后就是其性能了,我之前看过一篇文档,一个博主使用了性能测试对 对象 和Map 进行了检测,分别检测其增删改查的性能数据,具体数值忘了,以十万级为阙值,对象结构的性能 在十万以内 比 Map30% , 在接近十万体量的过程中数值不断减小。
  • 这里加说几句,map性能, map比对象而言结构上还有几点性能优势, 第一是 map 不需要把所有键转换为字符串,第二是 长度,对象需要通过 keys 获取 length 来得到对象有多少数据,时间复杂度是 o(n) , map 结构只需要通过 size 来确定, 复杂度是 o(1)。

我在项目中使用 Map 还是比较多的,其优势明显强于 对象,而某些情况下,使用 WeckMap 更加优越,从一些包库的源码中便可以窥探其一二了~,下面是我项目里的一个简单例子。

Snipaste_2022-12-15_12-51-33.png

Snipaste_2022-12-15_13-03-13.png

Snipaste_2022-12-15_13-03-33.png

Snipaste_2022-12-15_13-03-53.png 我这个项目同时也是 vue3+ts+pinia,那么这里我有一个队列,就是 sessionList, 使用的就是一个 数组 和 Map 的映射来存储,大家可以看到我这里的 增、删、查,改也是同样的,查到以后,拿到的 Map 中的其实就是这个对象的引用,可以直接修改~,这么做的原因是我这队列会经常更新,频率很频繁,如果我直接使用数组,那会造成频繁的数组内部对对象的增删改查操作,而且对于渲染视图来讲也有很大弊端,这部分代码写了有半年以上了,具体原因其实忘差不多了哈哈, 总之那时候也是试过了数组和对象,最终才选择了 Map,下面来看一下这个数据如何使用的:

Snipaste_2022-12-15_13-10-09.png 这个 showList 就是我那个 sessionList 中的数组, 这里我循环的是这个数组, 那这里的红线是因为更新了插件,这里使用了 get 去获取 Map 结构中(showSessionObj),数组中每项是 item 的值,其实我们的 , Map 结构也可以转成对象结构,但那可就没有了意义,虽然那样就可以使用对象的语法进行数据访问,但要记得, Map 的性能要强于对象哦~

WeakSet 和 WeakMap

这两个在面试的时候问的频率不大,但是如果问到了,能够答上来算给自己加分了,没有问的话,也可以在 SetMap 之上自己进行扩展, 回答的时候掌握好几个关键点,弱引用,垃圾回收机制,不能存储简单数据类型,下面看一下阮一峰的解释:

Snipaste_2022-12-15_17-31-44.png 下面是我自己总结的两者导图的笔记,可以直观的看出来区别:

Snipaste_2022-12-15_17-37-50.png

Snipaste_2022-12-15_17-38-12.png

  • 总结:JavaScript 给我们提供了这样的结构,就证明其必然有其存在的道理,和某些条件下能够使用的必要性而达到优化的目的,写代码的时候思路不应该仅仅局限于自己之前学习到的知识,应该能够懂得灵活运用各种方式优化自己的代码~
  • 引用: 阮一峰ES6