前言

过去,我们浏览静态网站时无须过多关注内存管理,因为加载新页面时,之前的页面信息会从内存中删除。 然而,随着单页Web应用(SPA)的兴起,应用程序消耗的内存越来越多,这不仅会降低浏览器性能,甚至会导致浏览器卡死。因此,在编码实践中,开发人员需要更加关注与内存相关的内容。因此,小编今天将为大家介绍JavaScript内存泄漏的编程模式,并提供一些内存管理的改进方法。

什么是内存泄漏以及如何发现它?

什么是内存泄漏?

JavaScript对象被保存在浏览器内存的堆中,并通过引用方式访问。值得一提的是,JavaScript垃圾回收器则运行于后台,并通过识别无法访问的对象来释放并恢复底层存储空间,从而保证JavaScript引擎的良好运行状态。

当内存中的对象在垃圾回收周期中应该被清理时,若它们被另一个仍然存在于内存中的对象通过一个意外的引用所持有,就会引发内存泄漏问题。这种情况下,冗余对象会继续占据内存空间,导致应用程序消耗过多的内存资源,并可能导致性能下降和表现不佳的情况出现。因此,及时清理无用对象并释放内存资源是至关重要的,以确保应用程序的正常运行和良好的性能表现。

如何发现内存泄漏?

那么如何知道代码中是否存在内存泄漏?内存泄漏往往隐蔽且很难检测和定位。即使代码中存在内存泄漏,浏览器在运行时也不会返回任何错误。如果注意到页面的性能逐渐下降,可以使用浏览器内置的工具来确定是否存在内存泄漏以及是哪个对象引起的。

任务管理器(不要与操作系统的任务管理器混淆)提供了浏览器中所有选项卡和进程的概览。Chrome 中,可以通过在 Linux 和 Windows 操作系统上按 Shift+Esc 来打开任务管理器;而在 Firefox 中,通过在地址栏中键入 about:performance 则可以访问内置的管理器,它可以显示每个标签的 JavaScript 内存占用情况。如果网站停留在那里什么都不做,但 JavaScript内存使用量逐渐增加,那很可能是存在内存泄漏。

开发者工具提供了一些先进的内存管理方法,例如,使用Chrome浏览器的性能记录工具,可以对页面的性能进行可视化分析。在这个过程中,可以通过一些指标来判断是否存在内存泄漏问题,比如堆内存使用量增加的情况,并及时采取措施解决这些问题,以确保应用程序的正常运行和良好的性能表现。

另外,通过Chrome和Firefox的开发者工具提供的内存工具,可以进一步探索内存使用情况。队列内存使用快照的比较可以显示在两个快照之间分配了多少内存以及分配的位置,并提供额外信息来帮助识别代码中存在问题的对象。这些工具为开发者提供了便利,能够更好地进行内存管理和性能优化,提高应用程序的质量和性能。

JavaScript代码中常见的内存泄漏的常见来源:

研究内存泄漏问题就相当于寻找符合垃圾回收机制的编程方式,有效避免对象引用的问题。下面小编就为大家介绍几个常见的容易导致内存泄漏的地方:

1.全局变量

全局变量始终存储在根目录下,且永远不会被回收。而在JavaScript的开发中,一些错误会导致局部变量被转换到了全局,尤其是在非严格的代码模式下。下面是两个常见的局部变量被转化到全局变量的情况:

  1. 为未声明的变量赋值
  2. 使用this指向全局对象。
function createGlobalVariables() {
leaking1 = 'I leak into the global scope'; // 为未声明的变量赋值
this.leaking2 = 'I also leak into the global scope'; // 使用this指向全局对象
};
createGlobalVariables();
window.leaking1;
window.leaking2;

注意:严格模式("use strict")将帮助您避免上面示例中的内存泄漏和控制台错误。

2.闭包

函数中定义的变量会在函数退出调用栈并且在函数外部没有指向它的引用时被清除。而闭包则会保持被引用的变量一直存在,即便函数的执行已经终止。

function outer() {
const potentiallyHugeArray = []; return function inner() {
potentiallyHugeArray.push('Hello'); // function inner is closed over the potentiallyHugeArray variable
console.log('Hello');
};
};
const sayHello = outer(); // contains definition of the function inner function repeat(fn, num) {
for (let i = 0; i < num; i++){
fn();
}
}
repeat(sayHello, 10); // each sayHello call pushes another 'Hello' to the potentiallyHugeArray // now imagine repeat(sayHello, 100000)

在这个例子中,potentiallyHugeArray从未被任何函数返回,也无法被访问,但它的大小会随着调用 inner 方法的次数而增长。

3.定时器

在JavaScript中,使用使用 setTimeout 或 setInterval函数引用对象是防止对象被垃圾回收的最常见方法。当在代码中设置循环定时器(可以使 setTimeout 表现得像 setInterval,即使其递归)时,只要回调可调用,定时器回调对象的引用就会永远保持活动状态。

例如下面的这段代码,只有在移除定时器后,data对象才会被垃圾回收。在没有移除setInterval之前,它永远不会被删除,并且data.hugeString 会一直保留在内存中,直到应用程序停止。

function setCallback() {
const data = {
counter: 0,
hugeString: new Array(100000).join('x')
}; return function cb() {
data.counter++; // data object is now part of the callback's scope
console.log(data.counter);
}
} setInterval(setCallback(), 1000); // how do we stop it?

那么应该如何避免上述这种情况的发生呢?可以从以下两个方法入手:

  1. 注意定时器回调引用的对象。
  2. 必要时取消定时器。

如下方的代码所示:

function setCallback() {
// 'unpacking' the data object
let counter = 0;
const hugeString = new Array(100000).join('x'); // gets removed when the setCallback returns return function cb() {
counter++; // only counter is part of the callback's scope
console.log(counter);
}
} const timerId = setInterval(setCallback(), 1000); // saving the interval ID // doing something ... clearInterval(timerId); // stopping the timer i.e. if button pressed

4.事件监听

活动的事件监听器会阻止其范围内的所有变量被回收。一旦添加,事件监听器会一直生效,直到下面两种情况的发生:

  1. 通过 removeEventListener() 移除。
  2. 相关联的 DOM 元素被移除。

在下面的示例中,使用匿名内联函数作为事件监听器,这意味着它不能与 removeEventListener() 一起使用。此外,由于document 不能被移除,触发方法中的内容会一直驻留内存,即使只使用它触发一次。

const hugeString = new Array(100000).join('x');

document.addEventListener('keyup', function() { // anonymous inline function - can't remove it
doSomething(hugeString); // hugeString is now forever kept in the callback's scope
});

那么如何避免这种情况呢?可以通过removeEventListener()释放监听器:

function listener() {
doSomething(hugeString);
} document.addEventListener('keyup', listener); // named function can be referenced here...
document.removeEventListener('keyup', listener); // ...and here

如果事件监听器只需要运行一次,addEventListener() 可以带有第三个参数,一个提供附加选项的对象。只要将 {once: true} 作为第三个参数传递给 addEventListener(),监听器将在事件处理一次后自动删除。

document.addEventListener('keyup', function listener() {
doSomething(hugeString);
}, {once: true}); // listener will be removed after running once

5.缓存

如果不断向缓存中添加内容,而未使用的对象也没有移除,也没有限制缓存的大小,那么缓存的大小就会无限增长:

let user_1 = { name: "Peter", id: 12345 };
let user_2 = { name: "Mark", id: 54321 };
const mapCache = new Map(); function cache(obj){
if (!mapCache.has(obj)){
const value = `${obj.name} has an id of ${obj.id}`;
mapCache.set(obj, value); return [value, 'computed'];
} return [mapCache.get(obj), 'cached'];
} cache(user_1); // ['Peter has an id of 12345', 'computed']
cache(user_1); // ['Peter has an id of 12345', 'cached']
cache(user_2); // ['Mark has an id of 54321', 'computed'] console.log(mapCache); // ((…) => "Peter has an id of 12345", (…) => "Mark has an id of 54321")
user_1 = null; // removing the inactive user // Garbage Collector
console.log(mapCache); // ((…) => "Peter has an id of 12345", (…) => "Mark has an id of 54321") // first entry is still in cache

为了解决这个问题,需要清除不需要的缓存:

一种有效的解决内存泄漏问题的方法是使用WeakMap。它是一种数据结构,其中键引用被保持为弱引用,并且仅接受对象作为键。如果使用对象作为键,并且它是唯一引用该对象的引用,相关条目将从缓存中移除,并进行垃圾回收。在下面的示例中,当替换user_1后,与之关联的条目将在下一次垃圾回收时自动从WeakMap中移除。

let user_1 = { name: "Peter", id: 12345 };
let user_2 = { name: "Mark", id: 54321 };
const weakMapCache = new WeakMap(); function cache(obj){
// ...same as above, but with weakMapCache return [weakMapCache.get(obj), 'cached'];
} cache(user_1); // ['Peter has an id of 12345', 'computed']
cache(user_2); // ['Mark has an id of 54321', 'computed']
console.log(weakMapCache); // ((…) => "Peter has an id of 12345", (…) => "Mark has an id of 54321"}
user_1 = null; // removing the inactive user // Garbage Collector console.log(weakMapCache); // ((…) => "Mark has an id of 54321") - first entry gets garbage collected

结论

对于复杂的应用程序,检测和修复 JavaScript 内存泄漏问题可能是一项非常艰巨的任务。了解内存泄漏的常见原因以防止它们发生是非常重要的。在涉及内存和性能方面,最重要的是用户体验,这才是最重要的。

[Redis从入门到实践](https://gcdn.grapecity.com.cn/course-273.html)

[ 一节课带你搞懂数据库事务!](https://gcdn.grapecity.com.cn/course-58.html)

[Chrome开发者工具使用教程](https://gcdn.grapecity.com.cn/course-78.html)

扩展链接:

如何在前端系统中使用甘特图

窗口函数大揭秘!轻松计算数据累计占比,玩转数据分析的绝佳利器

探秘移动端BI:发展历程与应用前景解析

如何避免JavaScript中的内存泄漏?的更多相关文章

  1. JavaScript 中的内存泄漏

    JavaScript 中的内存泄漏 JavaScript 是一种垃圾收集式语言,这就是说,内存是根据对象的创建分配给该对象的,并会在没有对该对象的引用时由浏览器收回.JavaScript 的垃圾收集机 ...

  2. JavaScript中的内存泄漏以及如何处理

    随着现在的编程语言功能越来越成熟.复杂,内存管理也容易被大家忽略.本文将会讨论JavaScript中的内存泄漏以及如何处理,方便大家在使用JavaScript编码时,更好的应对内存泄漏带来的问题. 概 ...

  3. Javascript中的内存泄漏

    最新博客站点:欢迎来访 一.内存泄漏        由于某些原因不再需要的内存没有被操作系统或则空闲内存池回收.编程语言中有多种管理内存的方式.这些方式从不同程度上会减少内存泄漏的几率,高级语言嵌入了 ...

  4. 了解 JavaScript 应用程序中的内存泄漏

    简介 当处理 JavaScript 这样的脚本语言时,很容易忘记每个对象.类.字符串.数字和方法都需要分配和保留内存.语言和运行时的垃圾回收器隐藏了内存分配和释放的具体细节. 许多功能无需考虑内存管理 ...

  5. 高效使用 JavaScript 闭包,避免 Node.js 应用程序中的内存泄漏

    在 Node.js 中,广泛采用不同形式的闭包来支持 Node 的异步和事件驱动编程模型.通过很好地理解闭包,您可以确保所开发应用程序的功能正确性.稳定性和可伸缩性. 闭包是一种将数据与处理数据的代码 ...

  6. javascript中的内存管理和垃圾回收

    前面的话 不管什么程序语言,内存生命周期基本是一致的:首先,分配需要的内存:然后,使用分配到的内存:最后,释放其内存.而对于第三个步骤,何时释放内存及释放哪些变量的内存,则需要使用垃圾回收机制.本文将 ...

  7. JavaScript 中对内存的一些了解

    在使用JavaScript进行开发的过程中,了解JavaScript内存机制有助于开发人员能够清晰的认识到自己写的代码在执行的过程中发生过什么,也能够提高项目的代码质量.其实关于内存的文章也有很多,写 ...

  8. 关于Hash集合以及Java中的内存泄漏

    <学习笔记>关于Hash集合以及Java中的内存泄漏 标签: 学习笔记内存泄露hash 2015-10-11 21:26 58人阅读 评论(0) 收藏 举报  分类: 学习笔记(5)  版 ...

  9. 系统剖析Android中的内存泄漏

    [转发]作为Android开发人员,我们或多或少都听说过内存泄漏.那么何为内存泄漏,Android中的内存泄漏又是什么样子的呢,本文将简单概括的进行一些总结. 关于内存泄露的定义,我可以理解成这样 没 ...

  10. [转载]Java应用程序中的内存泄漏及内存管理

    近期发现测试的项目中有JAVA内存泄露的现象.虽然JAVA有垃圾回收的机制,但是如果不及时释放引用就会发生内存泄露现象.在实际工作中我们使用Jprofiler调用java自带的 jmap来做检测还是很 ...

随机推荐

  1. FlutterWeb部署到服务器

    目标:把flutter web项目部署到自己的服务器上,可以使用自己的服务器IP访问 前提:服务器已经安装了nginx, 这是我的flutter配置 edz@lwqdeMacBook-Pro ~ % ...

  2. RTC+AI|“即智”数智人创新内容生产体验,为企业降本增效再提速

    号称"史上最卷"的618年中大促落下帷幕,几大电商巨头在直播投入和短视频内容建设上持续加码,短视频+直播电商的营销模式成为618期间的主要输出. 以某美妆专场直播间为例,主播现场手 ...

  3. 私网部署DNS(BIND)笔记

    准备工作 下载 yum install -y bind bind-utils包含dig.nslookup等调试命令,非必须. yum install -y bind-utils 防火墙 firewal ...

  4. 一分钟学一个 Linux 命令 - rm

    前言 大家好,我是 god23bin,欢迎回到咱们的<一分钟学一个 Linux 命令>系列,今天我要讲的是一个比较危险的命令,rm 命令,没错,你可以没听过 rm 命令,但是删库跑路你不可 ...

  5. WPF 全局样式资源管理

    在WPF通常我们习惯于把样式直接写在控件属性上,例如: <TextBox x:Name="pluginPathTxt" Margin="0,0,0,0" ...

  6. Wampserver64 报错:无法启动此程序,因为计算机中丢失 MSVCR110.dll。尝试重新安装该程序以解决此问题。

    缺少环境配置, 程序下载地址如下: https://www.microsoft.com/zh-cn/download/confirmation.aspx?id=30679 点击下载,下载完成后,双击程 ...

  7. Jenkins服务开机自启动

    最近因为护网行动,每天都要对服务器进行开.关机操作.为了省事儿,对Jenkins服务进行开机自动启动服务改造.实现如下: 1. 通过chkconfig --list命令列出系统中已安装的服务及其启动状 ...

  8. 推荐一款免费好用的远程桌面:Getscreen

    因为平时有多台设备要用,所以远程桌面是我经常要使用的工具. 最近,正好看到一款不错的远程桌面软件,马上拿出来推荐给大家,如果有需要的可以看看. 今天要推荐的远程桌面软件就是这款叫Getscreen的软 ...

  9. 安装iTerm2和oh-my-zsh

    安装iTerm2和oh-my-zsh 此文是在参考许多教程(见目录:参考)并结合本人安装经历写下的一篇关于iTerm2和oh-my-zsh的认识和超级详细安装教程.全文所有图片均为本人截屏拍摄.希望能 ...

  10. MySQL——后码锁(Next-Key Block)

    众所周知,Mysql的事务隔离级别分为4个,分别是READ-UNCOMMITED,READ-COMMITED,REPEATABLE-READ,SERIALIZABLE,在常规数据库概论中,前三种事务隔 ...