深入了解 JavaScript 内存泄漏

作者:京东零售 谢天
在任何语言开发的过程中,对于内存的管理都非常重要,JAVAscript 也不例外 。
然而在前端浏览器中,用户一般不会在一个页面停留很久,即使有一点内存泄漏,重新加载页面内存也会跟着释放 。而且浏览器也有自己的自动回收内存的机制,所以前端并没有特别关注内存泄漏的问题 。
但是如果我们对内存泄漏没有什么概念,有时候还是有可能因为内存泄漏,导致页面卡顿 。了解内存泄漏,如何避免内存泄漏,都是不可缺少的 。
什么是内存

在硬件级别上,计算机内存由大量触发器组成 。每个触发器包含几个晶体管,能够存储一个位 。单个触发器可以通过唯一标识符寻址,因此我们可以读取和覆盖它们 。因此,从概念上讲,我们可以把我们的整个计算机内存看作是一个巨大的位数组,我们可以读和写 。
 
这是内存的底层概念,JavaScript 作为一个高级语言,不需要通过二进制进行内存的读写,而是相关的 JavaScript 引擎做了这部分的工作 。
内存的生命周期
内存也会有生命周期,不管什么程序语言,一般可以按照顺序分为三个周期:
 
  • 分配期:分配所需要的内存
  • 使用期:使用分配的内存进行读写
  • 释放期:不需要时将其释放和归还
 
内存分配 -> 内存使用 -> 内存释放
什么是内存泄漏
在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存 。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费 。
 
如果内存不需要时,没有经过生命周期的的释放期,那么就存在内存泄漏 。
内存泄漏的简单理解:无用的内存还在占用,得不到释放和归还 。比较严重时,无用的内存会持续递增,从而导致整个系统的卡顿,甚至崩溃 。
JavaScript 内存管理机制
像 C 语言这样的底层语言一般都有底层的内存管理接口,但是 JavaScript 是在创建变量时自动进行了内存分配,并且在不使用时自动释放,释放的过程称为“垃圾回收” 。然而就是因为自动回收的机制,让我们错误的感觉开发者不必关心内存的管理 。
JavaScript 内存管理机制和内存的生命周期是一致的,首先需要分配内存,然后使用内存,最后释放内存 。绝大多数情况下不需要手动释放内存,只需要关注对内存的使用(变量、函数、对象等) 。
内存分配
JavaScript 定义变量就会自动分配内存,我们只需要了解 JavaScript 的内存是自动分配的就可以了 。
let num = 1; const str = "名字"; const obj = { a: 1, b: 2 } const arr = [1, 2, 3]; function func (arg) { ... } 内存使用
使用值的过程实际上是对分配的内存进行读写的操作,读取和写入的操作可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数 。
// 继续上部分 // 写入内存 num = 2; // 读取内存,写入内存 func(num); 内存回收
垃圾回收被称为 GC(Garbage Collection)
内存泄漏一般都是发生在这一步,JavaScript 的内存回收机制虽然可以回收绝大部分的垃圾内存,但是还是存在回收不了的情况,如果存在这些情况,需要我们自己手动清理内存 。
以前一些老版本的浏览器的 JavaScript 回收机制没有那么完善,经常出现一些 bug 的内存泄漏,不过现在的浏览器一般都没有这个问题了 。
这里了解下现在 JavaScript 的垃圾内存的两种回收方式,熟悉一下这两种算法可以帮助我们理解一些内存泄漏的场景 。
引用计数
这是最初级的垃圾收集算法 。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它” 。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收 。
// “对象”分配给 obj1 var obj1 = { a: 1, b: 2 } // obj2 引用“对象” var obj2 = obj1; // “对象”的原始引用 obj1 被 obj2 替换 obj1 = 1;
当前执行环境中,“对象”内存还没有被回收,需要手动释放“对象”的内存(在没有离开当前执行环境的前提下)
obj2 = null; // 或者 obj2 = 1; // 只要替换“对象”就可以了
这样引用的“对象”内存就被回收了 。


推荐阅读