从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)]