ES6 WeakSet和WeakMap

本文深入探讨了WeakSet和WeakMap的特性与用途,包括它们的相似之处、不计入垃圾回收机制的原因、操作限制以及在JavaScript中的应用场景,如DOM元素状态管理、监听事件和私有属性的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

WeakSet和WeakMap

(1)WeakSet和WeakMap一些相似的特点
1.WeakSet的成员只能是对象,WeakMap只接受对象(null除外)作为键名
    let weakset = new WeakSet([1, 2, 3]);
    //TypeError: Invalid value used in weak set

    let weakmap=new WeakMap([[0,2],[0,3]]);
    //TypeError: Invalid value used as weak map
    //这里的0为数字而不是对象

上面的代码均会报错。

   let weakmap=new WeakMap();

    let key={};
    weakmap.set(key,1);

    console.log(weakmap);
    //WeakMap { {}=>1 }

WeakMap只接受对象(null除外)作为键名,其他类型的值都可以作为键值。

2.不计入垃圾回收机制

WeakSet中的对象不计入垃圾回收机制,WeakMap中键名指向的对象不计入垃圾回收机制。

常见垃圾收集方式——标记清除

我们先来复习一下垃圾回收机制:在编写javascript程序时,开发人员不用关心内存的使用问题,所需内存的分配以及无用内存的回收实现了自动管理,垃圾收集器会按照固定的时间间隔,找出那些不再继续使用的变量,然后释放其占用的内存。
javascript中通常有两种垃圾收集方式:
1.标记清除(Mark-and-Sweep)
2.引用计数(Reference-counting)
大多数浏览器使用的是标记清除策略,因为引用计数策略无法解决因为循环引用而造成内存泄露问题。
标记清除指的是:
当变量进入执行环境,就将这个变量标记为“进入环境”,当变量离开环境时,将其标记为“离开环境”。垃圾收集器在运行的时候会给储存在内存中的所有变量加上标记(不同的浏览器可能使用不同的标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。在此之后仍然被加上标记的变量将被视为准备删除的变量,之后垃圾收集器运行的时候(不同浏览器垃圾收集器运行的时间间隔互有不同)销毁带标记的值并回收它们占用的内存。

3.WeakSet和WeakMap没有遍历操作,没有size属性,没有clear方法

WeakSet中的对象是弱引用,WeakMap的键名所引用的对象也是弱引用。垃圾回收机制不将它们的引用考虑在内。一旦不再需要,垃圾回收机制会自动将它们回收,不用手动删除,避免了内存的泄露。

因为WeakSet的成员和WeakMap引用的键名可能会随时消失,所以WeakSet和WeakMap没有遍历操作,也没有size属性,并且两者都没有clear方法。

对于WeakMap来说,弱引用的只是键名而不是键值
    let weakmap = new WeakMap();

    let key = {};
    let obj = {"foo": 1};
    weakmap.set(key, obj);

    console.log(weakmap);
    //WeakMap { {}=>{"foo":1} }

    obj = null;

    console.log(weakmap);
    //WeakMap { {}=>{"foo":1} }

键值obj是正常引用的。所以,即使在WeakMap外部消除了obj的引用,WeakMap内部的引用依然存在。

    let key = {};
    let obj = {"foo": 1};
    let weakmap = new WeakMap([[key,obj]]);

    console.log(weakmap);
    //WeakMap { {}=>{"foo":1} }

    key = {"rrr":1};
    obj = {"foo": 2};

    console.log(weakmap);
    //WeakMap { {}=>{"foo":1} }

即使在WeakMap外部改变了键名key和键值obj的外部引用,但在WeakMap内部依然引用的是构造时的引用值。

(2)WeakSet

WeakSet结构与Set类似,也是不重复的值的集合。

1.基本语法
WeakSet构造函数

任何具有iterable接口的对象都可以成为WeakSet构造函数的参数。以数组为例,数组的所有成员都会自动成为WeakSet实例对象的成员,而数组的成员只能是对象。

    let weakset = new WeakSet([[1, 2], [2, 3]]);
    console.log(weakset);
    // WeakSet [[1,2],[2,3]]
    // WeakSet [[2,3],[1,2]]
    //都有可能出现

    let set=new Set([[1, 2], [2, 3]]);
    console.log(set);
    //Set [[1,2],[2,3]]

以上代码可以看到WeakSet中的元素顺序和Set中的元素顺序是不同的。
WeakSet中元素顺序并不是确定的,当使用add()后,元素的顺序还会发生改变:

    let weakset = new WeakSet([[1, 2], [2, 3]]);
    console.log(weakset);
    // WeakSet [[4,5],[1,2],[2,3]]
    // WeakSet [[2,3],[4,5],[1,2]]
    //等等各种顺序都有可能出现
    weakset.add([4,5]);
    console.log(weakset);
    // WeakSet [[4,5],[1,2],[2,3]]
    // WeakSet [[2,3],[4,5],[1,2]]
    //等等各种顺序都有可能出现

两次运行相同的代码,在WeakSet中元素的顺序有可能是不同的。

2.操作方法

WeakSet没有遍历操作,没有size属性,没有clear方法。

add(value)

向WeakSet实例添加一个新成员。

delete(value)

清除WeakSet实例的指定成员。

has(value)

返回一个布尔值,表示某个值是否在WeakSet实例中。

3.使用WeakSet的例子
const foos = new WeakSet()
class Foo {
  constructor() {
    foos.add(this)
  }
  method () {
    if (!foos.has(this)) {
      throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
    }
  }
}

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

(3)WeakMap

WeakMap结构与Map结构类似也用于生成键值对的集合。

1.基本语法
WeakMap构造函数

WeakMap可以接受一个数组,作为构造函数的参数:

    let k1 = [1, 2];
    let k2 = [2, 3];

    let weakmap = new WeakMap([[k1, 123], [k2, 234]]);
    console.log(weakmap);
    //WeakMap { [2,3]=>234,[1,2]=>123 }
    //WeakMap { [1,2]=>123,[2,3]=>234 }
    //都有可能出现
    
   let map=new Map([[k1, 123], [k2, 234]]);
    console.log(map);
    //WeakMap { [2,3]=>234,[1,2]=>123 }

以上代码可以看到WeakMap中的元素顺序和Map中的元素顺序是不同的。
WeakMap中元素顺序并不是确定的,当使用set()后,元素的顺序还会发生改变:

    let k1 = [1, 2];
    let k2 = [2, 3];

    let weakmap = new WeakMap([[k1, 123], [k2, 234]]);
    console.log(weakmap);
    //WeakMap { [2,3]=>234,[1,2]=>123,[4,5]=>456}
    //WeakMap { [2,3]=>234,[4,5]=>456,[1,2]=>123}
    //WeakMap { [4,5]=>456,[2,3]=>234,[1,2]=>123}
    //等等各种顺序都有可能出现
    weakmap.set([4,5],456);
    console.log(weakmap);
    //WeakMap { [2,3]=>234,[1,2]=>123,[4,5]=>456}
    //WeakMap { [2,3]=>234,[4,5]=>456,[1,2]=>123}
    //WeakMap { [4,5]=>456,[2,3]=>234,[1,2]=>123}
    //等等各种顺序都有可能出现

两次运行相同的代码,在WeakMap中元素的顺序有可能是不同的。

2.操作方法

WeakMap没有遍历操作,没有size属性,没有clear方法。
WeakMap只有以下四种方法:

get()
set()
has()
delete()
3.使用WeakMap的例子
以DOM作为键名的场景
let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();

myWeakmap.set(myElement, {timesClicked: 0});

myElement.addEventListener('click', function() {
  let logoData = myWeakmap.get(myElement);
  logoData.timesClicked++;
}, false);

上面代码中,myElement是一个 DOM 节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是myElement。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

实现注册监听事件的listener对象
    const listener = new WeakMap();
    
    listener.set(element1, handler1);
    listener.set(element2, handler2);
    
    element1.addEventListener("click",listener.get(element1),false);
    element2.addEventListener("click",listener.get(element2),false);

上面的代码中,监听函数放在WeakMap里面。一旦DOM对象消失,与它绑定的监听函数也会自动消失。

部署私有属性
const _counter = new WeakMap();
const _action = new WeakMap();

class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  dec() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}

const c = new Countdown(2, () => console.log('DONE'));

c.dec()
c.dec()
// DONE

上面代码中,Countdown类的两个内部属性_counter和_action,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。

参考文献:《ECMAScript 6 入门》阮一峰

03-18
<think>嗯,用户想了解JavaScript中的WeakMap的用法特性,还有示例。首先,我需要回忆一下WeakMap的基本概念。根据引用[4],WeakMapES6引入的一种数据结构,Map类似,但有一些关键区别。比如,WeakMap的键必须是对象,而值可以是任意类型。另外,WeakMap的键是弱引用,不会阻止垃圾回收,这点很重要。 接下来,用户可能需要知道具体怎么用。根据引用[2],示例代码展示了如何创建WeakMap,使用set、get、has、delete方法。比如用new WeakMap()实例化,然后set添加键值对,has检查是否存在,get获取值,delete删除条目。这些方法用户可能需要具体步骤例子。 然后,特性方面,引用[3]提到WeakMap的键是弱引用,不会阻止垃圾回收,这有助于避免内存泄漏。但性能可能比Map稍差,特别是在大规模操作时。不过,因为不会保留对象引用,这对内存管理有利。另外,WeakMap不可迭代,没有size属性,也不能用clear方法,这些都需要在回答中说明。 用户可能还会想知道使用场景。根据引用[5],WeakMap适用于临时存储数据或缓存,比如DOM元素关联数据,私有属性存储。例如,当DOM节点被移除后,WeakMap中的键会自动被垃圾回收,不会造成内存泄漏。这点需要结合示例说明。 还要注意Map的区别,引用[4]指出Map的键可以是任何类型,而WeakMap只能是对象,并且Map有size属性迭代方法。这些对比能帮助用户更好理解何时使用WeakMap。 另外,引用[1][2]中的代码示例可以用来构造具体的例子,比如存储对象的附加信息,或者缓存计算结果。需要确保示例代码正确,语法无误,并且符合用户提供的格式要求,比如行内数学表达式用$...$,但这里可能用不上数学公式。 还需要检查是否有其他特性,比如WeakMap不能遍历,没有keys、values、entries方法,这些也是重要的点。同时,当键对象被垃圾回收后,对应的键值对会被自动移除,这点需要强调。 可能用户的问题还包括如何正确使用WeakMap,常见错误,以及为什么选择WeakMap而不是Map。不过根据用户的问题,重点在用法、特性示例,所以需要集中在这几点。 最后,生成相关问题的时候,要考虑用户可能进一步想了解的内容,比如Map的区别、内存管理的具体机制、实际应用案例等。确保问题覆盖这些方面,帮助用户深入学习。</think>### WeakMap的用法、特性及示例 #### 一、WeakMap的定义与核心特性 1. **弱引用键** WeakMap的键必须是对象(非基本类型),且键是弱引用。当键对象失去其他引用时,会被垃圾回收机制自动回收,对应的键值对也会从WeakMap中移除[^3][^5]。 示例: ```javascript let obj = { name: 'test' }; const weakMap = new WeakMap(); weakMap.set(obj, '关联数据'); obj = null; // 当obj被回收时,WeakMap中的对应条目自动消失 ``` 2. **不可迭代性** WeakMap不支持遍历操作(如`keys()`、`values()`、`entries()`),也没有`size`属性[^4]。 3. **内存管理优势** 适合存储与对象关联的临时数据(如DOM元素缓存、私有属性),避免因长期持有引用导致内存泄漏[^5]。 --- #### 二、基本用法 1. **初始化与操作** ```javascript const weakMap = new WeakMap(); // 初始化[^1] const keyObj = { id: 1 }; // 添加键值对 weakMap.set(keyObj, 'value'); // key必须为对象[^2] // 检查是否存在 console.log(weakMap.has(keyObj)); // true[^2] // 获取值 console.log(weakMap.get(keyObj)); // 'value'[^2] // 删除条目 weakMap.delete(keyObj); ``` 2. **与对象生命周期的关联** 当键对象被销毁时,WeakMap自动清理对应条目: ```javascript let element = document.getElementById('domNode'); weakMap.set(element, { clicks: 0 }); element.remove(); // 若element被移除且无其他引用,WeakMap中的条目会被自动回收 ``` --- #### 三、使用场景 1. **私有属性存储** 通过WeakMap实现类私有变量(避免外部访问): ```javascript const _privateData = new WeakMap(); class User { constructor(name) { _privateData.set(this, { name }); } getName() { return _privateData.get(this).name; } } ``` 2. **DOM元素元数据缓存** 存储与DOM节点关联的临时数据,节点移除后自动清理: ```javascript const nodeMetadata = new WeakMap(); const button = document.querySelector('#btn'); nodeMetadata.set(button, { lastClicked: Date.now() }); ``` --- #### 四、与Map的对比 | **特性** | **WeakMap** | **Map** | |-------------------|--------------------------------------|-----------------------------| | 键类型 | 仅对象 | 任意类型(包括对象) | | 垃圾回收影响 | 键被回收后条目自动删除 | 长期持有引用导致内存保留 | | 遍历与大小查询 | 不支持 | 支持`size`、`keys()`等方法 | | 性能 | 大规模操作略差,但内存管理更优 | 适合频繁查询遍历 | ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值