搞定javascript内存泄漏

本文详细介绍了JavaScript的标记清除垃圾回收算法,探讨了内存泄漏的常见场景,如意外的全局变量、滥用闭包和未关闭的定时器,以及V8引擎的代际内存管理和垃圾回收器的工作原理。
摘要由CSDN通过智能技术生成

从2012年起,所有现代浏览器都使用了标记清除垃圾回收算法。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。

标记清除法

这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后继续找这些对象引用的对象.

在开始说标记清除法之前, 补说一个知识点, 就是栈和堆的概念, 看看下面的例子

`let obj = {};

let obj2 = {};



obj = null;

obj2 = null;







我们知道, 在javascript中, 除了八大基本类型(截至目前为止是八种), 剩下的都是对象类型, 在js中对象类型都是引用类型, 内容的实体是存在堆中的, 如下面我画的这张图所示:



![](https://img-blog.csdnimg.cn/img_convert/9278c5098e3967c41e4c0cf3b960a2e7.png)



当我们重新赋值obj, obj1的时候内存结构会变成这样



![](https://img-blog.csdnimg.cn/img_convert/248dce415b89e409c1b14faa9715afdb.png)



堆内存中的对象没有人引用他们, 但是他们还占用这内存, 这时候就需要我们的垃圾回收出场销毁他们了, V8引擎的垃圾回收机制不仅销毁掉堆内存中无人引用的空间, 还会对堆内存进行碎片整理, V8的GC(垃圾回收)工作如下面动图所示:



![](https://img-blog.csdnimg.cn/img_convert/e2eccb1231735ed6c12357a82907bbb7.gif)



V8的GC大致可以分为以下几个步骤



第一步,通过 GC Root 标记空间中活动对象和非活动对象。目前V8采用的是可访问性算法, 从GC Root出发遍历所有的对象, 通过GC Root可以遍历到的标记为`可访问的`, 称为活动对象,必须保留在内存中, GC Root无法遍历到的标记为`不可访问的`, 称为非活动对象, 这些不可访问的对象将会被GC清理掉.



第二步,回收非活动对象所占据的内存。其实就是在所有的标记完成之后,统一清理内存中所有被标记为可回收的对象。



第三步,做内存整理。一般来说,频繁回收对象后,内存中就会存在大量不连续空间,我们把这些不连续的内存空间称为`内存碎片`。



受`代际假说`的影响, V8引擎采用两个垃圾回收器, 主垃圾回收器–Major GC、副垃圾回收器–Minor GC(Scavenger), 你可能会问什么是`代际假说`:



> 第一个是大部分对象都是“朝生夕死”的,也就是说大部分对象在内存中存活的时间很短,比如函数内部声明的变量,或者块级作用域中的变量,当函数或者代码块执行结束时,作用域中定义的变量就会被销毁。因此这一类对象一经分配内存,很快就变得不可访问;

> 

> 第二个是不死的对象,会活得更久,比如全局的 window、DOM、Web API 等对象。



这两个回收器的作用如下:



*   主垃圾回收器 -Major GC,主要负责老生代的垃圾回收。

*   副垃圾回收器 -Minor GC (Scavenger),主要负责新生代的垃圾回收。



这里又会引出`新生代内存`和`老生代内存`的概念, 将堆内存分成两块区域



新生代的内存区域一般比较小, 但是垃圾回收得会比较频繁, 而老生代内存区的特点就是对象占用空间相对较大, 对象存活时间较长, 垃圾回收的频率也较低.



对了补一句, 垃圾回收时是会阻塞进程的.



[]( )2、常见的内存泄漏情况

----------------------------------------------------------------------



了解垃圾回收和内存泄漏是什么之后, 我们来看一些常见的内存泄漏场景:



**1\. 意外的全局变量**



前面我们提到有些对象是常驻内存的, 视为不死对象, 如window对象, 是浏览器中javascript的顶级对象, 它的存在贯穿这个javascript的生命周期, 如果我们不小心把庞大又用不上的变量挂到了window对象上, 将会造成内存泄漏, 当然这是一个很低级的错误.





`function test() {

// 漏掉了声明, 将会自动挂载到window对象下

str = '';

for (let i = 0; i < 100000; i++) {

str += 'xx';

}

return str;

}

// test执行结束后, str应该就没用了, 但是它常驻在了内存中

test();







**2\. 滥用闭包**



此处来顺便了解下闭包的概念, 闭包的概念网上很多说的都比较抽象, 我个人理解的闭包是:



`函数和其可操作的其他作用域变量的词法环境称为闭包`



当然了如果我是个杠精, 可能会说`with`语法是不是也算闭包呢? 按照定义with不是函数所以不属于闭包.



> MDN中对闭包的描述:

> 

> 一个函数和对其周围状态(**lexical environment,词法环境**)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是**闭包**(**closure**)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。



闭包是静态作用域(又称为词法作用域)语言独有的功能.





`function fn() {

const x = 'xx';

return function() {

return x;

}

}

const getX = fn();

console.log(getX());







如上面的例子就是一个闭包, fn执行结束之后内部变量并没有销毁, 我们在全局作用域下可以通过getX访问到fn函数作用域内的变量x.



如果不好理解可以看下上面提到的静态作用域(又称词法作用域), 看到静态作用域应该你会问, 有没有动态作用域呢, 但是是有的, bash脚本采用的就是动态作用域, javascript采用的是静态作用域, 闭包是静态作用域采用的功能.



看两个例子





`const x = 123;

function fn() {

console.log(x);

}

function fn2() {

const x = 345;

fn();

}

fn2(); // 结果是123







**静态作用域: fn中输出的x所处的作用域是在定义时确定的**



再看个动态作用域的例子





`# test.sh

value="global";

function fn() {

echo $value;

}

function fn2() {

local value="local";

fn;

}

fn2; # 结果是local







**动态作用域: fn中输出的x所处的作用域是在调用时确定的**



还有一点, 只有滥用闭包才能叫内存泄漏, 因为根据定义只有我们用不到了, 而且没有被销毁的内存才叫内存泄漏, 闭包中的值是我们用到的所以不应该叫做内存泄漏.





`function generateRandomMath() {

let x = Math.random();

return function() {

return x;

}

}







如上面这个例子就是滥用闭包了, 就该叫做内存泄漏



**3\. 被遗忘的定时器**



这个没什么好说的, 就是设置了定时器请记住在不要的时候使用`clearInterval`或者`clearTImeout`给他关一下.



**4\. DOM相关**



给某个dom节点绑定了很多事件, 使用过程中dom节点被移除但是被释放内存



我们来看个例子
 **自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

![img](https://img-blog.csdnimg.cn/img_convert/b9687ce7cc1819ec2d2b936a4fe023ae.jpeg)

![](https://img-blog.csdnimg.cn/img_convert/d0b8e9ff95de5d742cce83a3656a6b94.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!**

![](https://img-blog.csdnimg.cn/img_convert/ff295b6b6dd24f6ef9c41181bce26634.png)

**由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

**如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)**

![](https://img-blog.csdnimg.cn/img_convert/866b0e35a5bb4a4f152b782473d28de6.jpeg)
### 最后

javascript是前端必要掌握的真正算得上是编程语言的语言,学会灵活运用javascript,将对以后学习工作有非常大的帮助。掌握它最重要的首先是学习好基础知识,而后通过不断的实战来提升我们的编程技巧和逻辑思维。这一块学习是持续的,直到我们真正掌握它并且能够灵活运用它。如果最开始学习一两遍之后,发现暂时没有提升的空间,我们可以暂时放一放。继续下面的学习,javascript贯穿我们前端工作中,在之后的学习实现里也会遇到和锻炼到。真正学习起来并不难理解,关键是灵活运用。

[**资料领取方式:点击这里免费领取前端全套学习资料**](https://bbs.csdn.net/topics/618191877)

![css源码pdf](https://img-blog.csdnimg.cn/img_convert/f1c91d0fa18c6af054ffba803cd4a1d4.webp?x-oss-process=image/format,png)

![JavaScript知识点](https://img-blog.csdnimg.cn/img_convert/54bbb6e2c10d68cf7bfe860119adf276.webp?x-oss-process=image/format,png)
avascript,将对以后学习工作有非常大的帮助。掌握它最重要的首先是学习好基础知识,而后通过不断的实战来提升我们的编程技巧和逻辑思维。这一块学习是持续的,直到我们真正掌握它并且能够灵活运用它。如果最开始学习一两遍之后,发现暂时没有提升的空间,我们可以暂时放一放。继续下面的学习,javascript贯穿我们前端工作中,在之后的学习实现里也会遇到和锻炼到。真正学习起来并不难理解,关键是灵活运用。

[**资料领取方式:点击这里免费领取前端全套学习资料**](https://bbs.csdn.net/topics/618191877)

[外链图片转存中...(img-zHU1sjHx-1713844300092)]

[外链图片转存中...(img-eyImJdO2-1713844300093)]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值