在很长时间一段时间里,对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...of
或 for...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 ]
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总结
-
Set
最大的特点就是它的元素是唯一的,基于这个特性可以用于去重 -
Set
可以存储任意类型的值,但是初始化的时候只能传入可迭代对象 -
Set
可以与Array
进行互相转换,也可以把String
转换为Set
类型 -
扩展运算符和
Set
结构相结合实现数组或字符串去重
Map
在ES6以前,在js中实现 键/值式 存储可以使用 Object
来高效完成,ES6新增了一种新的集合类型 Map
它最大的特点就是:Map
中键的范围不限于字符串类型,各种类型的值(包括对象)都可以当做是一个键或一个值
所以Object
和 Map
键值类型:
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
只能接收两种类型的键名:String
和Symbol
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之后就会可以按顺序保存了,只是通过隐式转换为字符串的键就乱序了
总结
Map
的键可以是任意类型,并且可以用forEach
等迭代Map
的键值对是根据set
设置的顺序存储的Map
获取长度就是用size
属性直接返回,时间复杂度为O(1)
Map
缺点就是不能使用[]
和点
来设置和获取键值,只能用set
和get
来替换- 固定大小内存,
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
weakMap
是 Map
的变体,它们的方法基本是一样的,区别在于内部分配的工作方式
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
属性