JavaScript 的垃圾回收

147 阅读3分钟

这是我参与更文挑战的第 27 天,活动详情查看:更文挑战

与 C 和 C++ 不同,JavaScript 的垃圾回收由执行环境自动处理,通过自动内存管理实现内存分配和闲置资源回收,降低开发者的负担。

垃圾回收程序会每隔一段时间运行一次,回收的基本思路是:确定哪个变量不会再使用,就释放它的内存。

那么如何确定变量是否不再使用呢?浏览器会对变量进行标记,跟踪记录变量的使用情况。主要的标记策略有两种:「标记清理」和「引用计数」。

标记清理

标记清理是比较常用的垃圾回收策略。

当变量进入上下文时,会被加上存在于上下文中的标记。当变量离开上下文时,也会被加上离开上下文的标记。该标记我们称为标记 A。

垃圾回收程序运行的时候,会将内存中所有的变量做一个标记 B,然后将所有在上下文中的变量以及被在上下文中的变量的引用变量的标记 B 去掉,那么剩下存在标记 B 的变量就是待删除的了,因为任何在上下文中的变量都无法访问到它们。最后做一次内存清理,销毁存在标记 B 的所有值并收回它们的内存。

引用计数

引用计数比较不常见,其实现思路是对每个值都记录它被引用的次数。

声明变量并给它赋一个引用值时,该值的引用数为 1,如果同一个值又被赋给另一个变量,引用数加 1.如果引用该值的变量被其它值覆盖了,那该值的引用数减 1。当一个值的引用数为 0 时,就会在下一次垃圾回收程序运行时进行回收,释放内存。

引用计数无法解决循环引用的问题。例如:

function problem() {
    let objectA = {};
    let objectB = {
            otherObject: objectA
        };
    objectA.otherObject = objectB;
}

上述代码中,两个对象的属性相互引用。如果采用标记清理策略,在problem函数运行结束后,这两个对象都不在上下文中,会被垃圾回收程序释放内存。而在引用计数策略下,由于它们互相引用,所以引用数永远不会变 0,即无法被垃圾回收程序清理。

优化内存占用

虽然 JavaScript 自动帮我们做了内存管理,但我们也可以进行一些人为的优化。使内存占用量保持在一个较小的值能够让页面性能更好。

优化内存的最佳手段是保证在执行代码时只保存必要的数据,如果数据不再需要,将它设置为 null,从而释放其引用,让它在下次垃圾回收时会被回收,特别是全局变量和全局对象的属性。

使用 constlet 声明变量。这两个关键字以块(而非函数)作为作用域,相比于使用 var,所声明的变量在函数作用域还没有终止,但是块作用域终止的情况下,就可以被垃圾回收。

参考资料

JavaScript高级程序设计(第4版)