写点什么

JavaScript 内存泄漏及如何避免

  • 2025-10-29
    山东
  • 本文字数:3077 字

    阅读完需:约 10 分钟

内存泄漏是前端开发中经常遇到的问题,它会影响应用程序的性能和可靠性。当变量或对象不再需要但仍占用内存空间时,就会发生内存泄漏。这会导致内存使用量增加、浏览器速度变慢,甚至导致应用程序崩溃。


在这篇文章中,我们将探讨 JavaScript 中内存泄漏的一些原因,例如被遗忘的事件监听器、闭包、大型数据结构以及 DOM 引用。我们还将学习如何使用 Chrome DevTools 检测和修复这些问题。

一、导致内存泄漏的原因

1.被遗忘的事件监听器

JavaScript 中最常见的内存泄漏原因之一是将事件监听器附加到元素上,但没有在不再需要时将其移除。例如:

const button = document.getElementById("button");button.addEventListener("click", function() {console.log("Button clicked");});
复制代码

此代码向按钮元素添加了一个点击监听函数。该监听函数会一直保留在内存中,直到使用 removeEventListener 显式移除它。如果按钮元素从 DOM 中移除但监听器未分离,则会产生内存泄漏。为了避免这个问题,我们应该在不再需要事件监听器时将其删除。

const button = document.getElementById("button");function handleClick() {console.log("Button clicked");} button.addEventListener("click", handleClick);// 在不再需要时...button.removeEventListener("click", handleClick);
复制代码

要使用 Chrome DevTools 检测因忘记事件监听器而导致的内存泄漏,我们可以使用“元素”选项卡下的“事件监听器”面板。此面板显示了附加到元素的所有事件监听器及其源代码位置。

我们还可以使用“内存”选项卡下的“内存分配时间线”工具。此工具会记录一段时间内内存分配的堆栈轨迹。我们可以将这些轨迹可视化,进行比较,并找出哪些内存被分配后没有被释放。

2.闭包

闭包是 JavaScript 的另一个强大功能,如果使用不当,可能会导致内存泄漏。闭包是指即使在外部函数返回后,仍可访问其外部作用域变量的函数。

function createCounter() {let count = 0;return function() {count++;return count;};} const counter = createCounter();console.log(counter()); // 1console.log(counter()); // 2console.log(counter()); // 3
复制代码

createCounter 返回的内部函数是一个闭包,即使在 createCounter 返回后,它仍然可以访问 count 变量。这允许我们创建在多次调用中保持不变的私有变量。

然而,这也意味着只要我们的代码中某处有对计数器函数的引用,计数变量就会保留在内存中。如果我们创建许多闭包来保存对不再需要的大型对象或数据结构的引用,则可能会造成内存泄漏。例如:

function createImage(url) {const image = new Image();image.src = url;return function() {return image;};} const images = [];for (let i = 0; i < 100; i++) {images.push(createImage("https://example.com/image" + i + ".jpg"));  }
复制代码

这段代码创建了一个包含 100 个闭包的数组,每个闭包都保存着一个图像对象的引用。即使我们不再使用这些闭包,这些图像对象仍然会占用内存空间,直到图像数组被清除或销毁。

为了避免这个问题,我们应该在不再需要保存大型对象或数据结构的变量或属性时,将其置为无效。

function createImage(url) {const image = new Image();image.src = url;return function() {return image;};} const images = [];for (let i = 0; i < 100; i++) {images.push(createImage("https://example.com/image" + i + ".jpg"));} // Later…images.length = 0; // Clear the array
复制代码

这样,我们删除了对闭包和图像对象的所有引用,从而允许它们被垃圾收集。

要使用 Chrome DevTools 检测闭包导致的内存泄漏,我们可以使用“内存”选项卡下的“堆快照”工具。此工具会在给定时间点拍摄堆内存快照,并以树状结构显示所有对象及其引用。我们还可以使用比较视图来比较两个快照,并查看它们之间添加或删除了哪些对象。

3.意外的全局变量

如果管理不当,意外的全局变量可能会导致内存泄漏。

function foo() {bar = 'hello';} foo();console.log(bar);
复制代码

在此示例中,在函数 bar 内部赋值, foo() 未使用 var 、 let 或进行声明 const 。这会意外创建一个全局变量,如果管理不当,可能会导致内存泄漏。

下面是一个使用 "use strict" 以防止意外全局变量的示例:

// Later…'use strict';function foo() {bar = 'hello'; // Uncaught ReferenceError: bar is not defined} foo();console.log(bar);
复制代码

在此示例中,将 "use strict" 指令添加到代码的开头,用于启用严格模式。当 foo() 调用时,由于 bar 未声明,因此会引发错误。这可以防止 bar 意外地成为全局变量并导致内存泄漏。

使用严格模式可以帮助在开发早期发现此类错误并防止它们在以后引起问题。

4.大型数据结构

大型数据结构(例如数组或对象)如果管理不当,也会占用大量内存。

const data = [];for (let i = 0; i < 1000000; i++) {data.push({ id: i, name: "Item " + i });}
复制代码

这段代码创建了一个包含一百万个对象的数组,每个对象都有一个 id 和一个 name 属性。即使我们不再使用它,这个数组也会占用大量的内存空间。

为了避免这个问题,我们应该在不再需要数组或对象属性时删除它们。例如:

const data = [];for (let i = 0; i < 1000000; i++) {  data.push({ id: i, name: "Item " + i }); }
// Later…data.length = 0; // Clear the array
复制代码

或者,我们可以使用弱映射或弱集合来存储对象的引用,而不是常规的映射或集合。弱映射和弱集合是不会阻止其元素被垃圾回收的集合。

const data = new WeakMap();for (let i = 0; i < 1000000; i++) {const item = { id: i, name: "Item " + i };data.set(item, item);  }
复制代码

这段代码创建了一个弱映射,用于存储一百万个对象的引用。如果代码中没有其他引用这些对象,它们将被自动回收。

要使用 Chrome DevTools 检测由大型数据结构引起的内存泄漏,我们可以使用与闭包相同的工具:堆快照和比较视图。

5.DOM 引用

DOM 引用是指使用 JavaScript 动态创建但未附加到文档树的元素。这些元素仍然可以被 JavaScript 代码访问,并且可以保存对其他元素或对象的引用。例如:

const div = document.createElement("div");div.innerHTML = "Hello";const p = div.querySelector("p");
复制代码

这段代码创建了一个 div 元素和一个 ap 元素,它们没有附加到文档树中。div 元素持有对 p 元素的引用,反之亦然。如果在不再需要这些元素时不将其删除,则可能会导致内存泄漏。

为了避免这个问题,我们应该将这些元素附加到文档树,或者在不再需要它们时将其从内存中删除。例如:

const div = document.createElement("div");div.innerHTML = "
Hello";const p = div.querySelector("p");
// Attach them to the document treedocument.body.appendChild(div);
// Or remove them from memorydiv.remove();p.remove();
复制代码

要使用 Chrome DevTools 检测由 DOM 节点溢出引起的内存泄漏,我们可以使用“内存”选项卡下的“堆快照”工具,并在摘要部分按“已分离”进行筛选。这将显示所有仍在内存中的已分离 DOM 节点。

二、结语

JavaScript 中的内存泄漏会对您的 Web 应用程序造成严重后果,例如性能不佳、内存使用率高以及浏览器崩溃等问题。在这篇博文中,我们了解了 JavaScript 中一些常见的内存泄漏原因,例如忘记事件监听器、闭包、大型数据结构以及 DOM 节点溢出。


希望这篇文章对您有所帮助。如果您有任何疑问或反馈,请在下方留言。


欢迎大家积极留言共建,期待与各位技术大咖的深入交流!

此外,欢迎大家下载我们的inBuilder低代码社区,可免费下载使用,加入我们,开启开发体验之旅!

用户头像

还未添加个人签名 2023-03-07 加入

塑造企业一体化研发新范式

评论

发布
暂无评论
JavaScript内存泄漏及如何避免_JavaScript_inBuilder低代码平台_InfoQ写作社区