您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > javascript

JavaScript 内存管理:如何避免常见的内存泄漏并提高性能

时间:2023-02-20 15:56:37  来源:  作者:佚名
作为 Web 开发人员,您知道您编写的每一行代码都会对应用程序的性能产生影响吗?谈到 JAVAScript,最需要关注的领域之一就是内存管理。

介绍

作为 Web 开发人员,您知道您编写的每一行代码都会对应用程序的性能产生影响吗?谈到 JavaScript,最需要关注的领域之一就是内存管理。

想一想,每次用户与您的网站交互时,他们都会创建新的对象、变量和函数。如果您不小心,这些对象可能会堆积起来,阻塞浏览器的内存并降低整个用户体验。这就像信息高速公路上的交通堵塞,一个令人沮丧的瓶颈,可以让用户望而却步。

但它不一定是这样的。凭借正确的知识和技术,您可以控制您的 JavaScript 内存并确保您的应用程序平稳高效地运行。

在今天的文章中,我们将探讨 JavaScript 内存管理的来龙去脉,包括内存泄漏的常见原因以及避免它们的策略。无论您是专业的还是新手JavaScript开发人员,您都会对如何编写精简、平均和快速的代码有更深入的了解。

了解 JavaScript 内存管理

1.垃圾收集器

JavaScript 引擎使用垃圾收集器来释放不再使用的内存。垃圾收集器的工作是识别并删除应用程序不再使用的对象。它通过持续监控代码中的对象和变量,并跟踪哪些对象和变量仍在被引用来实现这一点。一旦一个对象不再被使用,垃圾收集器将其标记为删除并释放它正在使用的内存。

垃圾收集器使用一种称为“标记和清除”的技术来管理内存。它首先标记所有仍在使用的对象,然后“扫过”堆并删除所有未标记的对象。这个过程会定期进行,并且在堆内存不足时进行,以确保应用程序的内存使用始终尽可能高效。

2. 堆栈与堆

当谈到 JavaScript 中的内存时,有两个主要参与者:堆栈和堆。

堆栈用于存储仅在函数执行期间需要的数据。它快速高效,但容量有限。当一个函数被调用时,JavaScript 引擎将函数的变量和参数压入堆栈,当函数返回时,它再次将它们弹出。堆栈用于快速访问和快速内存管理。

另一方面,堆用于存储应用程序整个生命周期所需的数据。它比栈慢一点,组织性差一点,但容量大得多。堆用于存储对象、数组和其他需要多次访问的复杂数据结构。

内存泄漏的常见原因

您很清楚内存泄漏可能是一个偷偷摸摸的敌人,它会潜入您的应用程序并导致性能问题。通过了解内存泄漏的常见原因,您可以用战胜它们所需的知识武装自己。

1. 循环引用

内存泄漏的最常见原因之一是循环引用。当两个或多个对象相互引用时,就会发生这种情况,从而形成垃圾收集器无法破坏的循环。这可能会导致对象在不再需要后很长时间内仍保留在内存中。

这是示例:

 
let object1 = {};
let object2 = {};


// create a circular reference between object1 and object2
object1.next = object2;
object2.prev = object1;


// do something with object1 and object2
// ...


// set object1 and object2 to null to break the circular reference
object1 = null;
object2 = null;

在此示例中,我们创建了两个对象,object1 和 object2,并通过向它们添加 next 和 prev 属性在它们之间创建循环引用。

然后,我们将 object1 和 object2 设置为 null 以打破循环引用,但由于垃圾收集器无法打破循环引用,因此对象将在不再需要后很长时间内保留在内存中,从而导致内存泄漏。

为了避免这种类型的内存泄漏,我们可以使用一种称为“手动内存管理”的技术,通过使用 JavaScript 的 delete 关键字来删除创建循环引用的属性。

 
delete object1.next;
delete object2.prev;

避免此类内存泄漏的另一种方法是使用 WeakMap 和 WeakSet,它们允许您创建对对象和变量的弱引用,您可以在本文后面阅读有关此选项的更多信息。

2.事件监听器

内存泄漏的另一个常见原因是事件监听器,当您将事件侦听器附加到元素时,它会创建对侦听器函数的引用,该函数可以防止垃圾收集器释放元素使用的内存。如果在不再需要该元素时未删除侦听器函数,这可能会导致内存泄漏。

我们一起来看一个例子:

 
let button = document.getElementById("my-button");


// attach an event listener to the button
button.addEventListener("click", function() {
  console.log("Button was clicked!");
});


// do something with the button
// ...


// remove the button from the DOM
button.parentNode.removeChild(button);

在此示例中,我们将事件侦听器附加到按钮元素,然后从 DOM 中删除该按钮。即使按钮元素不再存在于文档中,事件侦听器仍附加到它,这会创建对侦听器函数的引用,以防止垃圾收集器释放该元素使用的内存。如果在不再需要该元素时未删除侦听器函数,这可能会导致内存泄漏。

为避免此类内存泄漏,在不再需要该元素时删除事件侦听器很重要:

 
button.removeEventListener("click", function() {
  console.log("Button was clicked!");
});

另一种方法是使用 EventTarget.removeAllListeners() 方法删除所有已添加到特定事件目标的事件侦听器。

 
button.removeAllListeners();

3.全局变量

内存泄漏的第三个常见原因是全局变量。当您创建全局变量时,可以从代码中的任何位置访问它,这使得很难确定何时不再需要它。这可能会导致变量在不再需要后很长时间仍保留在内存中。这是一个例子:

 
// create a global variable
let myData = {
  largeArray: new Array(1000000).fill("some data"),
  id: 1
};


// do something with myData
// ...


// set myData to null to break the reference
myData = null;

在这个例子中,我们创建了一个全局变量 myData 并在其中存储了大量数据。

然后我们将 myData 设置为 null 以中断引用,但是由于该变量是全局变量,它仍然可以从您的代码中的任何位置访问,并且很难确定何时不再需要它,这会导致该变量在内存中保留很长时间 在不再需要它之后,导致内存泄漏。

为避免这种类型的内存泄漏,您可以使用“函数作用域”技术。它涉及创建一个函数并在该函数内声明变量,以便它们只能在函数范围内访问。这样,当不再需要该函数时,变量会自动被垃圾回收。

 
function myFunction() {
  let myData = {
    largeArray: new Array(1000000).fill("some data"),
    id: 1
  };


  // do something with myData
  // ...
}
myFunction();

另一种方法是使用 JavaScript 的 let 和 const 代替 var,这允许您创建块范围的变量。用 let 和 const 声明的变量只能在定义它们的块内访问,并且当它们超出范围时将被自动垃圾收集。

 
{
  let myData = {
    largeArray: new Array(1000000).fill("some data"),
    id: 1
  };


  // do something with myData
  // ...
}

手动内存管理的最佳实践

JavaScript 提供了内存管理工具和技术,可以帮助您控制应用程序的内存使用情况。

1.使用弱引用

JavaScript 中最强大的内存管理工具之一是 WeakMap 和 WeakSet。这些是特殊的数据结构,允许您创建对对象和变量的弱引用。

弱引用不同于常规引用,因为它们不会阻止垃圾收集器释放对象使用的内存。这使它们成为避免循环引用引起的内存泄漏的好工具。这是一个例子:

 
let object1 = {};
let object2 = {};


// create a WeakMap
let weakMap = new WeakMap();


// create a circular reference by adding object1 to the WeakMap
// and then adding the WeakMap to object1
weakMap.set(object1, "some data");
object1.weakMap = weakMap;


// create a WeakSet and add object2 to it
let weakSet = new WeakSet();
weakSet.add(object2);


// in this case, the garbage collector will be able to free up the memory
// used by object1 and object2, since the references to them are weak

在这个例子中,我们创建了两个对象,object1 和 object2,并通过将它们分别添加到 WeakMap 和 WeakSet 来创建它们之间的循环引用。

因为对这些对象的引用很弱,垃圾收集器将能够释放它们使用的内存,即使它们仍在被引用。这有助于防止循环引用引起的内存泄漏。

2. 使用垃圾收集器 API

另一种内存管理技术是使用垃圾收集器 API,它允许您手动触发垃圾收集并获取有关堆当前状态的信息。

这对于调试内存泄漏和性能问题很有用。

以下是一个例子:

 
let object1 = {};
let object2 = {};


// create a circular reference between object1 and object2
object1.next = object2;
object2.prev = object1;


// manually trigger garbage collection
gc();

在此示例中,我们创建了两个对象,object1 和 object2,并通过向它们添加 next 和 prev 属性在它们之间创建循环引用。然后,我们使用 gc() 函数手动触发垃圾收集,这将释放对象使用的内存,即使它们仍在被引用。

请务必注意,并非所有 JavaScript 引擎都支持 gc() 函数,其行为也可能因引擎而异。还需要注意的是,手动触发垃圾回收会对性能产生影响,因此,建议谨慎使用,仅在必要时使用。

除了 gc() 函数,JavaScript 还为一些 JavaScript 引擎提供了 global.gc() 和 global.gc() 函数,也为一些浏览器引擎提供了 performance.gc() ,可以用来检查 堆的当前状态并测量垃圾收集过程的性能。

3. 使用堆快照和分析器

JavaScript 还提供堆快照和分析器,可以帮助您了解您的应用程序如何使用内存。堆快照允许您拍摄堆当前状态的快照并对其进行分析以查看哪些对象使用的内存最多。

下面是一个示例,说明如何使用堆快照来识别应用程序中的内存泄漏:

 
// Start a heap snapshot
let snapshot1 = performance.heapSnapshot();


// Do some actions that might cause memory leaks
for (let i = 0; i < 100000; i++) {
  myArray.push({
    largeData: new Array(1000000).fill("some data"), 
    id: i
  });
}


// Take another heap snapshot
let snapshot2 = performance.heapSnapshot();


// Compare the two snapshots to see which objects were created
let diff = snapshot2.compare(snapshot1);


// Analyze the diff to see which objects are using the most memory
diff.forEach(function(item) {
  if (item.size > 1000000) {
    console.log(item.name);
  }
});

在此示例中,我们在执行将大数据推送到数组的循环之前和之后拍摄两个堆快照,然后,比较这两个快照以识别在循环期间创建的对象。

接着,我们可以分析差异以查看哪些对象使用了最多的内存,这可以帮助我们识别由大数据引起的内存泄漏。

分析器允许您跟踪应用程序的性能并识别内存使用率高的区域:

 
let profiler = new Profiler();


profiler.start();


// do some actions that might cause memory leaks
for (let i = 0; i < 100000; i++) {
  myArray.push({
    largeData: new Array(1000000).fill("some data"), 
    id: i
  });
}


profiler.stop();


let report = profiler.report();


// analyze the report to identify areas where memory usage is high
for (let func of report) {
  if (func.memory > 1000000) {
    console.log(func.name);
  }
}

在这个例子中,我们使用 JavaScript 分析器来开始和停止跟踪我们应用程序的性能。该报告将显示有关已调用函数的信息以及每个函数的内存使用情况。

并非所有 JavaScript 引擎和浏览器都支持堆快照和分析器,因此在您的应用程序中使用它们之前检查兼容性很重要。

结论

我们已经介绍了 JavaScript 内存管理的基础知识,包括垃圾回收过程、不同类型的内存以及 JavaScript 中可用的内存管理工具和技术。我们还讨论了内存泄漏的常见原因,并提供了如何避免它们的示例。

通过花时间了解和实施这些内存管理最佳实践,您将能够创建消除内存泄漏可能性的应用程序。



Tags:JavaScript   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
17 个你需要知道的 JavaScript 优化技巧
你可能一直在使用JavaScript搞开发,但很多时候你可能对它提供的最新功能并不感冒,尽管这些功能在无需编写额外代码的情况下就可以解决你的问题。作为前端开发人员,我们必须了解...【详细内容】
2024-04-03  Search: JavaScript  点击:(5)  评论:(0)  加入收藏
你不可不知的 15 个 JavaScript 小贴士
在掌握如何编写JavaScript代码之后,那么就进阶到实践&mdash;&mdash;如何真正地解决问题。我们需要更改JS代码使其更简单、更易于阅读,因为这样的程序更易于团队成员之间紧密协...【详细内容】
2024-03-21  Search: JavaScript  点击:(27)  评论:(0)  加入收藏
构建一个通用灵活的JavaScript插件系统?看完你也会!
在软件开发中,插件系统为应用程序提供了巨大的灵活性和可扩展性。它们允许开发者在不修改核心代码的情况下扩展和定制应用程序的功能。本文将详细介绍如何构建一个灵活的Java...【详细内容】
2024-03-20  Search: JavaScript  点击:(20)  评论:(0)  加入收藏
对JavaScript代码压缩有什么好处?
对JavaScript代码进行压缩主要带来以下好处: 减小文件大小:通过移除代码中的空白符、换行符、注释,以及缩短变量名等方式,可以显著减小JavaScript文件的大小。这有助于减少网页...【详细内容】
2024-03-13  Search: JavaScript  点击:(2)  评论:(0)  加入收藏
跨端轻量JavaScript引擎的实现与探索
一、JavaScript 1.JavaScript语言JavaScript是ECMAScript的实现,由ECMA 39(欧洲计算机制造商协会39号技术委员会)负责制定ECMAScript标准。ECMAScript发展史: 2.JavaScript...【详细内容】
2024-03-12  Search: JavaScript  点击:(2)  评论:(0)  加入收藏
面向AI工程的五大JavaScript工具
令许多人惊讶的是,一向在Web开发领域中大放异彩的JavaScript在开发使用大语言模型(LLM)的应用程序方面同样大有价值。我们在本文中将介绍面向AI工程的五大工具,并为希望将LLM...【详细内容】
2024-02-06  Search: JavaScript  点击:(53)  评论:(0)  加入收藏
18个JavaScript技巧:编写简洁高效的代码
本文翻译自 18 JavaScript Tips : You Should Know for Clean and Efficient Code,作者:Shefali, 略有删改。在这篇文章中,我将分享18个JavaScript技巧,以及一些你应该知道的示例...【详细内容】
2024-01-30  Search: JavaScript  点击:(68)  评论:(0)  加入收藏
使用 JavaScript 清理我的 200GB iCloud,有了一个意外发现!
本文作者在综合成本因素之下,决定用 Java 脚本来清理一下自己的 iCloud,结果却有了一个意外发现,即在 iCloud 中上传同一个视频和删除此视频之后,iCloud 的空间并不一致,这到底是...【详细内容】
2024-01-11  Search: JavaScript  点击:(99)  评论:(0)  加入收藏
JavaScript前端框架2024年展望
Angular、Next.js、React和Solid的维护者和创作者们展望2024年,分享了他们计划中的改进。译自2024 Predictions by JavaScript Frontend Framework Maintainers,作者 Loraine...【详细内容】
2024-01-05  Search: JavaScript  点击:(91)  评论:(0)  加入收藏
JavaScript开发者转向Rust的原因?
JavaScript开发者转向Rust的原因可能有很多,这里列出一些可能的原因: 性能: Rust是一种编译型语言,其性能通常优于JavaScript等解释型语言。对于需要处理大量数据或需要高并发的...【详细内容】
2024-01-04  Search: JavaScript  点击:(99)  评论:(0)  加入收藏
▌简易百科推荐
17 个你需要知道的 JavaScript 优化技巧
你可能一直在使用JavaScript搞开发,但很多时候你可能对它提供的最新功能并不感冒,尽管这些功能在无需编写额外代码的情况下就可以解决你的问题。作为前端开发人员,我们必须了解...【详细内容】
2024-04-03  前端新世界  微信公众号  Tags:JavaScript   点击:(5)  评论:(0)  加入收藏
你不可不知的 15 个 JavaScript 小贴士
在掌握如何编写JavaScript代码之后,那么就进阶到实践&mdash;&mdash;如何真正地解决问题。我们需要更改JS代码使其更简单、更易于阅读,因为这样的程序更易于团队成员之间紧密协...【详细内容】
2024-03-21  前端新世界  微信公众号  Tags:JavaScript   点击:(27)  评论:(0)  加入收藏
又出新JS运行时了!JS运行时大盘点
Node.js是基于Google V8引擎的JavaScript运行时,以非阻塞I/O和事件驱动架构为特色,实现全栈开发。它跨平台且拥有丰富的生态系统,但也面临安全性、TypeScript支持和性能等挑战...【详细内容】
2024-03-21  前端充电宝  微信公众号  Tags:JS   点击:(25)  评论:(0)  加入收藏
构建一个通用灵活的JavaScript插件系统?看完你也会!
在软件开发中,插件系统为应用程序提供了巨大的灵活性和可扩展性。它们允许开发者在不修改核心代码的情况下扩展和定制应用程序的功能。本文将详细介绍如何构建一个灵活的Java...【详细内容】
2024-03-20  前端历险记  微信公众号  Tags:JavaScript   点击:(20)  评论:(0)  加入收藏
对JavaScript代码压缩有什么好处?
对JavaScript代码进行压缩主要带来以下好处: 减小文件大小:通过移除代码中的空白符、换行符、注释,以及缩短变量名等方式,可以显著减小JavaScript文件的大小。这有助于减少网页...【详细内容】
2024-03-13  WangLiwen    Tags:JavaScript   点击:(2)  评论:(0)  加入收藏
跨端轻量JavaScript引擎的实现与探索
一、JavaScript 1.JavaScript语言JavaScript是ECMAScript的实现,由ECMA 39(欧洲计算机制造商协会39号技术委员会)负责制定ECMAScript标准。ECMAScript发展史: 2.JavaScript...【详细内容】
2024-03-12  京东云开发者    Tags:JavaScript   点击:(2)  评论:(0)  加入收藏
面向AI工程的五大JavaScript工具
令许多人惊讶的是,一向在Web开发领域中大放异彩的JavaScript在开发使用大语言模型(LLM)的应用程序方面同样大有价值。我们在本文中将介绍面向AI工程的五大工具,并为希望将LLM...【详细内容】
2024-02-06    51CTO  Tags:JavaScript   点击:(53)  评论:(0)  加入收藏
JS小知识,使用这6个小技巧,避免过多的使用 if 语句
最近在重构我的代码时,我注意到早期的代码使用了太多的 if 语句,达到了我以前从未见过的程度。这就是为什么我认为分享这些可以帮助我们避免使用过多 if 语句的简单技巧很重要...【详细内容】
2024-01-30  前端达人  今日头条  Tags:JS   点击:(56)  评论:(0)  加入收藏
18个JavaScript技巧:编写简洁高效的代码
本文翻译自 18 JavaScript Tips : You Should Know for Clean and Efficient Code,作者:Shefali, 略有删改。在这篇文章中,我将分享18个JavaScript技巧,以及一些你应该知道的示例...【详细内容】
2024-01-30  南城大前端  微信公众号  Tags:JavaScript   点击:(68)  评论:(0)  加入收藏
使用 JavaScript 清理我的 200GB iCloud,有了一个意外发现!
本文作者在综合成本因素之下,决定用 Java 脚本来清理一下自己的 iCloud,结果却有了一个意外发现,即在 iCloud 中上传同一个视频和删除此视频之后,iCloud 的空间并不一致,这到底是...【详细内容】
2024-01-11    CSDN  Tags:JavaScript   点击:(99)  评论:(0)  加入收藏
站内最新
站内热门
站内头条