【心得】来聊聊令人头疼的前端内存泄漏~

前言

内存泄漏可以被视为你家中的水泄漏;虽然一开始小滴水可能看起来不是什么大问题,但随着时间的推移,它们可能会造成严重的损害。

同样,在JavaScript中,当不再需要的对象没有从内存中释放时,就会发生内存泄漏。随着时间的推移,这种累积的内存使用可以减慢甚至崩溃你的应用程序。


文章目录

  • 前言
    • 垃圾收集器的角色
    • 1. 全局变量
    • 2.定时器和回调函数
      • 示例:
    • 3. 闭包
  • 4. 事件监听器
      • 示例:
    • 5. 分离的DOM元素
    • 6.Websockets和外部连接
    • 7.工具来对抗内存泄漏
    • 总体建议

垃圾收集器的角色

在编程领域,尤其是在处理 JavaScript 等语言时,内存管理至关重要。幸运的是,JavaScript 内置了一个名为 “垃圾回收器”(GC)的机制来帮助实现这一目标。想象一下,一个勤劳的清洁工会定期清扫你的房子,捡起任何不用的物品并丢弃,以保持整洁。

垃圾回收器会定期检查不再需要或不再可访问的对象,并释放它们占用的内存。在理想情况下,它可以无缝运行,确保未使用的内存无需任何人工干预即可回收。然而,就像我们的清洁工有时可能会忽略隐藏角落里的闲置物品一样,垃圾回收器也可能会遗漏因引用而无意中保持存活的对象,从而导致内存泄漏。这就是为什么了解内存管理的细微差别并注意潜在的隐患对于任何开发人员来说都至关重要:

现在,让我们来看看哪些因素会导致应用程序内存泄漏:

1. 全局变量

在 JavaScript 中,最高级别的作用域是全局作用域。在此作用域中声明的变量可从代码中的任何地方访问,这可能很方便,但也有风险。对这些变量的不当管理可能会导致意外的内存保留。

原因是什么?当一个变量在未使用 let 、 const 或 var 声明的情况下被错误赋值时,它就会成为一个全局变量。此类变量驻留在全局作用域中,除非显式删除,否则会在应用程序的整个生命周期中持续存在。

例如:假设你正在创建一个计算矩形面积的函数:

function calculateArea(width, height) {area = width * height; // 误地创建全局变量“area”return area;
}calculateArea(10, 5);

这里, area 变量无意中被全局化,因为它没有与 let 、 const 或 var 一起声明。这意味着函数执行后, area 仍然可以访问并占用内存:

console.log(area); // Outputs: 50

避免:最佳做法是始终使用 let 、 const 或 var 声明变量,以确保它们具有正确的作用域,不会无意中成为全局变量。此外,如果你有意使用全局变量,请确保它们对于全局访问是必不可少的,并有意识地管理它们的生命周期。

修改上述示例以正确对 area 变量进行作用域设置:

function calculateArea(width, height) {let area = width * height; return area;
}calculateArea(10, 5);

现在,在函数执行后, area 变量在函数之外不可访问,并且在函数执行后将被正确垃圾回收。

2.定时器和回调函数

JavaScript提供了内置函数,允许在特定的时间段后异步执行代码(使用 setTimeout)或以规律的间隔执行(使用 setInterval)。尽管它们非常强大,但如果没有正确管理,它们可能无意中导致内存泄漏

原因:如果一个间隔或超时引用了一个对象,只要定时器还在运行,它就可以保持该对象在内存中,即使应用程序的其他部分不再需要该对象。

示例:

假设你有一个表示用户数据的对象,并设置一个间隔每5秒更新这些数据:

let userData = {name: "John",age: 25
};let intervalId = setInterval(() => {// 每5秒更新userDatauserData.age += 1;
}, 5000);

现在,如果某个时刻你不再需要更新userData,但忘记清除间隔,它会继续运行,阻止 userData 被垃圾回收。

避免方法:关键是在不需要定时器时始终停止它们。如果你完成了一个间隔或超时,使用clearInterval()或clearTimeout()分别清除它们。

继续上面的示例,如果你决定不再需要更新 userData,你可以这样清除间隔:

clearInterval(intervalId);

这会停止间隔,并允许其回调中引用的任何对象有资格进行垃圾回收,前提是没有其他挥之不去的引用。

3. 闭包

在JavaScript中,函数具有“记忆”它们创建时的环境的特殊能力。这种能力使内部函数可以访问外部(封闭)函数的变量,即使外部函数已经完成其执行。这种现象被称为“闭包”。

原因:闭包的能力伴随着责任。闭包保持对其外部环境变量的引用,这意味着如果闭包仍然活着(例如作为回调或在事件监听器中),它引用的变量将不会被垃圾回收,即使外部函数早已完成其执行。 示例:

假设你有一个创建倒计时的函数:

function createCountdown(start) {let count = start;return function() {return count--;};
}let countdownFrom10 = createCountdown(10);

这里,countdownFrom10 是一个闭包。每次调用它时,它会将 count 变量减少一个。由于内部函数保持对 count 的引用,count 变量不会被垃圾回收,即使在程序的其他地方没有对createCountdown函数的其他引用。

现在想象一下,如果count是一个更大、更消耗内存的对象,闭包无意中将其保留在内存中。

避免方法:虽然闭包是一个强大的特性并且经常是必要的,但重要的是要注意它们引用的内容。确保你:
1.只捕获你需要的内容:除非必要,不要在闭包中捕获大对象或数据结构。
2.完成后断开引用:如果一个闭包被用作事件监听器或回调,你不再需要它,就删除监听器或使回调为null,以断开闭包的引用。

修改上面的示例以有意断开引用:

function createCountdown(start) {let count = start;return function() {return count--;};
}let countdownFrom10 = createCountdown(10);countdownFrom10 = null;

4. 事件监听器

JavaScript中的事件监听器通过允许我们“监听” 特定的事件(如点击或按键)并在这些事件发生时采取行动,实现交互性。但与其他JavaScript功能一样,如果不仔细管理加粗样式,它们可能会成为内存泄漏的来源。

原因:当你将事件监听器附加到DOM元素时,它在该函数(通常是闭包)和该元素之间创建了一个绑定。如果删除了元素或不再需要该事件监听器,但没有明确删除监听器,关联的函数仍留在内存中,可能保留其引用的其他变量和元素。

示例:

假设你有一个按钮,你将一个点击监听器附加到它:

const button = document.getElementById('myButton');button.addEventListener('click', function() {console.log('Button was clicked!');
});

现在,稍后在你的应用程序中,你决定从DOM中删除按钮:

button.remove();

即使按钮从DOM中删除,事件监听器的函数仍然保留对按钮的引用。这意味着按钮不会被垃圾回收,导致内存泄漏。

避免方法:关键是积极管理你的事件监听器:
明确删除:在删除元素或不再需要它们时,使用removeEventListener()始终删除事件监听器。
使用一次:如果你知道一个事件只需要一次,你可以在添加监听器时使用{ once: true }选项。

button.addEventListener('click', handleClick, { once: true });

修改上面的示例以进行正确管理:

const button = document.getElementById('myButton');function handleClick() {console.log('Button was clicked!');
}button.addEventListener('click', handleClick);// 稍后在代码中,当我们完成按钮时:
button.removeEventListener('click', handleClick);
button.remove();

通过在删除按钮之前明确地删除事件监听器,我们确保监听器的函数和按钮本身都可以被垃圾回收。

5. 分离的DOM元素

文档对象模型(DOM)是网页上所有元素的分层表示。当你修改DOM,例如通过删除元素,但仍然在JavaScript中持有对该元素的引用,你就已经创建了所谓的** “分离的DOM元素” 。这些元素不再可见,但由于它们仍然被代码引用**,所以它们不能被垃圾回收

原因:当从DOM中删除元素但仍有指向它们的JavaScript引用时,会创建分离的DOM元素。这些引用阻止垃圾回收器回收这些元素占用的内存。

示例:

假设你有一个物品列表,并且决定删除一个:

let listItem = document.getElementById('itemToRemove');
listItem.remove();

现在,即使您已经从DOM中删除了 listItem,你仍然在 listItem 变量中对其有引用。这意味着实际的元素仍然在内存中,从DOM中分离但占用空间。

避免方法:为了防止分离的DOM元素引起的内存泄漏:

使引用为 null:删除DOM元素后,使对其的任何引用为 null:

listItem.remove();
listItem = null;

限制元素引用:只在绝对需要时存储对DOM元素的引用。如果你只需要对元素执行单一操作,那么你不需要保留对它的长时间引用。

修改上面的示例以防止内存泄漏:

let listItem = document.getElementById('itemToRemove');
listItem.remove();
listItem = null;  // 断开对分离的DOM元素的引用

通过在从DOM中删除 listItem 后使 listItem 引用为null,我们确保垃圾回收器可以回收已删除元素占用的内存。

6.Websockets和外部连接

Websockets 提供了一个全双工通信通道,通过单个、长时间的连接。这使它非常适合实时应用,如聊天应用、在线游戏和实时体育更新。然而,由于 Websockets 的性质是保持开放的,如果不正确处理,它们可能成为内存泄漏的潜在来源。

原因:当 Websockets和其他持久的外部连接管理不当时,它们即使不再需要也可以持有对象或回调的引用。这可以阻止这些引用的对象被垃圾回收,导致内存泄漏。

示例:

假设你有一个应用程序,该应用程序打开一个 websocket 连接以接收实时更新:

let socket = new WebSocket('ws://example.com/updates');socket.onmessage = function(event) {console.log(`Received update: ${event.data}`);
};

现在,如果在某个时候,您导航离开了应用的这一部分或关闭了使用此连接的特定UI组件,但忘记关闭 websocket,它仍然保持打开状态。与其事件监听器关联的任何对象或闭包都不能被垃圾回收。

避免方法:积极管理websocket连接至关重要:

明确关闭:当不再需要时,始终使用 close() 方法关闭 websocket 连接:

socket.close();

引用为 null:关闭 websocket 连接后,使任何关联的引用为 null 以帮助垃圾回收器:

socket.onmessage = null;
socket = null;

**错误处理:**实施错误处理以检测连接何时丢失或意外终止,然后清理任何相关的资源。

继续上面的示例,正确的管理看起来是这样的:

let socket = new WebSocket('ws://example.com/updates');socket.onmessage = function(event) {console.log(`Received update: ${event.data}`);
};// 稍后在代码中,当连接不再需要时:
socket.close();
socket.onmessage = null;
socket = null;

7.工具来对抗内存泄漏

预防内存泄漏的最佳方法是尽早检测它们。浏览器开发者工具,尤其是Chrome DevTools,可以成为你的最佳朋友。 “Memory”标签尤其有用,允许您监视内存使用情况,拍摄快照并随着时间的推移跟踪更改。

总体建议

  • 定期审核:定期审查您的代码以确保遵循最佳实践。
  • 测试:添加新功能后,测试潜在的内存泄漏。
  • 代码卫生:保持代码整洁、模块化并且记录完善。
  • 第三方库:明智地使用它们。有时它们可能是内存泄漏的原因。

请记住,就像在现实生活中一样,预防胜于治疗。通过保持警觉和积极主动,你可以确保JavaScript应用程序顺畅运行,而不会被内存泄漏拖累。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/115204.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数据开发中的秘密武器:探索Hadoop纠删码的奇妙世界

随着大数据技术的发展,HDFS作为Hadoop的核心模块之一得到了广泛的应用。为了系统的可靠性,HDFS通过复制来实现这种机制。但在HDFS中每一份数据都有两个副本,这也使得存储利用率仅为1/3,每TB数据都需要占用3TB的存储空间。因此&…

coc/soc/owc有什么区别

我们常说船东,除外了船东之外还有一个词是箱东,也就是集装箱的拥有者,也叫箱主,即Container Owner。世界上还有一个Container Owners Association,集装箱箱主协会。 根据所有者不同,集装箱分为三类&#x…

组件安全漏洞

1.常见组件 操作系统 liunx windows mac web容器 我们简单的认为,只要能够提供Web 服务的应用,就是Web 容器 apache httpd nginx iis tomcat 中间件 代码 php java python 数据库 关系型数据库 mysql sqlserver 非关系型…

【网络安全入门】学习网络安全必须知道的100 个网络基础知识

前言 先领取资料再阅读哦 【282G】网络安全&黑客技术零基础到进阶全套学习大礼包(附面试题答案),免费分享! 【282G】网络安全&黑客技术零基础到进阶全套学习大礼包(附面试题答案),免…

SpringBoot下的代理注解

EnableAspectJAutoProxy Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Import(AspectJAutoProxyRegistrar.class) public interface EnableAspectJAutoProxy {// 是否代理目标对象,ture:使用CGLIB代理 fasle:使用JDK代理boolean proxy…

安装Docker

本安装教程参考Docker官方文档,地址如下:https://docs.docker.com/engine/install/centos/ 卸载旧版 首先如果系统中已经存在旧的Docker,则先卸载: yum remove docker \ docker-client \ docker-client-latest \ docker-common…

【计算机网络】TCP 协议的相关特性

TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的协议。以下是TCP协议的相关特性: 可靠性:TCP通过确认和重传机制保证数据的可靠传输。 面向连接:TCP在传输数据前需要先建立连接。连接的建立过程包括三次握手…

C++ 读MTK代码 综测校准 PSU读开关电源电压或电流 visa

1.C定义dll接口 // The following ifdef block is the standard way of creating macros which make exporting // from a DLL simpler. All files within this DLL are compiled with the PSU_DLL_EXPORTS // symbol defined on the command line. this symbol should not b…

Kotlin中的Set集合

在Kotlin中,Set集合用于存储一组唯一的元素,它们是无序的,不可重复的。Set集合分为可变集合(MutableSet)和不可变集合(Set)。本篇博客将分别介绍可变集合和不可变集合,并提供相关的A…

Zookeeper 和 Kafka 工作原理及如何搭建 Zookeeper集群 + Kafka集群

目录 1 Zookeeper 1.1 Zookeeper 定义 1.2 Zookeeper 工作机制 1.3 Zookeeper 特点 1.4 Zookeeper 数据结构 1.5 Zookeeper 应用场景 1.6 Zookeeper 选举机制 2 部署 Zookeeper 集群 2.1 安装前准备 2.2 安装 Zookeeper 3 Kafka 3.1 为什么需要消息队列(…

防雷检测的项目和行业的等级区分

防雷检测是指对雷电防护装置的性能、质量和安全进行检测的活动,是保障人民生命财产和公共安全的重要措施。 地凯科技防雷检测的项目内容包括接闪器检测、引下线检测、接地装置检测、防雷区的划分、电磁屏蔽防雷检测、等电位连接检测、及电涌保护器 (SPD)性能检测。…

80个国内可用的Chatgpt网页版(2023.10.21更新)

ChatGPT:革命性的人工智能语言模型 ChatGPT,一款能够与人类进行自然流畅对话的人工智能语言模型,通过大量训练数据和先进算法,展现出卓越的自然语言处理能力。它能理解并回应人类问题,提供准确、连贯且有意义的答案&a…

C#,数值计算——分类与推理Phylo_nj的计算方法与源程序

1 文本格式 using System; using System.Collections.Generic; namespace Legalsoft.Truffer { public class Phylo_nj : Phylagglom { public double[] u; public override void premin(double[,] d, int[] nextp) { i…

电商接口中API key 和 token 有什么区别?

API key 和 token 就有这种问题,它们都是作为一种身份验证机制。前几天我在一次讨论中,有人提到这两个词可以互换使用。大约两分钟后,我不得不停止谈话并说“你们应该知道它们是不同的,对吧?”‍,说完会上鸦…

Java后端开发(十)-- idea(2022版)将 已push 的 远程仓库 的 多条commit记录 进行撤销

目录 1.多次 修改Test01类后,提交到本地仓库 。 2.多次重复 1 的步骤,多次commit成功后,在Git =》Log中会显示,commit记录

本地数仓网络设备迁移实录

环境: 本地机房 深信服防火墙A 8.0.75 AF-2000-FH2130B-SC H3C S6520-26Q-SI 交换机A Version 7.1.070,Release 6326H3C IDC机房 深信服防火墙B 8.0.75 AF-2000-FH2130B-SC H3C S6520 交换机B version 7.1.070, Release 6530P02 问题描述: 本地机房 H3C S6520 交换机…

idea的debug调试

目录 断点条件设置(condition) 断点表达式(evaluate expression) 断点回退(reset frame) 断点条件设置(condition) 条件断点,一般是满足我们设置的某个条件时,debug断点才会生效。这种条件断点设置,我们一般用在多重循环中。 这儿我们以li…

将数据写入文本文件

目录 将表导出到文本文件 将元胞数组导出到文本文件 将数值数组导出到文本文件 将表、元胞数组或数值数组中包含的表格数据从 MATLAB 工作区导出到文本文件。 将表导出到文本文件 可使用 writetable 函数将表格数据从 MATLAB 工作区导出到文本文件。创建样本表,…

前端数据可视化之【series、series饼图配置】配置项

目录 🌟Echarts配置项🌟series🌟饼图 type:pie🌟写在最后 🌟Echarts配置项 ECharts开源来自百度商业前端数据可视化团队,基于html5 Canvas,是一个纯Javascript图表库,提供直观&…

预测宝可梦武力值、分类宝可梦

regression case 股票预测 无人车看到的各种sensor 影像镜头看到马路上的东西作为输入,输出就是方向盘角度等等的操纵策略 scalar 标量 这个是热力图,相当于你的XYZ但是Z用颜色表示了 closed-form solution 闭合解 learning rate事先定好的数值 在lin…