MST

星途 面试题库

面试题:JavaScript性能优化之内存管理与垃圾回收

在JavaScript中,解释垃圾回收机制是如何工作的,列举至少三种可能导致内存泄漏的场景,并说明如何避免这些内存泄漏。
40.5万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

垃圾回收机制工作原理

JavaScript 采用标记清除(Mark and Sweep)算法来实现垃圾回收。

  1. 标记阶段:垃圾回收器从根对象(全局对象window在浏览器环境中,global在 Node.js 环境中)出发,遍历所有可访问的对象,并为它们打上标记,表示这些对象是活动的(正在使用)。
  2. 清除阶段:垃圾回收器再次遍历堆内存,回收所有没有被标记的对象,释放它们所占用的内存空间。

可能导致内存泄漏的场景及避免方法

  1. 意外的全局变量
    • 场景:在函数内部不使用varletconst声明变量,该变量会自动成为全局变量。例如:
function foo() {
    bar = "I am a global variable"; // 未声明,成为全局变量
}
- **避免方法**:始终使用`var`、`let`或`const`声明变量,严格模式(`'use strict';`)也会禁止这种意外创建全局变量的行为。

2. 事件监听未移除 - 场景:给 DOM 元素添加事件监听器后,如果该 DOM 元素从 DOM 树中移除,但事件监听器未被移除,就会导致内存泄漏。例如:

<div id="myDiv"></div>
<script>
    const myDiv = document.getElementById('myDiv');
    myDiv.addEventListener('click', function () {
        console.log('Clicked');
    });
    // 假设这里移除了myDiv
    document.body.removeChild(myDiv);
    // 但事件监听器仍然存在,导致内存泄漏
</script>
- **避免方法**:在移除 DOM 元素之前,先移除事件监听器。可以给事件监听器函数赋值给一个变量,然后在移除 DOM 元素前使用`removeEventListener`移除监听器。例如:
<div id="myDiv"></div>
<script>
    const myDiv = document.getElementById('myDiv');
    const clickHandler = function () {
        console.log('Clicked');
    };
    myDiv.addEventListener('click', clickHandler);
    // 移除DOM元素前
    myDiv.removeEventListener('click', clickHandler);
    document.body.removeChild(myDiv);
</script>
  1. 闭包导致的内存泄漏
    • 场景:如果闭包中引用了较大的对象,而闭包又长时间存在,会导致该对象无法被垃圾回收。例如:
function outer() {
    const largeObject = { /* 包含大量数据的对象 */ };
    return function inner() {
        // inner函数形成闭包,引用了outer函数作用域中的largeObject
        return largeObject.someProperty;
    };
}
const closure = outer();
// 即使outer函数执行完毕,largeObject也不会被回收,因为closure闭包引用了它
- **避免方法**:尽量减少闭包中对不必要对象的引用。如果闭包不需要访问某个对象,可以在适当的时候将其设为`null`,让垃圾回收器能够回收该对象。例如:
function outer() {
    let largeObject = { /* 包含大量数据的对象 */ };
    return function inner() {
        const result = largeObject.someProperty;
        largeObject = null; // 切断对largeObject的引用
        return result;
    };
}
const closure = outer();
  1. 循环引用
    • 场景:两个对象相互引用,形成循环引用,导致垃圾回收器无法回收它们。例如:
function createCircularReference() {
    const obj1 = {};
    const obj2 = {};
    obj1.prop = obj2;
    obj2.prop = obj1;
    return [obj1, obj2];
}
const circularRefs = createCircularReference();
// 即使circularRefs不再被使用,obj1和obj2也不会被回收,因为它们相互引用
- **避免方法**:在适当的时候打破循环引用。例如,在不再需要这些对象时,手动将循环引用的属性设为`null`。例如:
function createCircularReference() {
    const obj1 = {};
    const obj2 = {};
    obj1.prop = obj2;
    obj2.prop = obj1;
    return [obj1, obj2];
}
const circularRefs = createCircularReference();
// 不再需要circularRefs时
circularRefs[0].prop = null;
circularRefs[1].prop = null;