关于map和set

一、ES6 中新增 Set、Map 数据结构的介绍

Set => 集合 ,Map => 字典
集合:无序、相关联、且不重复的元素组成的组合,类似数组
字典:每个元素都有一个称作 key 和 value
区分:
共同点:都可以存储不重复的值
不同点:集合是[值,值]、字典是[键,值]的形式存储

一、Set
数据结构:结构上类似数组,值唯一,对象,Set 本身是一个构造函数
使用场景:常用来去重,且运行速度很快
// 初始化 Set, 传入 Array 数组,或者空
const set1 = new Set([1, 2, 3, 4, 5]) // Set(5) {1, 2, 3, 4, 5}
const set2 = new Set()
常用 API

add(): 添加某个值,返回 Set 结构本身,当添加实例中已经存在的元素时,set 不会进行添加

const set = new Set()
set.add(1).add(2).add(2) // 2 只会被添加一次

delete(): 删除某个值,返回一个布尔值,表示删除成功

const set = new Set()
set.add(1).add(2).add(2)
set.delete(1) // true

has(): 返回一个布尔值,判断该值是否为 Set 的成员

const set = new Set()
set.add(1).add(2).add(2)
set.delete(1) // true
set.has(1) // false
set.has(2) // true

clear(): 清除所有成员,没有返回值

const set = new Set()
set.add(1).add(2).add(2)
set.clear()
遍历

keys(键名), values(键值), entries(键值对), forEach(回调函数遍历)
Set 遍历顺序是插入排序

let set = new Set(['red', 'yellow', 'blue'])
set.keys() // 返回值都是Set迭代器对象,SetIterator {'red', 'yellow', 'blue'}
set.values() // 返回值都是Set迭代器对象,SetIterator {'red', 'yellow', 'blue'}
set.entries() // 返回值都是Set迭代器对象,SetIterator {'red' => 'red', 'yellow' => 'yellow', 'blue' => 'blue'}
for (let item of set.keys()) {
  console.log(item)
}
for (let item of set.values()) {
  console.log(item)
}
for (let item of set.entries()) {
  console.log(item)
  //['red', 'red']
  //['yellow', 'yellow']
  //['blue', 'blue']
}
set.forEach((value, key, set) => console.log(key + ':' + value))
//red:red
//yellow:yellow
//blue:blue

forEach 有第 2 个参数,用于绑定处理函数的 this
扩展运算符和 Set 结构相结合可实现数组或字符串的去重

// 数组
let arr = [3, 5, 2, 2, 5, 5]
let unique = [...new Set(arr)] // [3, 5, 2]

// 字符串
let str = '352255'
let unique = [...new Set(str)].join('') // "352"

并集、交集和差集

let a = new Set([1, 2, 3])
let b = new Set([4, 3, 2])

// 并集
let union = new Set([...a, ...b])
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a]).filter((x) => b.has(x))
// set {2, 3}

// (a 相对于 b 的)差集
let difference = new Set([...a].filter((x) => !b.has(x)))
// Set {1}
二、Map

Map 是键值对的有序列表,键和值可以是任意值。
Map 结构的实例有以下属性和操作方法: size 属性,set(),get(),has(),delete(),clear()
初始化 Map 需要传入一个 二维数组或空

const map1 = new Map([
  ['user', 'aaa'],
  ['num', 999],
]) // Map(2) {'user' => 'aaa', 'num' => 999}
const map2 = new Map()
map2.set('foo', true)
map2.set('bar', false) // Map(2) {'foo' => true, 'bar' => false}
map2.size // 2
const map = new Map()
map.set('edition', 6) // 键是字符串
map.set(222, 'standard') // 键是数字
map.set(undefined, 'nah') // 键是 undefined
map.set(1, 'a').set(2, 'b').set(3, 'c') // 链式操作

get() 方法读取 key 对应的键值,如果找不到 key,返回 undefined
has() 方法返回一个布尔值,表示某个键是否在当前的 Map 对象中
delete() 删除某个键,返回 true, 如果删除失败,返回 false

const map = new Map()
map.set(undefined, 'nanana')
map.has(undefined)
map.delete(undefined) // true
map.delete(undefined) // false

clear() 清除所有成员信息,没有返回值

let map = new Map()
map.set('foo', true)
map.set('bar', false)

map.size // 2
map.clear()
map.size // 0

遍历 keys(), values(), entries(), forEach()

const map = new Map([
  ['n', 'no'],
  ['y', 'yes'],
])
for (let key of map.keys()) {
  console.log(key)
}
for (let value of map.values()) {
  console.log(value)
}
for (let item of map.entries()) {
  console.log(item[0], item[1])
}
for (let [key, value] of map.entries()) {
  console.log(key, value)
}
// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value)
}
map.forEach((value, key, map) => {
  console.log('Key: %s, Value: %s', key, value)
})
三、WeakSet 和 WeakMap

WeakSet 可以接收一个具有 Iterable 接口的对象作为参数, 与 Set 区别:1、没有遍历操作的 API,2、没有 size 属性

const a = [
  [1, 2],
  [3, 4],
]
const ws = new WeakSet(a)
// WeakSet {[1, 2], [3, 4]}

WeakSet 的成员只能是引用类型,而不能是其他类型

const a = 2
// 成员不是引用类型
const ws = new WeakSet([a])
console.log(ws) // 报错
// 成员为引用类型
const b = { name: 1 },
  c = { name: 2 }
const wss = new WeakSet([b, c])
// WeakSet {{name:1}, {name: 2}}

WeakMap 与 Map 区别: 没有遍历操作;没有 clear() 清空

// WeakMap 可以使用 set 方法添加成员
const wm1 = new WeakMap()
const key = { foo: 1 }
wm1.set(key, 2) // WeakMap {{foo: 1} => 2}
wm1.get(key) // 2
// WeakMap 也可以接收一个数组,作为构造函数的参数
const k1 = [1, 2, 3],
  k2 = [4, 5, 6]
const wm2 = new WeakMap([
  [k1, 'foo'],
  [k2, 'bar'],
])
wm2.get(k2) // 'bar'
```

WeakMap 只能接受对象(null 除外)作为键名,不接受其他类型的值作为键名

```js
const map = new WeakMap()
map.set(1, 2) // TypeError: 1 is not an object!
map.set(Symbol(), 2) // TypeError: Invalid value used as weak map key
map.set(null, 2) // TypeError: Invalid value used as weak map key
```

WeakMap 键名所指向的对象,一旦不需要,里面的键名对象和所对应的键值会自动消失,不用手动删除引用

```js
const wm = new WeakMap()
const element = document.getElementById('example')
wm.set(element, 'dodododo')
wm.get(element) // 'dodododo'
```

注意:WeakMap 弱引用的只是键名,键值依然是正常引用

```js
const wm = new WeakMap()
let key = {},
  obj = { foo: 1 }
wm.set(key, obj)
obj = null
wm.get(key) // Object {foo: 1}
key.name = 'aaaa'
console.log(wm) // WeakMap {{name: 'aaaa'} => {foo: 1}}
四、Map 和 Set 对比

(1) 都有极快的查找速度

let num = 1000
let arr = new Array(1000).fill(2)
let set = new Set(arr)
let map = new Map()
for (let i = 0; i < num; i++) {
  arr[i] = i
  map.set(i, arr[i])
}
// Array
console.time()
for (let j = 0; j < num; j++) {
  arr.includes(j)
}
console.timeEnd() // default: 0.231201171875 ms
// Set
console.time()
for (let j = 0; j < num; j++) {
  set.has(j)
}
console.timeEnd() // default: 0.091064453125 ms
// Map
console.time()
for (let j = 0; j < num; j++) {
  map.has(j)
}
console.timeEnd() // default: 0.113037109375 ms

从上面的结果可以看出 Set 的执行速度最快,Map 其次, Array 最慢,其实 Set 和 Map 相差不大
(2) 初始化:Map 需要二维数组,Set 需要一维数组
(3) 都不允许键重复
(4) Map 的键不能修改,键对应的值可以修改;Set 不能通过迭代器来改变 Set 的值(键值相同)
(5) Map 是键值对,键值分开的;Set 没有 value, 只有 key,value 就是 key

五、Map 和 Object 对比

(1)、概念: Object 是顶级对象,也是构造函数,通过 new Object() 创建对象,javascript 中的所有对象都是 Object 的一个实例,字面量的方式可以通过 const obj = {} 进行声明,键只能是字符串,提供了“字符串-值”的对应;
Map 则是键值对的组合,是 Hash 结构,键不限于字符串,也可以是对象,提供“值-值”的对应,通过 const map = new Map() 创建 Map 对象;
(2)、访问:map.get(key),不存在返回 undefined; obj[‘a’] 或 obj.a 去访问,不存在返回 undefined;
(3)、赋值:map.set(key,value), key 可以是任意值; obj[‘a’] === 1 或 obj.a = 1, key 只能是字符串、数字或 symbol;
(4)、删除:map.delete(key), 删除成功返回 true,删除不存在的值返回 false; delete obj.a,及时删除不存在的属性,删除返回值也只是 true,但通过 Reflect.deleteProperty(target,prop) 删除不存在的属性还是返回 true
(5)、大小:map.size 属性;object: Object.keys(obj).length 转换为数组才能获得长度,或者通过 Reflect.ownKeys(obj) 获得 keys 的集合;
(6)、遍历:map 拥有迭代器,可通过 for-of, forEach 直接遍历,且顺序是确定的; object 没有迭代器,需要自行实现,或通过 for-in 循环去遍历,遍历顺序是不确定的;
(7)、使用场景:①、只是简单地存储 key-value,且 key 不需要存储引用类型,直接使用对象即可;②、需要进行 JSON 转化,需使用对象,Map 暂时不支持;③、map 的阅读性更好,有更多的 api 调用,编程体验更佳;