深入浅出Map、WeakMap、Set、WeakSet

本文深入探讨了ES6中的Map、WeakMap、Set和WeakSet四种数据结构。Map提供了键值对的存储,保持插入顺序,支持各种遍历方式。WeakMap则是弱引用的键值对存储,有利于内存管理。Set是不重复元素的集合,同样维护插入顺序。WeakSet与WeakMap类似,仅存储对象且不提供迭代。在选择使用时,应考虑内存开销、插入和查询性能等因素。
摘要由CSDN通过智能技术生成

深入浅出Map、WeakMap、Set、WeakSet

本文章已制作相关视频,点击跳转B站:深入浅出Map、WeakMap、Set、WeakSet

Map

在 ES6 之前,键值对存储通过一个 Object 对象来实现:

var obj = {
    key: "val"
}

ES6 实现了一个真正的键值对存储:Map

基本API

  1. 实例化:new Map()

我们可以通过 new 实例化一个 Map 对象:

const m = new Map()

当然,如果想要在实例化的时候就去添加数据,那我们可以添加一个可迭代的对象(iterable object),比如一个array对象:

const m = new Map([
    ['k1', 'v1'],
    ['k2', 'v2'],
    ['k3', 'v3']
])

console.log(m.size) //> 3
const m = new Map({
    [Symbol.iterator]: function* () {
        yield ['k1', 'v1']
        yield ['k2', 'v2']
        yield ['k3', 'v3']
    }
})

console.log(m.size) //> 3
  1. set(k, v),添加一对新的键值对,如果键已存在,则用新值覆盖其旧值
const m = new Map()
m.set('k1', 'v1')

// set()方法返回当前 Map 对象,所以可以链式调用
m.set('k2', 'v2')
    .set('k3', 'v3')
  1. get(k),根据键得到它对应的值
  2. has(k),判断是否存在该键
const m = new Map()
m.set('k1', 'v1')
    .set('k2', 'v2')
    .set('k3', 'v3')

console.log(m.has('k1')) //> true
console.log(m.get('k1')) //> v1
console.log(m.get('k4')) //> undefined
  1. size,得到当前 Map 对象的键值对个数
  2. delete(k):删除指定的键值对
  3. clear():清空 map 对象的所有键值对

遍历

Map 维护了插入顺序,会根据这个顺序进行遍历

const m = new Map()
m.set('k1', 'v1')
    .set('k2', 'v2')
    .set('k3', 'v3')
// 方式一:
for (const e of m) {
    console.log(e)
}
//> [ 'k1', 'v1' ]
//> [ 'k2', 'v2' ]
//> [ 'k3', 'v3' ]

// 方式二:
for (const e of m.entries()) {
    console.log(e)
}
//> [ 'k1', 'v1' ]
//> [ 'k2', 'v2' ]
//> [ 'k3', 'v3' ]

// 方式三:
for (const e of m[Symbol.iterator]()) {
    console.log(e)
}
//> [ 'k1', 'v1' ]
//> [ 'k2', 'v2' ]
//> [ 'k3', 'v3' ]

// 实际上方式二和方式三调用同一个方法
console.log(m.entries === m[Symbol.iterator])
//> true

// 方式四:
m.forEach((v, k) => {
    console.log([k, v])
})
//> [ 'k1', 'v1' ]
//> [ 'k2', 'v2' ]
//> [ 'k3', 'v3' ]

// 方式五:只遍历键
for (const k of m.keys()) {
    console.log(k)
}
//> k1
//> k2
//> k3


// 方式六:只遍历值
for (const v of m.values()) {
    console.log(v)
}
//> v1
//> v2
//> v3

选择Map还是Object

Map 和 Object 都是用于键值对存储,我们需要了解它们之间的区别,方便在开发的时候进行选择

  1. 内存开销:在引擎层面上,MapObject 的实现区别很大,一般来说,键值对存储的内存开销是随着键值对数量线性增长的。不过对于大部分浏览器来说,Map 在内存开销上还是略胜一筹,粗略计算,MapObject 减少50%的内存开销,也就是说,同样的内存,Map 可以存储更多键值对。
  2. 插入性能:对于 MapObject,一个插入操作不会被键值对数量所影响,但 Map 的速度会稍微快于 Object,如果你的代码有大量插入操作,建议选择 Map
  3. 查询性能:某些情况下,浏览器会优化 Object 的存放位置(比如,有连续的整数属性),而在Map中这是不可能的,因此,如果你有大量查询,建议使用 Object
  4. 删除性能:删除操作是一件很可怕的事情,如果有删除需要,建议使用伪删除(pseudo-deleting)——将该属性赋值为 undefined 或者 null。如果真的有删除需要,Map 的删除性能会更快一些。

WeakMap

基本API

  1. 实例化

WeakMap 对象的 key 只能是 Object 实例化的对象或者派生类的对象,如果不是的话,则会报错;WeakMap 对象的 value 可以是任意类型:

// 1. Object 实例化的对象
const k1 = new Object()

// 2. 使用字面量对象,字面量对象实际上也是 Object 实例化的对象
const k2 = {}

// 3. Object 派生类的对象
class Other extends Object{}
const k3 = new Other()

// WeakMap 的 value 可以是任意的基本类型或者引用对象
const wm = new WeakMap([
    [k1, 10086],
    [k2, "I am China Moblie"],
    [k3, new Array("Interesting")]
])

// 如果 key 不符合规范则会报错
const badWM = new WeakMap([
    [1, 2],
    ["Yoo", "Ugh"]
])
//> TypeError: Invalid value used as weak map key

WeakMap 的其他 API 基本和 Map对象是一样的,但注意,并没有 size 属性和 clear() 方法

  1. set(k, v),添加一对新的键值对,如果键已存在,则用新值覆盖其旧值
  2. get(k),根据键得到它对应的值
  3. has(k),判断是否存在该键
  4. delete(k):删除指定的键值对

弱键

WeakMap 的 key 只能是 Object 实例化对象或者派生类对象的目的是,让这个key被弱持有

假设我们有一个场景,我们需要存储DOM节点的属性以及它的值,我们可能会用到 Object 或者 Map,假设使用 Map

const m = new Map()
// 假设我们需要保存一个登录按钮的属性值
const loginButton = document.querySelector("#login")
m.set(loginButton, {disabled: true})

这样会产生一个问题:当用户登录之后,跑到另外一个页面,登录按钮被移除了,正常来说,这个登录 DOM 节点应该也应该被垃圾回收器清除,但它被 loginButton 变量引用,而 loginButton 作为 key 被 map 引用,所以登录 DOM 节点会保留在内存中,白白占用空间。

这时候解决方法是手动解除引用,要么使用 delete 方法删除该键值对,要么等 Map 对象被销毁。

如果我们使用 WeakMap 对象进行同样的储存:

const wm = new WeakMap()
const loginButton = document.querySelector("#login")
wm.set(loginButton, {disabled: true})

作为 WeakMap 对象key的 loginButton 不会被算成正式的引用(formal reference),也就是说 loginButton 变量相当于不会被 wm 引用,这时垃圾回收器就可以把这个 loginButton 变量和登录 DOM节 点都给干掉,释放内存空间。

这样就起到了自动清理的效果,这也是 WeakMap** 弱持有**的目的所在。

不可迭代键

WeakMap 的 key 是不算正式引用,随时可能会被回收清除掉,因此 WeakMap 不提供迭代的功能。

对于 size 属性和 clear() 方法,由于它们需要先迭代遍历所有的 key 才能计算得到,所以同样无法使用。

Set

ECAMScript6 引入了 Set 类型,它同我们高中学到的集合概念是一直的——确定性、互异性、无序性。

可能 SetMapset() 方法有点混,Set 意思是集合,名词;而Map.set()设置,动词来着,注意区分。

基本API

  1. 实例化。同 Map 一样在构造器中添加一个可迭代对象作为初始化数据。
const set1 = new Set()

const set2 = new Set(['v1', 'v2', 'v3'])

console.log(set2.size)
//> 3

const set3 = new Set(
    {
        [Symbol.iterator]: function* () {
            yield 'v1'
            yield 'v2'
            yield 'v3'
        }
    }
)
console.log(set3.size)
//> 3
  1. add(),添加数据

    const s = new Set()
    // add()返回当前Set对象,因此可以链式调用
    s.add('h').add('e').add('l').add('l').add('o')
    
  2. has(v),判断是否存在该 value

  3. delete(v),删除该 value

  4. clear(),删除 Set 对象中的所有 value

  5. size,返回当前 Setvalue 的个数

遍历

Map 一样,Set 也维护了插入顺序,会根据这个顺序进行遍历。

const s = new Set()
s.add('h').add('e').add('l').add('l').add('o')

// 方式一:
for (const v of s) {
    console.log(v)
}
//> h
//> e
//> l //! 注意这里只有一个 l,这是由于集合的互异性,每个元素都是不一样的
//> o

// 方式二:
for (const v of s.values()) {
    console.log(v)
}
// 或者
for (const v of s.keys()) {
    console.log(v)
}
//> h
//> e
//> l 
//> o

// 方式三:
for (const v of s[Symbol.iterator]()) {
    console.log(v)
}
//> h
//> e
//> l 
//> o

//! 以上三种方式其实都调用同一个方法
console.log(s.values === s[Symbol.iterator])
console.log(s.keys === s[Symbol.iterator])

// 方式四:
for (const pair of s.entries()) {
    console.log(pair)
}
//> [ 'h', 'h' ] //! 键和值都是相等的
//> [ 'e', 'e' ]
//> [ 'l', 'l' ]
//> [ 'o', 'o' ]

// 方式五:
s.forEach((val, sameVal) => {
    console.log(`[ '${val}', '${sameVal}' ]`)
})
//> [ 'h', 'h' ]
//> [ 'e', 'e' ]
//> [ 'l', 'l' ]
//> [ 'o', 'o' ]

WeakSet

WeakSetWeakMap 基本相同,存放的 value只能是 Object实例化的对象或者派生类的对象,并且不能迭代, 也没有 clear() 方法、size属性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值