开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第26天,点击查看活动详情
JavaScript
中自带的主要数据结构也就这两种了,前面我们说到的数据类型不算是数据结构,而对于浸染前端多年的人,这两种结构肯定是必须熟悉并且了解透彻的,对于刚刚入门的来讲,也要知道他们是干嘛的~,如果找工作被问到,当然我会经常问这两个结构,至少不会麻爪哦。接下来咱们就好好聊聊她俩~
Set
一种松散型的集合数据结构,数组属于是紧急结构 有下标,而
Set
属于松散型,没有下标,没有键。这里咱们就不介绍Set
那些方法了,文档上也都有,咱们就聊聊他的应用。
- 特点
- 不接受重复的数据
- 按照给的数据的顺序排序
- 没有索引
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);
打印如下
由此可见,其存储唯一值的特性,存储对象的话也相当于是存储的指针,两个相同的指针只会存储一次,简单数据类型都不会重复,而对于Symbol
来说,两个Symbol
都是不相等的。
去重
- 数组去重是面试中比较常问的问题了,除了使用遍历,可以用多种方法实现,包括但不限于:
index0f
,filter
,reduce
等,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));
打印如下:
我在实际业务当中使用
Set
的频率要明显低于 Map
,在项目使用的时候也一般会配合 Map
来进行使用,下面是我之前实际开发中的一个使用,大家可以简单看一下:
这是一个
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);
打印如下:
\
-
- 大家可以看到,其虽然有一个看着像是下标的 0,1,2... 但实际上那并不是下标,只是在控制台打印的时候,浏览器给我们在展示这一步进行了一个排列,这个排列对于我们的代码层面无用,拿不到也用不了,这个排列也是按照我们设置进去的顺序来的。
-
- 其次这里的 重复添加会覆盖上一个。
-
- 对于
Symbol
是和Set
一样的。
- 对于
-
- 其存储的是值——值的映射关系,我们存储的
key
对于引用类型而言是存储的指针。
- 其存储的是值——值的映射关系,我们存储的
-
- 最后就是其性能了,我之前看过一篇文档,一个博主使用了性能测试对 对象 和
Map
进行了检测,分别检测其增删改查的性能数据,具体数值忘了,以十万级为阙值,对象结构的性能 在十万以内 比Map
慢 30% , 在接近十万体量的过程中数值不断减小。
- 最后就是其性能了,我之前看过一篇文档,一个博主使用了性能测试对 对象 和
- 这里加说几句,map性能, map比对象而言结构上还有几点性能优势, 第一是 map 不需要把所有键转换为字符串,第二是 长度,对象需要通过 keys 获取 length 来得到对象有多少数据,时间复杂度是 o(n) , map 结构只需要通过 size 来确定, 复杂度是 o(1)。
我在项目中使用
Map
还是比较多的,其优势明显强于 对象,而某些情况下,使用 WeckMap 更加优越,从一些包库的源码中便可以窥探其一二了~,下面是我项目里的一个简单例子。
我这个项目同时也是
vue3+ts+pinia
,那么这里我有一个队列,就是 sessionList
, 使用的就是一个 数组 和 Map
的映射来存储,大家可以看到我这里的 增、删、查,改也是同样的,查到以后,拿到的 Map
中的其实就是这个对象的引用,可以直接修改~,这么做的原因是我这队列会经常更新,频率很频繁,如果我直接使用数组,那会造成频繁的数组内部对对象的增删改查操作,而且对于渲染视图来讲也有很大弊端,这部分代码写了有半年以上了,具体原因其实忘差不多了哈哈, 总之那时候也是试过了数组和对象,最终才选择了 Map
,下面来看一下这个数据如何使用的:
这个
showList
就是我那个 sessionList
中的数组, 这里我循环的是这个数组, 那这里的红线是因为更新了插件,这里使用了 get
去获取 Map
结构中(showSessionObj
),数组中每项是 item
的值,其实我们的 , Map
结构也可以转成对象结构,但那可就没有了意义,虽然那样就可以使用对象的语法进行数据访问,但要记得, Map
的性能要强于对象哦~
WeakSet 和 WeakMap
这两个在面试的时候问的频率不大,但是如果问到了,能够答上来算给自己加分了,没有问的话,也可以在
Set
和Map
之上自己进行扩展, 回答的时候掌握好几个关键点,弱引用,垃圾回收机制,不能存储简单数据类型,下面看一下阮一峰的解释:
下面是我自己总结的两者导图的笔记,可以直观的看出来区别:
- 总结:
JavaScript
给我们提供了这样的结构,就证明其必然有其存在的道理,和某些条件下能够使用的必要性而达到优化的目的,写代码的时候思路不应该仅仅局限于自己之前学习到的知识,应该能够懂得灵活运用各种方式优化自己的代码~ - 引用: 阮一峰ES6