面试管:小卡拉米,来个最基本的,说一下Set和Map的理解
作者:LiuMingXin
链接:juejin.cn/post/7343616267705221130
听到这个题之后的内心独白:
卧槽,我都是背的高达上,为啥来个基本的,没准备呀,这让我怎么吹牛逼,我特码的面的可是高级呀,不应该问我:项目调优、首屏优化、打包优化等问题吗?这个面试管不安套路出牌呀,完了,兜不住了,大爷的,嘎了,凉了,gg了,我高大上的八股文白背了,哎,只能江湖下次在见了!
顺便吆喝一句,技术大厂机会,前后端测试捞人。
Set和Map
概念说明
我们想一个问题,ES6中为啥要新增Set和Map这两种数据结构呢?为啥类似于Java中都有Set和Map这两种数据结构,而且还有其变形。官方回答(八股文答案,就是没毛病,感觉说了跟没说一样,呵呵):
总的来说,ES6中新增的Set和Map数据结构,主要是为了提供更强大和灵活的数据操作能力,以满足更复杂的编程需求。
什么是Set
Set我们可以叫做一种集合得数据结构
什么是Map
Map我们可以叫做一种字典得数据结构
那什么是集合?什么又是字典?
集合:是由一堆无序的、相关联的,而且不能重复的内存结构组成的组合字典:是一些元素的集合。每个元素有一个称作key 的域,不同元素的key 各不相同他们的共同点和不同点:
共同点:集合、字典都可以存储不重复的值不同点:集合是以[值,值]的形式存储元素,字典是以[键,值]的形式存储
使用示例 set 初始化
使用 new Set() 创建一个空 Set使用 new Set(iterable) 创建一个包含 iterable 中所有值的 Set
着重注意一下第二个new Set(iterable)的这种形式,iterable是指可迭代的。
// 初始化一个set数据结构 const set1 = new Set(); // 创建一个空set const set2 = new Set([1,2,3]); // 创建一个包含三个值的set const set3 = new Set('abc'); // 创建一个包含三个字符的 Set const set4 = new Set(new Map([['a', 1], ['b', 2]])); // 创建一个包含两个键值对的 Set
Set的用法
只要谈起数据结构,最基本的操作就是增删改查,Set相关常用的API如下:
add: 添加某个值(别乱想,添加只有各个方法,可别乱联想数组的push,那你可能数组用多了)delete: 删除某个值has:返回一个布尔值,表明是否含有这个值clear:清空所有的成员
上面增删改查的API没啥好说的。Set的大小或者说成员的个数是size属性,你可别来个length,length是数组的长度。数据存在完了,我们要干啥呢?自然就是遍历数据:Set提供的遍历方法
keys: 返回键名的遍历器values: 返回键值的遍历器entries:返回键值对的遍历器forEach:使用回调函数遍历每个成员 (别大惊小怪,用forEach遍历数组,怎么人家Set就不能有)for...of: 迭代Set中所有的成员
稍微发泄一下:
不行,这个得上示例,八股文背诵得多了,有点串了,不上示例,印象不深刻呀,干就完了,大爷的。特娘的,现在这个吊环境,你不上点花活,面试管都记不住你,关键是我特妈的基本的API都背串了,还上花活,很是苦逼。我现在用AIGC编程,可惜面试的时候不让我用呀,哈哈。操,想转行,一看别的行业也是遍地尸体,回头一想,要是转行了那我前期的积累(没啥价值,还不舍得扔的 积累)不就都废了,妈的,算了,继续跟你们卷,我要一天背10个八股文,卷死你们,哈哈。遇到点挫折,就特妈的大环境不好,怎么你到哪,那就大环境不好了,哈哈,要想PUA别人就得先PUA自己,我要把我自己PUA死,要不怎么撑下去和你们卷。他妈的,总之,干就完了。
// 初始化一个set数据结构 const set1 = new Set(); // 创建一个空set const set2 = new Set([1,2,3]); // 创建一个包含三个值的set const set3 = new Set('abc'); // 创建一个包含三个字符的 Set const set4 = new Set(new Map([['a', 1], ['b', 2]])); // 创建一个包含两个键值对的 Set // 一般来说比较常用的是values, entries,forEach, for...of方法 // forEach set2.forEach(value => { console.log(value) }); // for...of for (const value of set2) { console.log(value) } // values: 注意返回值是个遍历器,要遍历期中的数据才可以用 const iterator1 = set3.values(); for (const value of iterator1) { console.log(value) } // entries: 返回的也是迭代器,只是迭代器每一项的值是数组[index, value] const iterator2 = set2.entries(); for (const entry of iterator2) {console.log(entry) }
Map初始化
Map类型是键值对的有序列表,而键和值都可以是任意类型(包括原始值和对象),Map本身是一个构造函数,用来生成Map数据结构(我就问一下子,我八股文背的怎么样,服不服)。
Map的特性:
键值对: Map 中的每个元素都是一个键值对,键可以是任何类型,值可以是任何类型。
无序:Map 中的元素是无序的,插入顺序不会影响迭代顺序。唯一键:Map 中的键是唯一的,不允许重复。
高效:Map 在查找、插入和删除操作方面都非常高效。
创建Map:
可以使用 new Map() 创建一个空 Map,也可以使用 new Map(iterable) 创建一个包含 iterable 中所有键值对的 Map。
const map1 = new Map(); // 创建一个空 Map const map2 = new Map([['a', 1], ['b', 2]]); // 创建一个包含两个键值对的 Map // 这个有点意思,注意看,还不抓紧复制到控制台看看,等啥呢,大兄弟 const map3 = new Map(new Set([['a', 1], ['b', 2]])); // 创建一个包含两个键值对的 Map // 哈哈,其实map2和map3创建是一样的,看我花活玩的怎么样。
Map的用法
继续轮我三板斧,来,上增删改查,不行,说增删改查有点low,这把换个洋气点的,CRUD,怎么样?是不是跟国际接轨了。
添加元素:大哥他不可是add哈,add可是人家Set的,它的是set,对的,使用 map.set(key, value) 添加一个键值对到 Map 中删除元素:使用 map.delete(key) 删除一个键值对从 Map 中。检查元素是否存在:使用 map.has(key) 检查 Map 中是否包含某个键。获取元素:使用 map.get(key) 获取某个键对应的值。大哥,map有这个get方法,Set可没有哈,记清楚了。
map的长度跟Set一样,也是size属性,表示元素集合的个数。数据存储完了,是不是要上遍历了,搞起来:
- 使用 forEach 迭代 Map 中的所有键值对
- 使用 for...of 迭代 Map 中的所有键值对
- 使用 keys 方法获取 Map 中所有键的迭代器
- 使用 values 方法获取 Map 中所有值的迭代器
- 使用 entries 方法获取 Map 中所有键值对的迭代器
const map1 = new Map(); // 创建一个空 Map const map2 = new Map([['a', 1], ['b', 2]]); // 创建一个包含两个键值对的 Map const map3 = new Map(new Set([['a', 1], ['b', 2]])); // 创建一个包含两个键值对的 Map // forEach,遍历键值对哈!三遍:键值对 map1.forEach((value, key) => { console.log(key, value)} ); // 同上 for (const [key, value] of map2) {console.log(key, value)} // 所有的键 const keys = map3.keys(); for (const key of keys) {console.log(key) } // 所有的值 const values = map3.values(); for (const value of values) { console.log(value) } // 键值对 const entries = map3.entries(); for (const [key, value] of entries) { console.log(key, value) }
不行拿到控制台多输几遍,没啥丢人的,不满你说,我打了七八遍,哈哈,是不是很有天赋,仅仅七八遍就记住了一半,小卡拉米们,你们已经输在起跑线了,输在天赋上了,哈哈!
Set常见用法去重
Set数据结构中不允许重复的元素,利用这个特性可以很简洁的实现数组去重
[...new Set([1,2,3,3,1,2])]
不考虑性能的情况下,Set去重可以做到相当的简洁。
交集
可以使用Set计算两个数组的交集,写法也是相当的简洁。
const set1 = new Set([1, 2, 3]); const set2 = new Set([2, 3, 4]); // 交集 const intersection = new Set([...set1].filter(x => set2.has(x)));
并集
const set1 = new Set([1, 2, 3]); const set2 = new Set([2, 3, 4]); // 并集 const union = new Set([...set1, ...set2]);
差集
const set1 = new Set([1, 2, 3]); const set2 = new Set([2, 3, 4]); // 差集 const difference = new Set([...set1].filter(x => !set2.has(x)));
相等判断两个set是否相等
const set1 = new Set([1, 2, 3]); const set2 = new Set([1, 2, 3]); // 方法一:比较两个 Set 的大小和元素 console.log(set1.size === set2.size && [...set1].every(x => set2.has(x))); // true // 方法二:使用 Set 的 `equals` 方法 console.log(set1.equals(set2)); // true
来放松一下:在惊叹Set和Map中,喊着一声声牛逼,在牛逼中逐渐的迷失自己,感觉自己又可以了,哈哈,这是错觉,小卡拉米!特妈的,干就完了!小卡拉米冲吧!
哈拉西大,娘希匹,特奶奶的上Map!
Map常见用法转换
怎么说呢,其实Map的转换是很常见的用法,一定要掌握的。
Map 转为数组:使用 [...map] 或 Array.from(map) 将 Map 转换为数组。数组转为 Map:使用 new Map(iterable) 将数组转换为 Map。Map 转为对象:使用 Object.fromEntries(map) 将 Map 转换为对象。对象转为 Map:使用 new Map(Object.entries(obj)) 将对象转换为 Map。
// map转换为数组 const arr = [...map1]; // [["a", 1]] // 数组转换map const map4 = new Map([['a', 1], ['b', 2]]); // Map转换对象(这个有点意思哈,记住了,有用的) const obj = Object.fromEntries(map4); // {a: 1, b: 2} // 对象转换map const map5 = new Map(Object.entries(obj)); // Map {a => 1, b => 2}
模拟字典可以使用 Map 来模拟字典,键可以是单词,值可以是单词的定义。
const dictionary = new Map([ ["apple", "一种水果"], ["banana", "一种水果"], ["cat", "一种动物"], ]); console.log(dictionary.get("apple")); // "一种水果"
缓存数据这个有点意思,可以使用 Map 来缓存数据,提高性能。
const cache = new Map(); function getData(key) { if (cache.has(key)) { return cache.get(key); } else { const data = fetch(key); cache.set(key, data); return data; } } const data = getData("https://www.example.com");
对象去重可以使用 Map 来对对象进行去重,例如只保留对象的 id 属性
const objects = [ { id: 1, name: "John" }, { id: 2, name: "Mary" }, { id: 1, name: "Alice" }, ]; const uniqueObjects = new Map(); for (const object of objects) { if (!uniqueObjects.has(object.id)) { uniqueObjects.set(object.id, object); } } console.log(uniqueObjects.values()); // [ { id: 1, name: "John" }, { id: 2, name: "Mary" } ]
统计元素次数可以使用 Map 来统计元素出现的次数。
const words = ["a", "b", "c", "a", "b"]; const wordCounts = new Map(); for (const word of words) { if (wordCounts.has(word)) { wordCounts.set(word, wordCounts.get(word) + 1); } else { wordCounts.set(word, 1); } } // 上面的写法可以玩花活的,你懂的,娘希匹,反正我就觉得这种写法最好,可读性好高。(翻译成人话就是:我太菜了,这个才刚看明白,还花活,绕了我吧) console.log(wordCounts); // Map { "a" => 2, "b" => 2, "c" => 1 }
实现 LRU 缓存
可以使用 Map 来实现 LRU 缓存,即最近最少使用缓存。
我擦,都别拦我,我感觉我又行了,我可以去搞算法了,啥动态规划、回溯、贪心等等,都不在话下,就是主打一个看不懂,等我去召唤法师,卷死你们,哈哈。
class LRUCache { constructor(capacity) { this.capacity = capacity; this.cache = new Map(); } get(key) { if (this.cache.has(key)) { const value = this.cache.get(key); this.cache.delete(key); this.cache.set(key, value); return value; } else { return undefined; } } set(key, value) { if (this.cache.size === this.capacity) { const oldestKey = this.cache.keys().next().value; this.cache.delete(oldestKey); } this.cache.set(key, value); } } const cache = new LRUCache(2); cache.set("a", 1); cache.set("b", 2); console.log(cache.get("a")); // 1 cache.set("c", 3); console.log(cache.get("b")); // undefined
扩展
来吧,都说了Set,那不得介绍一下WeakSet,要不怎么显得我花活多(人话是:哎,真他妈的学够了,他们天天更新,我特码的天天跟个菜鸟一样,学学学,时候是个头呀,毁灭吧!)
WeakSet 简介
WeakSet 是 JavaScript 中一种新的数据结构,它用来存储弱引用的集合。与传统的 Set 数据结构不同,WeakSet 中的对象不会阻止垃圾回收机制回收它们。
特性:
弱引用:WeakSet 中的对象是弱引用,这意味着垃圾回收机制可以回收它们,即使它们仍然被 WeakSet 引用。无序:WeakSet 中的元素是无序的,插入顺序不会影响迭代顺序。唯一键:WeakSet 中的键是唯一的,不允许重复。高效:WeakSet 在查找、插入和删除操作方面都非常高效。
WeakMap简介
但用于存储键值对。WeakMap 中的键是弱引用,这意味着垃圾回收机制可以回收它们,即使它们仍然被 WeakMap 引用。
特性:
弱键:WeakMap 中的键是弱引用,这意味着垃圾回收机制可以回收它们,即使它们仍然被 WeakMap 引用。
无序:WeakMap 中的元素是无序的,插入顺序不会影响迭代顺序。
唯一键:WeakMap 中的键是唯一的,不允许重复。
高效:WeakMap 在查找、插入和删除操作方面都非常高效。
等我后续补充WeakSet 和 WeakMap ,我这小卡拉米和你们后边继续交流学习...
总结
基础不过关,都是几把扯淡,牛逼吹的越狠,可能越兜不住。
面试管:下一个问题(小卡拉米,说这么多有啥用,我们只要个性价比高的,能做的了项目即可,你跟我扯这么多,扯这么多高达上的干啥,一句没说到点子上,净浪费时间,你不还是来面外包了,小样)。
面试管心理独白:这个小卡拉米还想隔着跟我装逼,坚决不能惯着他,等我搜个高级面试在来,看我不问死他,小样,打击不死他,娘希匹!
中间内心独白和旁白纯属编造,你的懂得。