面试官(朋友):看简历上,您对JS很熟悉,您了解set和map吗?
我:set我常用来做数据去重,map没了解过。
面试官(朋友):哈哈哈,您真是太幽默了,欢迎那您来参加面试,后续人事会通知您面试结果的。
正文
关于set和map的面试题,我个人确实没怎么看,除了一开始翻过ES6的书,记得要用set去重数组之外,后续就没有深入了解了。
我甚至记不得weakset和weakmap这两种。
这里今天朋友客串了一把面试官,然后狠狠地嘲笑了我一顿,想想真是糟糕。
Set和Map
Set和Map是ES6新增加的数据类型。其中Set被称作“集合”,Map被称作“映射”。
新增的这两个数据结构提供了更灵活和强大的方式来处理和存储数据。
Set
Set是一个简单值的集合,类似于数组。Set的特点:Set成员的值都是唯一的,不允许重复。
通过Set这一特性,可以运用Set进行去重操作。
应用场景
数组去重
通过数组的解构和Set无重复值的特性,可以产生一个数组去重的小妙招。
1 | const arr = [1, 2, 3, 1, 2, 3, 1, 1] |
字符串去重
数组可以通过Set特性去重,而且数组还有join方法可以将数组转换成字符串,这样我们可以联想到字符串去重的方法。
1 | const str = 'abcabcaaa' |
Set常用方法
通过new Set()
初始化Set对象
1 | const set = new Set() |
调用add(value)
方法向 set 集合 中添加指定元素
1 | set.add(1) |
调用has(value)
方法检查集合中是否存在指定元素
1 | set.has(2) |
调用delete(value)
方法删除集合中的指定元素
1 | set.delete(1) |
调用clear()
方法清空集合内所有元素
1 | set.clear() |
set对象中的size
属性可以返回集合中元素的数量:
1 | set.add(1) |
Set的遍历方法
- 调用
keys()
方法返回一个新的迭代器对象,该对象包含Set对象的每个元素的键。 - 调用
values()
方法返回一个新的迭代器对象,该对象包含Set对象的每个元素的值。 - 调用
entries()
方法返回一个新的迭代器对象,该对象包含Set对象的每个元素(值和键)。
1 | const set = new Set([1, 2, 3, 1, 2, 3, 4]) |
遍历Set的方法
forEach
方法
和数组一样,Set也有forEach方法。
1 | const set = new Set([1, 2, 3, 1, 2, 3, 4]) |
for...of
循环
1 | const set = new Set([1, 2, 3, 1, 2, 3, 4]) |
Set的优缺点
- 优点:
- Set中的元素都是唯一的,不会重复的值。
- 因为Set中的值是唯一的,所有查找、删除和添加的操作执行得更快。
- 缺点:
- 在ES6中,Set保持了插入顺序,但是存在兼容问题。
- 与数组不同,Set不能通过索引来访问Set中的元素。
Map
在普通对象中,对象的键名的类型都是字符串,如果不是字符串类型也会被转换为字符串类型。
1 | let n = 123 |
这里我们注意到了,二维数组也被转为了空字符串。
然而随着需求的不断增加,出现了对象中的键名需要是其他类型的需求,因此JavaScript提供了Map这一数据结构。
Map这一数据结构允许使用任何值作为键。
注意:不要将数组的map方法和Map数据结构记混。
Map常用方法
通过new Map()
初始化Map对象。
1 | const map= new Map() |
调用set(key,value)
方法向Map中添加一个键值对。
注意:不要和上面的Set数据结构搞混喽。
1 | map.set('name','张三') |
调用get(key)
方法根据提供的键名返回对应的值,如果不存在该键名则返回undefined。
1 | map.get('name') |
调用has(key)
方法可以检查是否包含指定的键。
1 | console.log(map.has('name')) |
如果包含就返回true,如果不包含就返回false。
调用delete(key)
方法可以删除指定的键值对。
1 | map.delete('name') |
调用clear()
方法可以删除所有键值对。
1 | map.clear() |
Map的属性size
可以返回键值对数量。(注意size不是方法,而是属性)
1 | map.size |
Map的遍历方法
与Set的遍历方法相似。
keys()
:返回一个新的迭代器对象,其中包含Map的所有键名。values()
:返回一个新的迭代器对象,其中包含Map的所有键值。entries()
:返回一个新的迭代器对象,其中包含Map的所有键值对。
1 | const map = new Map(); |
遍历Map的方法
forEach
方法
1 | const map = new Map() |
for...of
循环
1 | const map = new Map() |
Map的优缺点
- 优点:
- Map可以存储任何类型的键和值
- Map的键也是唯一的,不存在重复的键
- Map内的顺序和插入顺序一致
- 缺点:
- Map不能和数组一样通过索引直接访问
- 存在兼容IE的问题
Map的应用场景
Map
因为是一组键值对的结构,具有极快的查找速度。
举个例子,假设要根据同学的名字查找对应的成绩,如果用Array
实现,需要两个Array
1 | var names = ['Michael', 'Bob', 'Tracy']; |
给定一个名字,要查找对应的成绩,就先要在names中找到对应的位置,再从scores取出对应的成绩,Array越长,耗时越长。
这里我们用Map实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。
1 | var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]); |
初始化Map
需要一个二维数组,或者直接初始化一个空Map
,Map
具有以下方法
1 | var m = new Map(); // 空Map |
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉
1 | var m = new Map(); |
弱引用和强引用
WeakSet和WeakMap都是弱引用。
首先了解一下什么是弱引用。
弱引用是不能确保其引用的对象不会被垃圾回收器回收的引用,而强引用是确保其引用的对象不会被垃圾回收器回收的引用。
也就是说,JavaScript引擎在执行代码时,对象通过变量直接赋值形成的引用会被视为强引用,垃圾回收器就不会回收这类对象;
通过WeakMap
和WeakSet
建立的引用会被视为弱引用,这类引用无法阻止垃圾回收器的回收。
当一个变量被设置为null
时,会断开该变量与原对象间的引用,该对象就会变成垃圾回收器的回收目标。
强引用
1 | let person = { name: "张三" }; |
创建一个叫person
的对象,并将该对象存储到person1
中;
然后将person
设置为null
,断开引用,但是因为变量person1
存在对person
对象的强引用,所以该对象不会被垃圾回收器给盯上。
弱引用
1 | let person1 = new WeakMap(); |
创建一个weakMap
对象person1
和一个对象person
,并且将该对象作为键,键值为"张三"
添加到person1
中;
然后将变量person
设置为null
,断开了变量与对象的引用,然而person1
对person
是弱引用,所以垃圾回收器可以回收person
对象。
WeakSet和WeakMap
WeakSet
WeakSet和Set非常相似,但是有一些不同之处:
- 成员类型:WeakSet的成员只能是Symbol值和对象,不能是其他的数据类型;Set的成员可以是任意数据类型。
- 引用类型:WeakSet是弱引用;Set是强引用。
- 方法和属性:WeakSet不支持迭代,所以没有
forEach
,values
,keys
,entries
方法,并且也没有size
属性;Set有forEach
,values
,keys
,entries
方法,也有size
属性。 - 作用:WeakSet可以实现自动清理回收;Set可以通过元素的唯一性用于实现去重工作。
WeakMap
WeakMap和Map也非常相似,但是也是有一些不同:
- 键的类型:WeakMap的键类型只能是对象和Symbol值;Map的键类型可以是任意数据类型。
- 引用类型:WeakMap是弱引用;Map是强引用。
- 方法和属性:WeakMap不支持迭代,所以没有
forEach
,values
,keys
,entries
方法,并且也没有size
属性;Map有forEach
,values
,keys
,entries
方法,也有size
属性。 - 作用:WeakMap也可以实现自动清理回收;Map提供需要高效键值对的操作。
结语
set和Map是常用的数据去重的类型,我个人平时没怎么用过去重的方法,所以接触的不深。
今天了解了一下,依然感觉没什么太广阔的用途,作为知识点了解下吧。