基础-内存泄漏与闭包

内存泄漏和闭包可以说是最基本的内容了,但要清楚他们之间的的关系,则得先了解什么是垃圾回收。

1. 垃圾回收

那什么是垃圾呢?一直以来也没有明确的定义,但都形成了一个共识,那就是不再需要的内存,这些内存里的数据就是垃圾。那什么又是不再需要呢?这个是由开发者决定的,需不需要取决于你。
比如说:

      function createIncrease() {
        const doms = new Array(100).fill(0).map((_, index) => {
          const dom = document.createElement("div");
          dom.innerHTML = index;
          return dom;
        });

        return function increase() {
          doms.forEach((dom) => {
            dom.innerHTML = Number(dom.innerHTML) + 1;
          });
        };
      }
      const increase = createIncrease();
      document.querySelector("button").addEventListener("click", increase);

这个createIncrease创建了100div,原来的div的内容是下标,从0开始。里面有个子函数increase,循环所有的div把内容+1

这个很标准的闭包了,拿到子函数,也拿到了上面声明的doms,从运行过程来看,每次点击按钮,使用这个子函数时,是需要这个doms的。所以这些doms是不是垃圾?很明显不是,因为我要使用它。垃圾与占用内容空间的大小无关。

再比如说:

      const arr = [1, 2, 3, 4, 5];
      const sum = arr.reduce((total, num) => total + num, 0);
      console.log('sum',sum);

对数组进行求和,第一次运行结束之后,那[1, 2, 3, 4, 5]是需要的还是不需要呢?是结束了,还是后面还要用到?所以回到上面,这取决于你。

所以说,现在有一些你认为的不再需要的内容,那它们就是垃圾。js语言里面有个垃圾回收器,它可以帮助我们回收那些不再需要的内容。😂问题又来了,它怎么知道我们不再需要了。嘿,连我们自己都不知道,它知道就离谱了。虽然它不知道,但是啊,它还是很大胆的,它做出假设,虽然不知道你需要哪些东西,但是你访问不了的东西,你肯定是不想要的。比如上面的例子,

   let arr = [1, 2, 3, 4, 5];
      arr = [4, 5, 6, 7, 8, 9];
      const sum = arr.reduce((total, num) => total + num, 0);
      console.log("sum", sum);

arr重新赋值,那初始化的那内容空间还有机会访问吗?没了,不可能访问到了,所以回收机制就认为这个就是你不需要的。

所以垃圾回收器回收的是那些我们认为不想要的,且无法触达的内存空间。那不想要的有很多,无法触达的知识其中的一小部分,还有一部分是可以触达的,所以垃圾回收器无法回收,它们就一直存在那里,这就是内存泄漏

那当这个内存泄漏变大了之后,就会严重影响我们程序的运行,那如何解决内存泄漏?就是让这些内容变得不可触达就可以了。比如说,

  let arr = [1, 2, 3, 4, 5];
      const sum = arr.reduce((total, num) => total + num, 0);
      console.log("sum", sum);
      arr = null;

在程序运行结束后,令arr = null,那[1, 2, 3, 4, 5];内存空间就变得不可触达,就能被垃圾回收器处理了。

又比如第一个,点击第一次之后就不需要了。

 const btn = document.querySelector("button");
      let increase = createIncrease();

      function handle() {
        increase();
        btn.removeEventListener("click");
        increase = null;
      }
      btn.addEventListener("click", handle);

第一次点击过后,就移除监听器,然后令increase = null,那之前的函数就变得不可触达,然后被回收。

哎,说到这里,怎么都是垃圾回收和内存泄漏,那内存泄漏和闭包的关系呢?从上面的例子看,好像没有什么直接的关系,只是闭包会让我们放松警惕。像例2,只需要在最后令arr = null,就可以了。但是闭包得绕一下,你只是认为以后不用这个函数了,函数能有多大的内存空间,所以就不理了。😳但是闭包所关联的那个环境,把doms保留下来了。所以不是流传着那句话闭包容易引起内存泄漏,慎用闭包嘛。

闭包的内存泄漏和其他内存泄漏没有啥本质的区别:

1.持有了不再需要的函数引用,会导致函数关联的词法环境无法销毁,从而导致内存泄漏。

2.当多个函数共享词法环境时,会导致词法环境膨胀,从而导致无法触达也无法回收的内容空间。

   function createIncrease() {
        const doms = new Array(100).fill(0).map((_, index) => {
          const dom = document.createElement("div");
          dom.innerHTML = index;
          return dom;
        });

        function increase() {}
        function tem() {
          doms;
        }

        return increase;
      }

      const btn = document.querySelector("button");
      let increase;
      btn.addEventListener("click", () => {
        increase = createIncrease();
      });

点击按钮,但是这个函数没有用到闭包环境里的domsdoms被另一个子函数使用了。点击按钮后,在控制台手动垃圾回收,再比较前后的内存,发现内存没被回收。

这就是因为tem函数的存在,它们的闭包环境都是一样的,共享一个词法环境。如果说只有一个子函数,那如果没有用到,浏览器就会把doms优化掉,这就不会造成内存泄漏。但是现在有另一个函数使用了doms,那浏览器不敢回收。后面返回increase,没用到doms,但是词法环境仍然保留了这个doms

原文链接:https://juejin.cn/post/7343255545811796004 作者:初出茅庐的

(0)
上一篇 2024年3月7日 下午5:02
下一篇 2024年3月7日 下午5:12

相关推荐

发表回复

登录后才能评论