JS学习(17)WeakSet 和 WeakMap

70 阅读7分钟

前言

  • 是否了解 WeakSet 和 WeakMap

Map

Map是JavaScript中新的集合对象,其功能类似于对象。但是,与常规对象相比,存在一些主要差异。

首先,让我们看一个创建Map对象的简单示例。

添加属性

const map = new Map(); 
// Map(0) {} 

Map 有一种特殊的方法可在其中添加称为 set 的属性。它有两个参数:键是第一个参数,值是第二个参数。

map.set('name', 'john'); 
// Map(1) {"name" => "john"} 

但是,它不允许你在其中添加现有数据。如果 Map 对象中已经存在与新数据的键对应的值,则不会添加新数据。

map.set('phone', 'iPhone'); 
// Map(2) {"name" => "john", "phone" => "iPhone"} 
map.set('phone', 'iPhone'); 
// Map(2) {"name" => "john", "phone" => "iPhone"} 

但是你可以用其他值覆盖现有数据。

map.set('phone', 'Galaxy'); 
// Map(2) {"name" => "john", "phone" => "Galaxy"} 

获取属性和长度

Map 对象有一个名为 get 的方法,可用于获取 Map 中的属性。

map.get('name'); 
// "john" 
map.get('phone'); 
// "Galaxy" 

Map 对象还有一个名为 size 的属性,可用于获取 Map 中的属性数量。

map.size; 
// 2 

遍历对象

Map 是一个可迭代的对象,这意味着可以使用 for-of 语句将其映射。

for (const item of map) { 
  console.dir(item); 
} 
// Array(2) ["name", "john"] 
// Array(2) ["phone", "Galaxy"] 

要记住的一件事是 Map 以数组形式提供数据,你应该解构数组或访问每个索引以获取键或值。

要仅获取键或值,还有一些方法可供你使用。

map.keys(); 
// MapIterator {"name", "phone"} 
map.values(); 
// MapIterator {"john", "Galaxy"} 
map.entries(); 
// MapIterator {"name" => "john", "phone" => "Galaxy"} 

你甚至可以使用展开操作符(...)来获取Map的全部数据,因为展开操作符还可以在幕后与可迭代对象一起工作。

const simpleSpreadedMap = [...map]; 
// [Array(2), Array(2)] 

删除属性

Map 对象有一个名为 delete 的方法,可用于删除 Map 中的属性。

map.delete('phone'); 
// true 

Map 和 Object 的区别

MapObject
额外的键Map 默认情况不包含任何键。只包含显式插入的键。一个 Object 有一个原型, 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突注意:  虽然 ES5 开始可以用 Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见。
键的类型一个 Map的键可以是任意值,包括函数、对象或任意基本类型。一个Object 的键必须是一个String 或 Symbol
键的顺序Map 中的 key 是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。一个 Object 的键是无序的。注意:自ECMAScript 2015规范以来,对象确实保留了字符串和Symbol键的创建顺序; 因此,在只有字符串键的对象上进行迭代将按插入顺序产生键。
SizeMap 的键值对个数可以轻易地通过size 属性获取Object 的键值对个数只能手动计算
迭代Map 是 iterable的,所以可以直接被迭代。迭代一个Object需要以某种方式获取它的键然后才能迭代。
性能频繁增删键值对的场景下表现更好。在频繁添加和删除键值对的场景下未作出优化。

WeakMap

WeakMap 是 Map 的一个变体,它只接受对象作为键,并且这些键是弱引用。这意味着在没有其他引用的情况下,垃圾回收器可以回收这些键。

  • 差异1:key 必须是对象

可以将任何值作为键传入Map对象,但WeakMap不同,它只接受一个对象作为键,否则,它将返回一个错误。

const weakMap = new WeakMap(); 
const key = { name: 'john' }; 
weakMap.set(key, 'john'); 
// WeakMap { { name: 'john' } => 'john' } 

尝试将非对象作为键传入WeakMap对象将返回一个错误。

const weakMap = new WeakMap(); 
const key = 'john'; 
weakMap.set(key, 'john'); 
// TypeError: Invalid value used as weak map key 
  • 差异2:并非Map中的所有方法都支持

WeakMap 对象支持以下方法:

  • get()

  • set()

  • has()

  • delete()

还有一个不同是WeakMap不支持选代对象的方法。

  • 差异3:当GC清理引用时,数据会被删除
let obj = {a: 1}

const map = new Map()
const weakMap = new WeakMap()

map.set(obj, 'value')
weakMap.set(obj, 'value')

obj = null

console.log(map) // Map(1) { { a: 1 } => 'value' }
console.log(weakMap) // WeakMap { <items unknown> }

当obj对象被垃圾回收时,Map对象将保持引用链接,而WeakMap对象将丢失链接。所以当你使用WeakMap时,你应该考虑这个特点。

Set

Set也非常类似于Map,但是Set对于单个值更有用。

添加属性

Set 对象有一个名为 add 的方法,可用于添加属性。

set.add('name'); 
// Set(1) {"name"} 

如果 Set 对象中已经存在与新数据的键对应的值,则不会添加新数据。

set.add('name'); 
// Set(1) {"name"} 

但是你可以用其他值覆盖现有数据。

set.add('phone');

对于原始数据类型(boolean、number、string、null、undefined),如果储存相同值则只保存一个,对于引用类型,引用地址完全相同则只会存一个。

  • +0与-0在存储判断唯一性的时候是恒等的,所以不可以重复。
  • undefined和undefined是恒等的,所以不可以重复。
  • NaN与NaN是不恒等的,但是在Set中只能存一个不能重复。

遍历对象

Set 是一个可迭代的对象,这意味着可以使用 for-of 语句将其映射。

for (const item of set) { 
  console.dir(item); 
} 
// "name" 
// "phone" 

删除属性

Set 对象有一个名为 delete 的方法,可用于删除 Set 中的属性。

set.delete('phone'); 
// true 

应用场景

// 去重
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);

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}

WeakSet

和Set结构类似,也是不重复的值的集合,但WeakSet的成员只能是对象。

其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

这是因为垃圾回收机制根据对象的可达性(reachability)来判断回收,如果对象还能被访问到,垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。

由于上面这个特点,WeakSet 的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。

WeakSet能够使用的方法如下:

add(value):向WeakSet对象追加value。返回带有附加值的WeakSet对象。

delete(value):移除与该value关联的元素,并返回一个布尔值,判断元素是否被成功移除。WeakSet.prototype.has(value)之后将返回false。

has(value):返回一个布尔值,断言对象中是否存在具有给定值的元素WeakSet。

clear():移除WeakSet对象中所有元素。

let obj = {a: 1}

const set = new Set()
const weakSet = new WeakSet()

set.add(obj)
weakSet.add(obj)

obj = null

console.log(set)
console.log(weakSet)

总结

  • 是否了解 WeakSet 和 WeakMap

WeakSet对象是一些对象值的集合,并且其中的每个对象值都只能出现一次。

它和Set对象的区别有两点:

  • 与Set相比,WeakSet只能是对象的集合,而不能是任何类型的任意值。

  • WeakSet持弱引用:集合中对象的引用为弱引用。如果没有其他的对Weakset中对象的引用,那么这些对象会被当成垃圾回收掉。这也意味着WeakSet中没有存储当前对象的列表。正因为这样,WeakSet是不可枚举的。

WeakMap对象也是键值对的集合。它的键必须是对象类型,值可以是任意类型。它的键被弱保持,也就是说,当其键所指对象没有其他地方引用的时候,它会被GC回收掉。WeakMap提供的接口与Map相同。

与Map对象不同的是,WeakMap的键是不可枚举的。不提供列出其键的方法。列表是否存在取决于垃圾回收器的状态,是不可预知的。