浅谈垃圾回收、内存泄漏与闭包

本文探讨了JavaScript中的垃圾回收机制,如何判断内存是否为垃圾,以及内存泄漏的常见原因,如未清除的定时器、事件监听器和闭包导致的内存占用问题。同时,给出了避免内存泄漏的策略和闭包引发内存泄漏的实例分析。
摘要由CSDN通过智能技术生成

什么是垃圾?

在js中,垃圾通常指的是不再被程序使用的内存或对象。也就是说,垃圾是指程序中分配的内存空间或对象,但不再被程序使用或无法被访问到的内容

function createIncrease() {
  const doms = new Array(100000).fill(0).map((item, i) => {
    const dom = document.createElement("div");
    dom.innerHTML = i;
    return dom;
  });
  function _increase() {
    doms.forEach((dom) => {
      dom.innerHTML = Number(dom.innerHTML + 1);
    });
  }
  return _increase;
}
const increase = createIncrease();
const btn = document.querySelector("#btn")
btn.addEventListener("click",increase)

上面的代码当执行createIncrease方法时,会创建一个increase方法,同时也会创建一个拥有100000个元素的doms数组,当btn点击时执行increase方法。此时的doms肯定不能称之为垃圾,因为当btn点击时需要使用doms,这种判断相对来说比较简单

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

上面的代码sum是由nums的每一项相加得出的结果,至于nums还会不会再次被使用,根本没法判断
因此,从上面对垃圾的阐述以及例子可以看出,是否为垃圾是由开发者自己决定的。通俗点说,当一段代码执行完成后不会再被使用,那么可以称之为垃圾

垃圾回收

垃圾回收是JavaScript中一种自动内存管理机制,用于检测和释放不再使用的对象,以便回收内存。垃圾回收器会定期扫描内存中的对象,并标记那些仍然被引用的对象,然后清除未被引用的对象垃圾回收器。换句话说,垃圾回收只回收那些开发者自己都访问不了无法触达的内存空间

垃圾回收算法:JavaScript 垃圾回收器使用不同的算法来确定内存中的对象是否可以被回收。常见的算法包括标记-清除引用计数增量标记等。标记-清除是最常用的算法,它通过标记不再需要的对象,然后清除它们来回收内存

function createIncrease() {
  const doms = new Array(100000).fill(0).map((item, i) => {
    const dom = document.createElement("div");
    dom.innerHTML = i;
    return dom;
  });
  function _increase() {
    doms.forEach((dom) => {
      dom.innerHTML = Number(dom.innerHTML + 1);
    });
  }
  return _increase;
}
const increase = createIncrease();
const btn = document.querySelector("#btn");
const flag = true;
function handleClick() {
  flag = false;
  if (flag) {
    increase();
    btn.removeEventListener("click", handleClick);
    increase = null;
  }
}
btn.addEventListener("click", handleClick);
const nums = [1, 2, 3, 4, 5];
const sum = nums.reduce((total, num) => total + num, 0);
nums = null

比如上面的代码:

  • btn被点击时执行increase方法,当点击完成后,移除btn点击事件以及将increase设置为null,此时就会被垃圾回收器回收
  • nums重新赋值为null时,那么之前声明的nums的内容我们就无法再访问,因此就会被回收

内存泄漏

内存泄漏是指当程序中的对象不再被使用,但由于某些原因仍然被保留在内存中,导致内存占用不断增加,最终耗尽可用内存的现象。

const timer = setTimeout(() => {
  // todo
}, 100);
const nums = [1, 2, 3, 4, 5];
const sum = nums.reduce((total, num) => total + num, 0);

在这里插入图片描述
上面的代码我们声明了一个timer的定时器和一个nums的数组,timer没有清理,nums我们明确以后不会再使用,但是timernums会一直存在内存中,这样就容易引起内存泄漏

常见的内存泄漏情况包括:

  • 未及时清除的计时器如果你创建了一个计时器,但忘记在不再需要时清除它,它将一直存在并持有对相关对象的引用,导致内存泄漏
  • 未解绑的事件监听器如果你绑定了一个事件监听器,但在元素被销毁之前忘记解绑它,这个监听器将一直存在,并可能持有对相关对象的引用,导致内存泄漏
  • 循环引用如果对象之间存在循环引用(例如,对象 A 持有对象 B 的引用,同时对象 B 也持有对象 A 的引用),即使它们不再被使用,垃圾回收器也无法释放它们的内存,导致内存泄漏

当内存泄漏过大时会严重影响我们的代码,因为在开发时为避免内存泄漏,可以采取以下措施:

  • 尽量避免创建不必要的闭包,当不再需要时,显式地解除对闭包的引用
  • 将不再使用的变量设置为 null,以协助垃圾回收
  • 尽量避免循环引用,特别是在闭包中
  • 仔细管理计时器、事件监听器和其他可能持有对对象的引用的机制
  • 使用优化的数据结构和算法,避免不必要的内存占用

闭包

闭包是指一个函数能够访问和操作在其词法作用域以外定义的变量。闭包在JavaScript 中被广泛应用,它可以用于创建私有变量、实现模块化等。由于闭包会使函数引用外部变量,当闭包存在时,垃圾回收器可能无法释放被闭包引用的对象,导致内存泄漏

function createIncrease() {
   const doms = new Array(100000).fill(0).map((item, i) => {
     const dom = document.createElement("div");
     dom.innerHTML = i;
     return dom;
   });
   function _temp() {
     return doms;
   }
   function _increase() {}
   return _increase;
 }
 let increase;
 btn.addEventListener("click", () => {
   increase = createIncrease();
 });

上面的代码当点击事件被执行时对increase重新赋值,而createIncrease方法返回_increase方法,因此,此时doms是一块无法触达的内存空间,那么此时的这块无法触达的内存空间会被回收吗?
未点击之前
在这里插入图片描述
点击后
在这里插入图片描述
从上面的图片可以看出,doms并不会被回收,这是因为_temp_increase方法的闭包环境是一样的,它们共用一个词法环境,因此doms不会被回收


从上面的例子可以看出,闭包导致内存泄漏的原因有:

  • 持有了不再需要的函数引用,会导致函数关联的词法环境无法销毁,导致内存泄漏
  • 当多个函数共享词法环境时,会导致词法膨胀,从而使一些无法触达的内存空间无法回收,导致内存泄漏
  • 39
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值