# Set

# 集合 Set

Es6 新增的 Set 是一种新的集合类型(它的所有成员的值都是唯一的,没有重复),为 JavaScript 带来集合数据结构。Set 很多方面像加强的 Map,因为它们大多数的 API 和行为都是共有的。

const s = new Set()

初始化之后可以使用 add() 增加值,has() 查询值,size 获取元素数量,delete()clear() 删除元素。

s.add('Matt').add('Kate') // add 返回实例集合,所以可以将多个操作连缀起来
s.has('Matt') // true
s.size // 2
s.delete('Matt')
s.clear() //  s.size = 0  s.has('Matt') // false

Map 一样,Set 可以包含任何 js 数据类型作为值。如果对象被用作值,属性被修改时也不会改变集合:

const s = new Set()
const obj = {}
const arr = []

s.add(obj).add(arr)
obj.name = 'Matt'
arr.push('Matt')
s.has(obj) // true
s.has(arr) // true

Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据接口)作为参数初始化。

// 示例1
const s1 = new Set([1,2,3,4,5])

// 示例2
function divs() {
    return [...document.querySelectorAll('div')]
}
const s2 = new Set(divs())

由于成员具有唯一性,可以用来对数组进行去重。

// 方法一
Array.from(new Set(array))

// 方法二
[...new Set(array)]

# 遍历操作

Set 实例有 4 个遍历方法。遍历的顺序就是插入的顺序。

  • keys() 返回键名的遍历器
  • values() 返回键值的遍历器
  • entires() 返回键值对的遍历器
  • forEach() 回调遍历成员
let set = new Set(['red', 'yellow', 'green', 'blue'])
set.keys() // ['red', 'yellow', 'green', 'blue']
set.values() // ['red', 'yellow', 'green', 'blue']
set.entires() // [['red', 'red'], ['yellow', 'yellow'], ['green', 'green'], ['blue', 'blue']]
set.forEach((value, key) => {})

Set 结构默认可以遍历,可以省略 values() 方法,直接用 for...of 遍历 Set

for(let x of set){
    console.log(x) //red
}

# 应用

  • 扩展运算符(...)
let set = new Set(['red', 'yellow', 'green', 'blue'])
let arr = [...set] // ['red', 'yellow', 'green', 'blue']
  • map 和 filter 方法
let set = new Set([1,2,3])
set = new Set([...set].map(x => x*2)) // {2,4,6}
set = new Set([...set].filter(x => x>2)) // {4,6}
  • 并集 Union
let a = new Set([1,2,3])
let b = new Set([4,3,2])
let union = new Set([...a, ...b]) // {1,2,3,4}
  • 交集 Intersect
let a = new Set([1,2,3])
let b = new Set([4,3,2])
let intersect = new Set([...a].filter(x => b.has(x))]) // {2,3}
  • 差集 Difference
let a = new Set([1,2,3])
let b = new Set([4,3,2])
let difference = new Set([...a].filter(x => !b.has(x))]) // {2,3}

# 弱集合 WeakSet

弱集合和集合的区别有三点:

  • WeakSet 的成员只能是对象,不能是基本类型的值
  • 垃圾回收机制不考虑 WeakSet 对该对象的引用
  • WeakSet 不可遍历

对于第一点,如果使用基本类型,如 set.add(1),就会报错: TypeError: Invalid value in weak set

对于第二点,因为垃圾回收机制依赖引用计数,如果一个值的引用次数不为0,垃圾回收机制就不会释放这块内存。而 WeakSet 里的引用都是弱引用,如果对象的外部引用消失,垃圾回收机制就会自动回收该对象所占用的内存。

对于第三点,当外部引用消失,它在 WeakSet 里面的引用就会自动消失,但由于不能确定垃圾回收机制执行的时间,在垃圾回收机制运行前后就可能导致成员个数不一样,因此 ES6 规定 WeakSet 不可遍历

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

const b = [1,2]
const ws = new WeakSet(b) // Uncaught TypeError: Invalid value used in weak set

WeakSet 结构有以下三个方法:

  • add()
  • delete()
  • has()
const ws = new WeakSet()
const obj = {}
const arr = []

ws.add(obj)
ws.has(obj) // true
ws.delete(obj) // true
ws.has(obj) // false

因为 WeakSet 中的成员在任何时候都有可能被销毁,所以没有必要提供迭代能力,如 forEach() 等遍历操作,也没有 size 属性。

# WeakSet 典型应用场景

  • 储存 DOM 节点,而不用担心节点移除时造成的内存泄漏。
const disabledElements = new WeakSet()
const loginButton = document.querySelector('#login')

当 login 按钮从 DOM 树中被删除,就可以立即释放内存。

  • 部署私有属性
const foos = new WeakSet()
class Foo {
    constructor() {
        foods.add(this)
    }
    method () {
        if(!foos.has(this)) {
            throw new TypeError('Foo.prototype.method 只能在 Foo 的实例上调用')
        }
    }
}

上面代码保证了 Foo 的实例方法只能在 Foo 的实例上调用。这里使用 WeakSet 的好处是,数组 foos 对实例的引用不会被计入内存回收机制,所以删除实例的时候不用考虑 foos。也不会出现内存泄漏。

上次更新: 12/22/2021, 10:35:53 PM