WeakMap

129 阅读3分钟

概念

在看vue源码的时候发现里面用到了WeakMap,这个东西之前没见过,不太了解,所以这里学习并记录一下。
  • 先看一下mdn上面的介绍,WeakMap 是一种键值对的集合,其中的键必须是对象或非全局注册的符号,且值可以是任意的 JavaScript 类型,并且不会创建对它的键的强引用。换句话说,一个对象作为 WeakMap 的键存在,不会阻止该对象被垃圾回收。一旦一个对象作为键被回收,那么在 WeakMap 中相应的值便成为了进行垃圾回收的候选对象,只要它们没有其他的引用存在。唯一可以作为 WeakMap 的键的原始类型是非全局注册的符号,因为非全局注册的符号是保证唯一的,并且不能被重新创建。

    翻译一下就是WeakMap可以像Map一样存储数据,但是区别也很明显
  1. key必须是对象或者Smybol类型
  2. value值在除了自己以外,没有其他的引用时,这个value值可以被垃圾回收

验证

  1. WeakMap示例
const weakMap = new WeakMap()
function fn() {
  for (let i = 0; i < 10000; i++) {
    const obj = createBigMemData(10000) //创建obj,为引用类型
    const symbolI = Symbol(i);
    weakMap.set(symbolI, obj)
  }
}
// 创建一个大内存数据
function createBigMemData(num) {
  return new Array(num).fill(1)
}
fn()
console.log("-----end------")

执行完上面的代码,在浏览器中查看内存,截图如下:

1729245378137.png 可见,所有通过createBigMemData生成的obj都被释放掉了。但是没有对比不好直接这么推测,所以搞个其他示例(没有释放掉的)做个对比。

  1. Map示例对比
const map = new Map();
function fn() {
  for (let i = 0; i < 1000; i++) {
    const obj = createBigMemData(10000) //创建obj,为引用类型
    const symbolI = Symbol(i);
    map.set(symbolI, obj)
  }
}
console.log("-----end------")

function createBigMemData(num) {
  return new Array(num).fill(1)
}

fn();

执行完后,继续查看内存,截图如下: 1729304172667.png 由此可见,后面Map存储的obj对象指向的内存是没有释放的。
那么有的小伙伴会问,如何才能把这个内存释放掉呢?这个也是大家在平时开发过程中容易遇到的问题。
释放内存代码如下:

let map = new Map();
function fn() {
  for (let i = 0; i < 1000; i++) {
    const obj = createBigMemData(10000) //创建obj,为引用类型
    const symbolI = Symbol(i);
    map.set(symbolI, obj)
  }
}
console.log("-----end------")

function createBigMemData(num) {
  return new Array(num).fill(1)
}

fn();

function releaseMem() {
  map = null;
  //or
  // map.clear();
}
releaseMem();

执行完后,继续查看内存,截图如下: 1729304998623.png 那么我们只需要将引用obj对象的Map对象进行清空或者置为null就可以将指向obj的引用干掉,那么obj就会被浏览器及时回收了。

  1. 模仿vue源码写个WeakMap内存释放的过程
let weakMap = new WeakMap()
    let otherMap = new Map()

    function fn() {
      for (let i = 0; i < 1000; i++) {
        const obj = createBigMemData(10000) //创建obj,为引用类型
        const symbolI = Symbol(i)
        weakMap.set(symbolI, obj)
        otherMap.set(symbolI, obj)
      }
    }

    console.log("-----end------")

    function createBigMemData(num) {
      return new Array(num).fill(1)
    }

    fn()
    // 十秒后清理obj的引用,回收内存
    function releaseMem() {
      setTimeout(() => {
        otherMap = null;
        console.log("-----release end------")
      }, 10000);
    }
    releaseMem();

上面的代码刚开始运行完内存占用为40多M,但是等待10s左右后,将指向obj的otherMap引用清理掉后,内存占用就回到1.5M左右。
由此可见WeakMap指向obj的应用并没有阻止obj内存的回收,这也符合一开头的概念定义。Vue正是借助这一点,巧妙的实现了对象和代理对象的存储关系,并且在代理对象销毁的时候,不会因为WeakMap仍然持有对这个代理对象的引用而阻止内存对代理对象的回收。 完结撒花~