彻底搞明白JS的Map与Set

在很长时间一段时间里,对Map与Set一直保持在"懂,但不多"的状态,工作中使用场景又不多,用到也只是copy出来改改,这次决定将其彻底搞明白,总结出这篇博客,望共同进步。

定义

Set是一种叫做集合的数据结构(结构和Array很像),Set中的元素是唯一的,即没有重复的(这点很重要)
Map是一种叫做字典的数据结构,和Object一样保存键值对,但Map中键的范围不限于字符串类型,各种类型的值(包括对象)都可以当做是一个键或一个值

Set

Set 本身是一个构造函数,用来生成 Set 数据结构

js 复制代码
 let set = new Set();
 console.log(set);  // {}

Set 对象可以存储任意类型的值,但是用Set 构造函数传入的参数只能是可迭代对象,如:数组、字符串

js 复制代码
 // 当你传入一个整数时,js会告诉你传入的参数1不是可迭代对象
 let set = new Set(1);
 console.log(set); // number 1 is not iterable
 
  // 传入对象会报错
 let set = new Set({});
 console.log(set); // object is not iterable
 
 // 必须是数组或字符串,
 let set1 = new Set('1');
 console.log(set1);
 let set2 = new Set([1, 'true']);
 console.log(set2);
 

常见的方法

方法 说明 示例
has() 判断是否有该值,返回布尔值 set.has('name')
add() 向Set()添加元素,若有相同会覆盖 set.add('name', 'zxc')
delete 用于删除某个元素,成功则返回true,失败返回false set.deldect('name')
clear() 用于清空 Set() 中所有的成员,没有返回值 set.clear()
size 属性 判断 Set() 有多少个元素 set.size
js 复制代码
 let set = new Set()
 let arr = [1, 2]
 set.add(1).add(arr)
 set.add(function() { console.log(123); })
 set.add({})
 console.log(set); // { 1, [ 1, 2 ], [Function (anonymous)], {} }
 
 // has() 判断元素
 console.log(set.has(arr));  // true
 
 // size 判断长度
 console.log(set.size);  // 4
 
 // delete 删除
 console.log(set.delete(1)); // true
 console.log(set.delete(arr)); // true,set中存储的对象,只能删除这种有明确地址索引的,不然只能用clear()
 
 // clear 清除
 set.clear()
 console.log(set);  // {}

顺序与迭代

Set() 结构有四个遍历的方法

方法 说明 示例
keys() 返回键的遍历器 set.keys()
values() 返回值的遍历器 set.values()
entries() 返回所有成员的遍历器,包含键,值 set.entries()
forEach 遍历Set的所有成员 set.forEach(function(), [this])

由于Set结构没有键名,所以其键名和键值是一致的

js 复制代码
 let set = new Set(['张三', '李四', '王五', 'true']);
 // 遍历键 
 console.log(set.keys());  // { '张三', '李四', '王五', 'true' }
 // 遍历值
 console.log(set.values());  // { '张三', '李四', '王五', 'true' }
 // 返回键值
 console.log(set.entries());
 /* {
   [ '张三', '张三' ],
   [ '李四', '李四' ],
   [ '王五', '王五' ],
   [ 'true', 'true' ]
 }*/
 ​
 // forEach() 循环
 let set = new Set(['张三', '李四', '王五', 'true'])
 set.forEach(function(value, key) {
     console.log(key + ':' + value);
 })
 /*
 张三:张三
 李四:李四
 王五:王五
 true:tru*/

也可以使用 for...offor...in

js 复制代码
 // 遍历值
 for (let value of set) { console.log(value);}
 // 遍历键
 for (let key in set) {console.log(key);}

类型转换

Set 类型可以与其它类型的值进行转换

Array 转换为 Set

js 复制代码
 let arr = [1, 2, 3, 4]
 let set = new Set(arr)
 console.log(set);  // { 1, 2, 3, 4 }

Set 转换为 Array:可以通过扩展运算符 ... 来实现

js 复制代码
 let set = new Set([1, 2, 3, 4])
 console.log([...set]);  // [ 1, 2, 3, 4 ]
  1. String 转换为 Set
js 复制代码
 let str = '张三'
 let set = new Set(str)
 console.log(set); // { '张', '三' }

Set 的作用

去重

利用Set不重复性可以用于数组的去重

js 复制代码
 // 将数组转换为Set,再将Set转换为数组
 let arr = [1, 1, 4, 1, 2, 3, 1, 2]
 let set = new Set(arr)
 console.log([...set]); // [ 1, 4, 2, 3 ]

并集

js 复制代码
 let arr1 = [1, 1, 4, '1', 2, 3]
 let arr2 = [5, 1, '2']
 let set = new Set([...arr1, ...arr2])
 console.log([...set]); // [ 1, 4, '1', 2, 3, 5, '2' ]

PS:Set 不允许强制转换,1 和 '1' 被认为是不同的值

Set总结

  1. Set 最大的特点就是它的元素是唯一的,基于这个特性可以用于去重

  2. Set 可以存储任意类型的值,但是初始化的时候只能传入可迭代对象

  3. Set 可以与Array 进行互相转换,也可以把 String 转换为 Set类型

  4. 扩展运算符和 Set 结构相结合实现数组或字符串去重


Map

在ES6以前,在js中实现 键/值式 存储可以使用 Object 来高效完成,ES6新增了一种新的集合类型 Map

它最大的特点就是:Map中键的范围不限于字符串类型,各种类型的值(包括对象)都可以当做是一个键或一个值

所以ObjectMap 键值类型:

  • Object字符串/Symbol类型: 任意类型
  • Map任意类型: 任意类型

初始化Map

创建一个空map

js 复制代码
 // 必须是以这种方式创建
 let map= new Map();

Map构造函数传入一个可迭代对象,需要包含键值对,每个键值对会按照迭代顺序映射到实例当中

js 复制代码
 const map = new Map([
     ['name', 'cyf'],
     ['age', 18]
 ]);
 console.log(map);  // {"name" => "zxc", "age" => 18}

Map常用方法

方法 说明 示例
get() 通过键来获取值,如果没有该键,则返回 undefined map.get('name')
has() 判断是否有该键,返回布尔值 map.get('name')
set() 用于设置对应的键值对,若有相同会覆盖 map.set('name', 'zxc')
delete() 用于删除某个键,成功则返回true,失败返回false map.deldect('name')
clear() 用于清空 Map() 中所有的成员,没有返回值 map.clear()
js 复制代码
 let map = new Map([
     ['namer', '张三'],
     ['age', 2]
 ]);
 ​
 // 获取namer
 console.log(map.get('namer')); // 张三
 ​
 // 判断namer
 console.log(map.has('age')); // 2
 ​
 // 设置键值对
 console.log(map.set('sex', '其它')); // { 'namer' => '张三', 'age' => 2, 'sex' => '其它' }
// 可连续设置
 console.log(map.set('id', 0).set('hobby', '讲段子'));
 ​
 // size属性,获取map长度
 console.log(map.size); // 5
 ​
 // 删除某个键
 console.log(map.delete('id')); // true
 ​
 // 清空map
 map.clear()
 console.log(map); // {}

一个key只能对应一个value,多次对一个key放入value,之前的值会被覆盖

js 复制代码
let map =new Map()
map.set('Amy',"女")
map.set('Amy',"男")
console.log(map) 

顺序与迭代

Object 类型的一个差异是:Map 实例会维护键值对的插入顺序,因此可以根据顺序执行迭代操作

Map 提供了3个遍历器和一个遍历方法

方法 说明 示例
keys() 返回键的遍历器 map.keys()
values() 返回值的遍历器 map.values()
entries() 返回所有成员的遍历器,包含键,值 map.entries()
forEach 遍历Map的所有成员 map.forEach(function(), [this])
js 复制代码
 let map = new Map().set('namer', '张三').set('age', 2).set('sex', '其它')
 ​
 // 获取键
 let keys = map.keys()
 console.log(keys);
 ​
 // 获取值
 let values = map.values()
 console.log(values);
 ​
 // 获取键值对
 console.log(map.entries());  // { [ 'namer', '张三' ], [ 'age', 2 ], [ 'sex', '其它' ] }
 ​
 for (let [key, value] of map) {
     console.log(key + ':' + value);
 }
 // namer:张三
 // age:2
 // sex:其它
 ​
 // forEach 循环
 map.forEach(function(value, index) {
     console.log(index + ':' + value);
 })
 ​
 // namer:张三
 // age:2
 // sex:其它

Map与Object的区别

1、键名类型

  • Object 只能接收两种类型的键名:StringSymbol
  • Map 能够接受任意类型的键名

Map 键名:

js 复制代码
 let map = new Map();
 map.set(1, 'Number').set(true, 'Boolean').set({ '键名': '键值' }, 'Object').set(function() {}, 'Function')
 console.log(map);  
 // { 1 => 'Number', true => 'Boolean', { '键名': '键值' } => 'Object', [Function (anonymous)] => 'Function'}

Object键名

js 复制代码
 let obj = {}
 obj[1] = 'Number';
 obj[true] = 'Boolean';
 obj[{ '键名': '键值' }] = 'Object';
 obj[function() {}] = 'Function'
 ​
 console.log(obj);
 // { '1': 'Number', true: 'Boolean', '[object Object]': 'Object', 'function() {}': 'Function' }

虽然说Object 可以接受其他类型的键名,当时js都会隐式地将其转换为字符串

迭代

  • Map 是可以迭代的,用 forEach循环或 for...of
  • Object 是不能直接进行迭代的

Object 遍历需要借助对象的静态方法

js 复制代码
 let obj = { 'namer': '张三', 'age': 2, 'sex': '其它' }
 ​
 // 遍历键
 for (let key of Object.keys(obj)) {
     console.log(key);
 }

还可以是:

js 复制代码
 for (let value of Object.values(obj)) {}
 ​
 for (let keyValue of Object.entries(obj)) {}
 ​
 for (let [key, value] of Object.entries(obj)) {}

当然可以使用 for...in 遍历键

js 复制代码
 for (let key in obj) {
     console.log(key); // namer age sex
 }

顺序和长度

长度

  • Map保存对长度的跟踪,可直接使用size,其时间事件复杂度为 O(1)
  • 对于 Object而言,想要获取对象长度需要对于其进行迭代,其时间复杂度为 O(n)

顺序

  • Map 始终是保持键值对插入时的顺序

  • Object则不是,不过ES6之后就会可以按顺序保存了,只是通过隐式转换为字符串的键就乱序了

总结

  1. Map的键可以是任意类型,并且可以用 forEach 等迭代
  2. Map 的键值对是根据set设置的顺序存储的
  3. Map 获取长度就是用size属性直接返回,时间复杂度为 O(1)
  4. Map 缺点就是不能使用 [] 来设置和获取键值,只能用setget 来替换
  5. 固定大小内存,Map 大约可以比Object 多存储50%的键值对

WeakSet 和 WeakMap

WeakSet

WeakSet 是一种 "弱集合"类型,其集合中的值只能是对象

由于Set结构没有键名,所以其键名和键值是一致的

js 复制代码
 let set = new Set(['张三', '李四', '王五', 'true'])
方法 说明
has() 判断是否有该值,返回布尔值
add() 向Set()添加元素,若有相同会覆盖
delete() 用于删除某个元素,成功则返回 true,失败返回 false

1、创建一个空的WeakSet

js 复制代码
 let WeakSet = new WeakSet();

2、初始化WeakSet

构造函数可以传入一个迭代对象,可迭代对象中的值必须得是对象

js 复制代码
 let x = { id: 1 },
     y = { id: 2 }
 let weakSet = new WeakSet([x, y]);
 console.log(weakSet);
 ​
 console.log(weakSet.has(x)); // true
 console.log(weakSet.delete(x)); // true 

WeakSet 中没有clear方法 和 size属性

因为 WeakSet 中的值任何时候都可能被销毁,所以没必要提供迭代的功能,也用不着先clear这个清空的功能

另外,因为 WeakSet 的成员可以被垃圾回收机制回收,所以可以用来保存DOM节点,不容易造成内存泄露

WeakMap

weakMapMap的变体,它们的方法基本是一样的,区别在于内部分配的工作方式

weakMap 只接受引用类型的数据作为键名,如:数组、函数、对象等

1、创建一个空的WeakMap

js 复制代码
 let WeakMap = new WeakMap();

2、初始化WeakMap

js 复制代码
 let weakMap = new WeakMap();
 let x = { id: 1 },
     y = { id: 2 }
 weakMap.set(x, '张三')
 weakMap.set(y, 2)
 
 console.log(weakMap.get(x)); // 张三
 console.log(weakMap.get(y)); // 2
 
 console.log(weakMap.has(x)); // true
 console.log(weakMap.delete(x));  // true

WeakMap 中没有clear方法 和 size属性

相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
uhakadotcom8 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪8 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪8 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom10 小时前
React与Next.js:基础知识及应用场景
前端·面试·github