第十一章 Set和Map数据结构
前面介绍完了7种数据类型,本章节阮一峰老师开始讲起了数据结构 Set
和 Map
。这两个数据结构在开发中十分常用,并且二者都有弱引用写法。
本章节着重从它们的含义、语法、用法搭配示例入手,尽可能地详细解释说明。
Set
Set 是 ES6 引入的一种新的数据结构,类似于数组,但其中的元素值唯一,不允许重复。
基本用法
创建 Set 实例:
使用
new Set()
创建一个空的 Set 实例。可以通过传入一个数组或其他可迭代对象来初始化 Set,初始化时会自动去除重复的元素。
jsconst s = new Set() // 创建一个空的 Set 实例 const set1 = new Set([1, 2, 3, 4, 4]) // 初始化 Set,自动去重
向 Set 添加元素:
使用
add
方法向 Set 添加新元素,如果元素已经存在则不会重复添加。jsconst s = new Set(); [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x)) // s 现在为 Set { 2, 3, 5, 4 }
遍历 Set:
使用
for...of
循环可以遍历 Set 中的所有元素,遍历顺序即插入顺序。jsconst s = new Set([2, 3, 5, 4]) for (const i of s) console.log(i) // 输出 2, 3, 5, 4
获取 Set 的大小:
使用
size
属性可以获取 Set 中元素的数量。jsconst items = new Set([1, 2, 3, 4, 5, 5, 5, 5]) items.size // 输出 5
特殊情况处理:
Set 内部使用 Same-value equality 算法判断元素是否相等,类似于
===
运算符,但是对 NaN 的处理不同。在 Set 中 NaN 是等于自身的。jsconst set = new Set() set.add(Number.NaN) set.add(Number.NaN) set // 输出 Set { NaN }
对象是根据引用地址判断是否相等,而非对象内容。
jsconst set = new Set() set.add({}) // 第一个空对象 set.add({}) // 第二个空对象,不同的引用 set.size // 输出 2
Set实例的属性和方法
Set 拥有 2个属性和 4种方法:
- 属性
- Set.prototype.constructor:构造函数,默认是 Set 函数
- Set.protptype.size:返回set实例的成员数量
- 方法
- add:添加某个值,返回添加后的 Set 结构
- delete:删除某个值,返回布尔值表示是否删除成功
- has:返回布尔值表示 Set 内是否存在该参数
- clear:清空 Set,无返回值
下面来使用一下:
const s = new Set()
s.add(1).add(2).add(2)
console.log(s, s.size) // [1, 2], 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2)
s.has(2) // false
Array.from
可以把 Set 转为数组,这就提供了数组去重的方法。
遍历操作
Set
有四种遍历方法:
keys()
返回键名values()
返回键值entries()
返回键值对对于
Set
而言,只有键值没有键名(或者说键名与键值相同),因此keys()
方法和values()
方法返回的值是一样的,entries()
方法返回的数组两个值一样。jsconst set = new Set(['tydumpling', 'xiaodao', 'tydumpling']) for (const key of set.keys()) console.log(key) // tydumpling // xiaodao // tydumpling for (const value of set.values()) console.log(value) // tydumpling // xiaodao // tydumpling for (const entrie of set.entries()) console.log(entrie) // [tydumpling, tydumpling] // [xiaodao, xiaodao] // [tydumpling, tydumpling]
另外
Set
默认可遍历,遍历返回的值默认是values()
。forEach()
使用回调函数遍历每个成员jsconst set = new Set([1, 2, 3]) set.forEach((value, key) => { console.log(value) }) // 1 // 2 // 3
特别的,Set
插入顺序就是遍历的顺序,这点在做回调函数列表时非常有用,能够保证按照插入顺序依次执行。
WeakSet
含义
WeakSet
与 Set
一样,也是不重复值的集合,但是有两点区别:
成员对象。
WeakSet
的成员对象只能是对象,如果使用其他类型的值,会报错jsconst wset = new WeakSet() wset.add(1) // TypeError: Invalid value used in weak set.
弱引用。
WeakSet
中的对象都是弱引用类型,若外部不再引用该对象,则垃圾回收器会自动回收,不考虑该对象是否还在WeakSet
中使用。因此,ES6 规定WeakSet
不可遍历
语法
由于 WeakSet
是一个构造函数,因此需要使用 new
创建数据结构。可以接收任何具有 iterable
接口的对象作为参数。
const a = new WeakSet([[1, 2], [3, 4]])
请注意,上述代码是每一项成员都是一个数组,只有数组对象可以作为 WeakSet
的成员。
const b = new WeakSet([1, 2]) // 报错
与 Set
相比,WeakSet
由于不能遍历,因此少了 size
等两种属性和 clear()
方法,只有三种方法:
add(value)
添加新成员delete(value)
删除指定成员has(value)
返回布尔值,表示是否有该值
Map
含义
ES6 之前都是使用对象 Object
做键值对的集合,但是对象有一个很大的限制,他只能使用字符串作为键名。
const element = document.querySelector('#content')
const obj = {}
obj[element] = 'content'
console.log(obj['[object HTMLDivElement]']) // content
上述代码是因为对象只能接受字符串,因此获取到的 DOM 元素会被 toString()
隐式转换为字符串。
ES6 新出了 Map
数据结构,类似于对象,但是键名不仅限于字符串,各种类型(包括对象)都可以作为键名。
Map
有两种添加值的方法:
set()
方法添加值,get()
方法获取值,delete
方法删除值,has()
方法判断是否有值jsconst m = new Map() m.set([1, 2], 'array') m.get([1, 2]) // array m.has([1, 2]) // true
接收一个二维数组作为参数,第二维的数组第一项是键名,第二项是键值
jsconst m = new Map([ ['name', '张三'], ['age', '24'] ]) m.size // 2 m.get('name') // 张三 m.has('age') // true
这个方法本质上是对传递的数组参数执行循环算法,依次
set
赋值。jsconst items = [ ['name', '张三'], ['age', '24'] ] const map = new Map() items.forEach(([value, key]) => { map.set(key, value) })
不仅是数组,所有具有
Iterator
接口且每个成员都是双元素数组的数据结构都可以作为Map
的构造函数参数。因此,Set
和Map
都可以用来生成新的Map
。jsconst set = new Set([ ['foo', 123], ['bar', 456] ]) const map = new Map(set) map.get('foo') // 123 map.get('bar') // 456 const m1 = new Map([ ['n', 1], ['m', 2] ]) const m2 = new Map(m1) m2.get('n') // 1 m2.get('n') // 2
⚠️ 注意
对于基本数据类型,只要值相同即可(需要注意的是,0 和 -0 在
Map
中是相同的;NaN
不严格等于自身,但在Map
中视为相同的)js// 基本数据类型 const mm = new Map() mm.set(0, '0') mm.get(-0) // '0' mm.set(Number.NaN, 123) mm.get(Number.NaN) // 123
对于数组和对象这类复杂数据类型,
Map
结构将其内存地址作为是否同一个键的比较标准jsconst map = new Map() map.set(['a'], 1) map.get(['a']) // undefined const arr = ['b'] map.set(arr, 'array') map.get(arr) // array
实例属性与语法
属性
size
属性可以获取Map
成员的总数jsconst map = new Map([ ['a', 1], ['b', 2], ['c', 3] ]) map.size // 3
方法
set(key, value)
方法设置 key
所对应的键值对,并返回总的 Map
结构。可以采取链式写法。
const m = new Map()
.set(1, 'tydumpling')
.set('2', 'xiaodao')
.set(undefined, 'tydumpling')
get(key)
方法获取 key
对应的键值,找不到返回 undefined
。
const m = new Map()
function sayHi() {
console.log('hello')
}
m.set('hi', sayHi)
m.get('hi') // function
m.get('hello') // undefined
has(key)
方法返回布尔值,表示该键值是否在 Map
中有数据。
delete(key)
方法删除 Map
中某个键,删除成功返回 true
,反之返回 false
。
clear()
方法清空所有成员,无返回值。
遍历操作
同 Set
一致,Map
也拥有 keys()
、values()
、entries()
和 forEach()
四种遍历方法,遍历的顺序也是插入顺序。
const map = new Map([
[1, 'a'],
[2, 'b'],
[3, 'c'],
])
for (const k of map.keys())
console.log(k)
// 1
// 2
// 3
for (const k of map.values())
console.log(k)
// a
// b
// c
for (const k of map.entries())
console.log(k[0], k[1])
// 1, a
// 2, b
// 3, c
Map
可以通过拓展运算符 ...
实现快速转数组操作,如转为数组、使用 map
或 filter
执行操作等。
const map = new Map().set(1, 'a').set(2, 'b').set(3, 'c')
const arr = [...map] // [[1, 'a'], [2, 'b'], [3, 'c']]
const keys = [...map.keys()] // [1, 2, 3]
const filterMap = new Map([...map].filter(([key, value]) => value !== 'c')) // Map(2) {1 => 'a', 2 => 'b'}
const mapMap = new Map([...map].map(([key, value]) => [key * 2, `${value}-`])) // Map(3) {2 => 'a-', 4 => 'b-', 6 => 'c-'}
此外,Map
可以使用 forEach
方法实现遍历操作,使用方式和数组一致。
const map = new Map([
[1, 'a'],
[2, 'b']
])
map.forEach((value, key, item) => {
console.log(value, key, item) // a, 1, [1, 'a']
})
Map
可以接收第二个参数,用于绑定 this
。
const map = new Map([
[1, 'a'],
[2, 'b']
])
const ojb = {
sayLog: (v, k, e) => {
console.log(v, k, e)
}
}
map.forEach((value, key, item) => {
this.sayLog(value, key, item)
}, ojb)
数据结构转换
Map
转数组上文也提到,
Map
转数组只需要拓展运算符...
即可。jsconst map = new Map([ [1, 'a'], [2, 'b'] ]) console.log([...map]) // [[1, 'a'], [2, 'b']]
数组转
Map
数组放到
new Map
的括号内就能转为Map
。jsconst arr = [ [1, 'a'], [2, 'b'] ] const map = new Map(arr)
Map
转对象如果
Map
的每一个键都是字符串的形式,则可以通过代码逻辑转为Map
。jsconst map = new Map([ ['yes', true], ['no', false] ]) function toObj(params) { const obj = Object.create(null) for (const [k, v] of params) obj[k] = v return obj } console.log(toObj(map)) // {yes: true, no: false}
对象转
Map
同理可得,可以通过代码逻辑把对象转为
Map
。jsconst obj = { yes: true, no: false } function toMap(params) { const map = new Map() for (const k of Object.keys(params)) map.set(k, params[k]) return map } console.log(toMap(obj)) // Map(2) {'yes' => true, 'no' => false}
Map
转JSON
Map
转JSON
有两种情况:键都是字符串型:此时可以转为对象形式的
JSON
jsfunction toObjJSON(params) { return JSON.stringify(toObj(params)) // 使用上方`Map` 转对象中的方法 } const map = new Map([ ['a', 1], ['b', 2] ])
键不全是字符串型:此时转为数组形式的
JSON
JSON
转Map