Set
是 ES6
提供的一种新的数据结构。类似于数组,但是不允许有重复值(因为键名和键值相同)。
使用 Set()
构造函数可以生成 Set
类型数据结构:
let set = new Set()
;[1, 2, 2, 3, 2, 1].map(item => set.add(item))
for (let item of set) {
console.log(item) // 1 2 3
}
Set()
构造函数可以接受一个具有 iterator
接口的数据结构作为参数(例如数组),用来初始化:
let set: Set<any> = new Set([1, 2, 3])
console.log(set) // Set { 1, 2, 3 }
set = new Set('abc')
console.log(set) // Set { 'a', 'b', 'c' }
由于 Set
中的值的唯一性,所以可以用来实现数组去重:
function dedupe(arg: any[] | string) {
return [...new Set(arg)]
}
console.log(dedupe('aabacdcdd')) // [ 'a', 'b', 'c', 'd' ]
console.log(dedupe([1, 1, 2, 3, 3, 2, 4])) // [ 1, 2, 3, 4 ]
Set
内部判断两个值是否相同,使用的算法叫做 "Same-value-zero equality"
,类似于精确相等运算符(===
),主要区别是 Set
中加入 NaN
只能加入一次,而精确相等运算符(===
)认为 NaN !== NaN
:
let se = new Set()
se.add(NaN)
se.add(NaN)
console.log(se) // Set { NaN }
注意:两个对象总是不相等的。这点与 ===
一致。
let set = new Set()
set.add({})
set.add({})
console.log(set) // Set { {}, {} }
console.log({} === {}) // false
Set 的实例方法分为两类: 操作方法 和 遍历方法:
操作方法
let se = new Set([2, 3])
se.add(1)
console.log(se.size) // 3
console.log(se.delete(2)) // true
console.log(se.has(2)) // false
se.clear()
console.log(se) // Set {}
遍历方法
Set
的遍历顺序就是按照插入的顺序。
keys(), values(), entires()
这三个方法均返回遍历器,由于 Set
键名与键值一样,所以 keys()
和 values()
效果一致:
let set = new Set(['red', 'blue', 'green'])
for (const item of set.keys()) {
console.log(item) // red blue green
}
for (const item of set.values()) {
console.log(item) // red blue green
}
for (const item of set.entries()) {
console.log(item)
}
// [ 'red', 'red' ]
// [ 'blue', 'blue' ]
// [ 'green', 'green' ]
Set
实例默认可以遍历,它的默认遍历器([Symbol.iterator])就是它的 values()
方法:
Set.prototype[Symbol.iterator] === Set.prototype.values // true
for (const item of set) {
console.log(item) // red blue green
}
forEach() 用于对每个成员进行某种操作:
set.forEach((value, key) => {
console.log(key, value)
})
// red red
// blue blue
// green green
遍历的应用
拓展运算符 ...
内部使用 for...of
循环,所以也可以用于 Set
结构:
let set = new Set([1, 2, 3, 2, 3, 1])
let arr = [...set]
console.log(arr) // [ 1, 2, 3 ]
Set
可以很容易实现 并集
, 交集
和 差集
:
let setOne = new Set([1, 2, 3])
let setTwo = new Set([4, 3, 2])
// 并集
let union = new Set([...setOne, ...setTwo]) // Set { 1, 2, 3, 4 }
// 交集
let intersect = new Set([...setOne].filter(item => setTwo.has(item))) // Set { 2, 3 }
// 差集
let difference = new Set([...setOne].filter(item => !setTwo.has(item))) // Set { 1 }
WeakSet
与 Set
类似,但是有两个区别:
WeakSet
的成员只能是对象(不包括 null),不能是其他类型的值:
let ws = new WeakSet()
ws.add({}) // ok
ws.add(1) // Error,只能存放对象成员
ws.add(Symbol()) // Error,只能存放对象成员
ws.add(null) // Error,只能存放对象成员
WeakSet
中的对象都是 弱引用
,垃圾回收机制不考虑 WeakSet
对该对象的引用,如果其他对象都不再引用该对象,该对象的内存将被回收, WeakSet
中该对象成员也将消失。正是由于这个特点,ES6 规定 WeakSet
无法被遍历,也没有 size
属性。
垃圾回收机制依赖于
引用计数
,如果一个对象的引用次数不为0
,则无法被回收,如果结束使用某个对象之后忘记取消引用,导致内存无法释放,则会引起内存泄漏
。WeakSet
中的引用属于弱引用,不计入垃圾回收机制,所以不存在这个问题。因此,它是用于临时存放一组对象。
WeakSet()
构造方法类似 Set()
构造方法,但是传入的 iterable
参数的成员必须是对象:
let arr1 = ['a']
let arr2 = [1]
let ws = new WeakSet([arr1, arr2])
WeakSet
没有遍历操作(即没有 keys()
, values()
, entries()
, forEach()
),也没有 size
属性。WeakSet
无法被清空,即没有 clear()
方法。const ws = new WeakSet()
const obj = {}
const foo = {}
const bar = {}
ws.add(obj)
ws.add(foo)
console.log(ws.has(obj)) // true
console.log(ws.has(bar)) // false
console.log(ws.delete(obj)) // true
console.log(ws.has(obj)) // false
ES6
提供的 Map
数据结构,类似于对象,但是键不限于字符串,各种类型的值(包括对象)都可以作为键,实现了 值 - 值
对应。
let m = new Map()
let o = { o: 'abc' }
m.set(o, 'hello')
console.log(m.get(o)) // "hello"
Map()
构造函数还接受一个具有 iterator
接口数据结构、且每个成员都是一个双元素数组的数据结构作为参数,符合条件的数组,Set,Map 都可以。
数组作为构造函数的参数:
const obj1 = { a: 'A' }
const arrA: [any, any][] = [
[12, 'arrA'],
['name', 21],
[obj1, obj1.toString()]
]
let m1 = new Map(arrA)
console.log(m1) // Map { 12 => 'arrA', 'name' => 21, { a: 'A' } => '[object Object]' }
Set 作为构造函数的参数:
const obj1 = { a: 'A' }
const arrA: [any, any][] = [
[12, 'arrA'],
['name', 21],
[obj1, obj1.toString()]
]
const setC = new Set(arrA)
let m2 = new Map(setC)
console.log(m2) // Map { 12 => 'arrA', 'name' => 21, { a: 'A' } => '[object Object]' }
Map 作为构造函数的参数:
const obj1 = { a: 'A' }
const arrA: [any, any][] = [
[12, 'arrA'],
['name', 21],
[obj1, obj1.toString()]
]
let m1 = new Map(arrA)
let m3 = new Map(m1)
console.log(m3) // Map { 12 => 'arrA', 'name' => 21, { a: 'A' } => '[object Object]' }
在 Map
中对同一个键多次赋值将导致后面的值覆盖前面的值。 Map
中键的相等判断方法与 Set
中判断方法相同。
特别需要注意的是:如果以对象作键名,则只有对同一个对象的引用,Map 才将其视为同一个键:
let m = new Map()
let obj = [1]
m.set([1], 'A')
console.log(m.get(obj)) // undefined
m.set(obj, 'B')
console.log(m.get(obj)) // "B"
Map 实例的方法同样分为 操作方法 和 遍历方法:
操作方法:
let map = new Map()
map.set(1, 'A').set('a', 2)
console.log(map.size) // 2
console.log(map.has(1)) // true
console.log(map.get(1)) // "A"
console.log(map.delete('a')) // true
console.log(map) // Map { 1 => 'A' }
map.clear()
console.log(map) // Map {}
遍历方法:
Map
的遍历顺序就是插入顺序。
let map = new Map()
map.set(1, 'A').set('a', 2)
for (const key of map.keys()) {
console.log(key) // 1 "a"
}
for (const value of map.values()) {
console.log(value) // "A" 2
}
for (const item of map.entries()) {
console.log(item[0] + ' -- ' + item[1])
}
// "1 -- A"
// "a -- 2"
map.forEach((value, key, map) => {
console.log(key + ' -- ' + value)
})
// "1 -- A"
// "a -- 2"
Map
实例默认可以遍历,它的默认遍历器([Symbol.iterator])就是它的 entries()
方法:
Map.prototype[Symbol.iterator] === Map.prototype.entries // true
for (const item of map) {
console.log(item[0], item[1])
}
// 1 "A"
// "a" 2
Map 转数组
Map 转数组最便捷的方法是使用拓展运算符 ...
:
let map = new Map()
map.set({}, 'A').set({}, 1)
console.log([...map]) // [ [ {}, 'A' ], [ {}, 1 ] ]
数组转 Map
将数组传入 Map 构造函数即可:
const obj1 = { a: 'A' }
const arrA: [any, any][] = [
[12, 'arrA'],
['name', 21],
[obj1, obj1.toString()]
]
let m1 = new Map(arrA)
console.log(m1) // Map { 12 => 'arrA', 'name' => 21, { a: 'A' } => '[object Object]' }
WeakMap
与 Map
的关系和 WeakSet
与 Set
的关系类似:
WeakMap
只接受对象作为键名(不包括 null):
let wm = new WeakMap()
wm.set(1, 1) // 错误
wm.set(Symbol(), 1) // 错误
wm.set(null, 1) // 错误
wm.set({}, 1) // ok
WeakMap
键名所引用的对象是 弱引用
,一旦该对象的其他引用被清除, WeakMap
中该键名对应的成员自动移除。基本上想要王对象上添加数据,但是又不想干扰垃圾回收机制,就可以使用 WeakMap
。
WeakMap
相比 Map
的 API,没有了遍历方法(keys()
, values()
, entries()
, forEach()
)。
另外,WeakMap
无法被清空,即没有了 clear()
方法。所以只有 4 个方法可用: set()
, get()
, has()
, delete
。