Set vs WeakSet 集合类型比较

1,071 阅读4分钟

前言

在 JavaScript 中,SetWeakSet 是不同的集合类型,它们在多个方面存在显著差异。本文将深入探讨它们的异同,从数据存储方式、引用特性、迭代能力到实际应用场景,帮助读者更全面地理解和运用这两种集合类型。

Set :

  1. 数据存储方式: Set是一种存储唯一值的集合,它不允许重复的元素。它可以包含任何数据类型,包括原始数据类型和对象引用。
  2. 引用: Set中的元素是强引用,这意味着当集合中的对象不再被引用时,集合仍然保留对该对象的引用。有时可能导致内存泄漏。
  3. 可迭代性: Set是可迭代的,可以使用for...of循环或forEach方法遍历集合中的元素。

WeakSet :

  1. 数据存储方式: WeakSet同样用于存储对象引用,但是它只允许存储对象引用,并且这些引用是弱引用。
  2. 引用: WeakSet中的对象引用是弱引用,这意味着如果在程序的其他地方没有对该对象的强引用,垃圾回收器可能会回收该对象,并从WeakSet中移除。
  3. 可迭代性: WeakSet不是可迭代的,没有提供直接的方法来遍历其元素,因为元素的引用是弱引用,不稳定且无法确定何时会被垃圾回收。

它们的主要区别就是以下两点:

1. 类型限制:

  • Set: Set可以包含任何类型的值,包括原始数据类型和对象引用。

    const mySet = new Set();
    mySet.add(1); // 可以存储原始数据类型
    mySet.add("Hello"); // 可以存储字符串
    const obj = { key: "value" };
    mySet.add(obj); // 可以存储对象引用
    
  • WeakSet: WeakSet的成员只能是对象和Symbol值,不能包含其他类型的值。

    const myWeakSet = new WeakSet();
    myWeakSet.add({}); // 只能存储对象
    const symbol = Symbol("symbol");
    myWeakSet.add(symbol); // 可以存储Symbol值
    myWeakSet.add(1); // 会报错,不能存储原始数据类型
    

2. 弱引用特性:

  • Set: Set中的元素是强引用,即使在程序的其他地方没有对该对象的引用,该对象仍然存在于Set中。

  • WeakSet: WeakSet中的对象是弱引用,如果在程序的其他地方没有对该对象的强引用,垃圾回收机制会自动回收该对象,同时也会从WeakSet中移除。

    const weakSet = new WeakSet();
    let obj = { key: "value" };
    
    weakSet.add(obj);
    console.log(weakSet.has(obj)); // true
    
    // 当没有其他引用指向obj时,垃圾回收机制会回收obj,并从WeakSet中移除
    obj = null;
    console.log(weakSet.has(obj)); // false
    

方法和遍历:

  • Set: 提供了adddeletehas等方法,以及可迭代的特性。
  • WeakSet: 提供了adddeletehas等方法,但没有可迭代的特性,也没有size属性。而且WeakSet不能遍历,因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。
const mySet = new Set();
mySet.add(1);
mySet.delete(1);
console.log(mySet.has(1)); // false
console.log(mySet.size); // 获取Set的大小

const myWeakSet = new WeakSet();
const obj = {};
myWeakSet.add(obj);
myWeakSet.delete(obj);
console.log(myWeakSet.has(obj)); // false
console.log(myWeakSet.size); // undefined,WeakSet没有size属性

应用场景:

  • Set: 适合需要存储任意类型值且不希望有重复值的情况。
  • WeakSet: 适合临时存放一组对象,以及存放跟对象绑定的信息。由于WeakSet中的引用不会影响垃圾回收,因此适合用于防止内存泄漏,例如存储DOM节点,确保当节点被移除时,不会阻止其被垃圾回收。

下面来个WeakSet实例:

const foos = new WeakSet()
class Foo {
  constructor() {
    foos.add(this)
  }
  method () {
    if (!foos.has(this)) {
      throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
    }
  }
}
  • const foos = new WeakSet():创建一个WeakSet实例,用于存储Foo类的实例引用。
  • class Foo { ... }:定义了一个Foo类。
  • Foo类的构造函数中,foos.add(this):在foosWeakSet中添加当前Foo类的实例。这确保了Foo的实例会被弱引用存储,不影响垃圾回收。
  • method()Foo类的实例方法,用于执行某些操作。在方法的开头使用if (!foos.has(this))检查this是否在foos中,如果不在,说明该方法不是在Foo的实例上调用的,会抛出一个TypeError,提示用户只能在Foo的实例上调用该方法。

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

总结

  • 使用Set当你需要存储一组唯一的值,并且这些值在整个程序的生命周期内都是稳定的。
  • 使用WeakSet当你需要存储对象引用,并且希望这些引用是弱引用,当对象在其他地方没有被强引用时可以被垃圾回收。

最后

总的来说,WeakSet主要用于需要弱引用特性以避免内存泄漏的情况,而Set适用于一般的集合需求。希望本文能够给你带来帮助!感谢阅读!

我的Gitee:    CodeSpace (gitee.com)

技术小白记录学习过程,有错误或不解的地方还请评论区留言,如果这篇文章对你有所帮助请 “点赞 收藏+关注” ,感谢支持!!