面试官最爱问的 JS 内存泄漏问题!你中招了吗?

开发 前端
说起内存管理,可能是很多同学既熟悉、又陌生的一个领域。熟悉是因为我们经常会听到这个词,陌生是因为好像我们在开发时又很少去关注它。

Hello,大家好,我是 Sunday。

说起内存管理,可能是很多同学既熟悉、又陌生的一个领域。熟悉是因为我们经常会听到这个词,陌生是因为好像我们在开发时又很少去关注它。

不过,在目前的前端面试中,内存管理的问题被问的又比较频繁。所以,咱们今天就来看看 JS 内存管理的那些事...

JavaScript 如何管理内存

JavaScript 与许多现代编程语言一样,使用自动内存管理,即:JS 会自动完成垃圾回收。这也意味着,我们在程序运行时无需手动分配和释放内存(这也是很多同学很少关注它的原因)。

自动内存管理(垃圾回收)主要通过 标记-清除 法完成,大致分为两个阶段:

  1. 标记阶段:垃圾收集器从根对象开始,标记所有可到达或可访问的对象。
  2. 清除阶段:任何未标记(即可无法访问)的对象都被视为“垃圾”,并且其内存将被释放。

但是,自动的内存管理毕竟无法解决所有场景下的内存问题,所以在某些特殊情况下就会导致出现 内存泄漏 的问题。

而这个 内存泄漏 就是我们在面试中聊到内存管理时的,重点描述 内容!

关于【内存泄漏】

垃圾回收经常会伴随着内存泄漏的概念。不过我们需要知道的是 内存泄漏内存溢出 是不同的。


PS 内存溢出指的是:程序运行过程中需要使用的内存大于系统能够提供的内存。即:系统内存不足。

所谓的内存泄漏指的是:当一块内存(通常是变量)未被释放(垃圾回收),同时我们也无法访问到它时。大家可以简单理解为:一个变量,我们访问不到它了,但是它占用的内容还没有被回收。那么此时就会出现内存泄漏的问题:

1. 未清理的定时器或回调

  • 场景:使用 setIntervalsetTimeout,但在组件销毁或不再需要时没有清理。
  • 结果:定时器依然存在,引用被保留,导致无法释放内存。
  • 示例:
function startTimer() {
   setInterval(() => {
       console.log('...');
   }, 1000);
}
// 如果调用此函数后不清理定时器,则会造成内存泄漏
  • 修复:
const timer = setInterval(() => {
    console.log('Timer running...');
}, 1000);
clearInterval(timer); // 组件销毁时清理

2. DOM 引用未释放

  • 场景:保留对已移除 DOM 元素的引用。
  • 结果:虽然 DOM 节点从页面上被删除,但 JavaScript 中仍有对它的引用,导致内存泄漏。
  • 示例:
let element = document.getElementById('leak');
document.body.removeChild(element);
// 仍然引用 element,导致内存无法释放
  • 修复:
element = null; // 手动清除引用

3. 闭包中的多余引用

  • 场景:闭包内的变量保留了对外部对象的引用,导致对象无法被垃圾回收。
  • 结果:长时间的引用链阻止了内存的释放。
  • 示例:
function createClosure() {
    const largeObject = new Array(1000).fill('leak');
    return function() {
        console.log(largeObject);
    };
}
const closure = createClosure();
// 即使 closure 不再使用,largeObject 依然存在
  • 修复:
function createClosure() {
    let largeObject = new Array(1000).fill('leak');
    return function() {
        console.log(largeObject);
        largeObject = null; // 主动清除引用
    };
}

4. 全局变量或未声明变量

  • 场景:未使用 var, let, 或 const 定义变量,导致变量挂载在全局作用域。
  • 结果:全局变量生命周期与页面一致,无法被垃圾回收。
  • 示例:
function leak() {
    leakedVariable = 'I am a leak'; // 隐式全局变量
}
leak();
  • 修复:
function noLeak() {
    let localVariable = 'I am safe'; // 使用局部作用域变量
}

5. 事件监听器未清理

  • 场景:在 DOM 元素上绑定了事件监听器,但没有在元素销毁时移除。
  • 结果:监听器仍然存在,阻止 DOM 元素被回收。
  • 示例:
const button = document.getElementById('myButton');
button.addEventListener('click', () => console.log('点击!'));
document.body.removeChild(button);
// 内存泄漏:button 和监听器仍被引用
  • 修复:
const button = document.getElementById('myButton');
const handler = () => console.log('点击!');
button.addEventListener('click', handler);
button.removeEventListener('click', handler); // 组件销毁时移除
document.body.removeChild(button);

6. 忘记清理 Map/Set 中的键或值

  • 场景:MapSet 中的键/值对象未手动移除。
  • 结果:即使这些对象不再需要,也会因其被引用而无法回收。
  • 示例:
const map = new Map();
const obj = { key: 'value' };
map.set(obj, 'data');
// 即使 obj 不再使用,map 仍然引用它
  • 修复:
map.delete(obj); // 手动清理不需要的键

7. 闭环引用

  • 场景:两个对象互相引用,导致垃圾回收无法判断它们是否可释放。
  • 结果:循环引用造成内存泄漏。
  • 示例:
function CircularReference() {
    const obj1 = {};
    const obj2 = {};
    obj1.ref = obj2;
    obj2.ref = obj1;
}
  • 修复:
// 如果可能,避免互相引用,或通过 WeakMap/WeakSet 管理对象
const obj1 = new WeakMap();
const obj2 = {};
obj1.set(obj2, 'value');


责任编辑:武晓燕 来源: 程序员Sunday
相关推荐

2023-09-26 00:37:38

Spring微服务框架

2024-07-10 08:10:10

2018-01-19 10:43:06

Java面试官volatile关键字

2021-01-07 08:12:08

自学编程学习

2021-03-17 08:39:24

作用域作用域链JavaScript

2018-10-25 10:36:50

物联网误区IOT

2018-10-22 17:52:28

GitHub代码开发者

2021-08-16 14:00:27

手机科技功能

2018-08-14 11:02:55

机器学习项目失败

2021-03-23 10:17:45

5G手机网络

2010-08-23 15:06:52

发问

2013-09-30 09:18:39

2015-09-16 11:53:21

2015-08-13 10:29:12

面试面试官

2021-11-08 09:18:01

CAS面试场景

2024-04-26 00:15:51

2021-12-25 22:31:10

MarkWord面试synchronize

2022-11-04 08:47:52

底层算法数据

2022-04-01 17:32:00

Windows3.1元宇宙模式黑客

2021-12-16 18:38:13

面试Synchronize
点赞
收藏

51CTO技术栈公众号