5个常见的JavaScript内存错误

news2024/11/18 21:39:17

JavaScript 不提供任何内存管理操作。相反,内存由 JavaScript VM 通过内存回收过程管理,该过程称为垃圾收集。

既然我们不能强制的垃圾回收,那我们怎么知道它能正常工作?我们对它又了解多少呢?

  • 脚本执行在此过程中暂停

  • 它为不可访问的资源释放内存

  • 它是不确定的

  • 它不会一次检查整个内存,而是在多个周期中运行

  • 它是不可预测的,但它会在必要时执行

这是否意味着无需担心资源和内存分配问题?当然不是。如果我们一不小心,可能会产生一些内存泄漏。

什么是内存泄漏?

内存泄漏是软件无法回收的已分配的内存块。

Javascript 提供了一个垃圾收集程序,但这并不意味着我们就能避免内存泄漏。为了符合垃圾收集的条件,该对象必须不被其他地方引用。如果持有对未使用的资源的引用,这将会阻止这些资源被回收。

这就是所谓的无意识的内存保持。

泄露内存可能会导致垃圾收集器更频繁地运行。由于这个过程会阻止脚本的运行,它可能会让我们程序卡起来,这么一卡,挑剔的用户肯定会注意到,一用不爽了,那这个产品离下线的日子就不完了。

更严重可能会让整个应用奔溃,那就gg了。

如何防止内存泄漏? 主要还是我们应该避免保留不必要的资源。来看看一些常见的场景。

1、计时器的监听

setInterval() 方法重复调用函数或执行代码片段,每次调用之间有固定的时间延迟。它返回一个时间间隔ID,该ID唯一地标识时间间隔,因此您可以稍后通过调用 clearInterval() 来删除它。

我们创建一个组件,它调用一个回调函数来表示它在x个循环之后完成了。我在这个例子中使用react,但这适用于任何FE框架。

 
import react, { useRef } from 'react';
const Timer = ({ cicles, onFinish }) => {    const currentCicles = useRef(0);
    setInterval(() => {        if (currentCicles.current >= cicles) {            onFinish();            return;        }        currentCicles.current++;    }, 500);
    return (        <div>Loading ...</div>    );}
export default Timer;

一看,好像没啥问题。不急,我们再创建一个触发这个定时器的组件,并分析其内存性能。

 
import React, { useState } from 'react';import styles from '../styles/Home.module.css'import Timer from '../components/Timer';
export default function Home() {    const [showTimer, setShowTimer] = useState();    const onFinish = () => setShowTimer(false);
    return (      <div className={styles.container}>          {showTimer ? (              <Timer cicles={10} onFinish={onFinish} />          ): (              <button onClick={() => setShowTimer(true)}>                Retry              </button>          )}      </div>    )}

在 Retry 按钮上单击几次后,这是使用Chrome Dev Tools获取内存使用的结果:

当我们点击重试按钮时,可以看到分配的内存越来越多。这说明之前分配的内存没有被释放。计时器仍然在运行而不是被替换。

怎么解决这个问题?setInterval 的返回值是一个间隔 ID,我们可以用它来取消这个间隔。在这种特殊情况下,我们可以在组件卸载后调用 clearInterval。

 
useEffect(() => {    const intervalId = setInterval(() => {        if (currentCicles.current >= cicles) {            onFinish();            return;        }        currentCicles.current++;    }, 500);
    return () => clearInterval(intervalId);}, [])

有时,在编写代码时,很难发现这个问题,最好的方式,还是要把组件抽象化。

这里使用的是React,我们可以把所有这些逻辑都包装在一个自定义的 Hook 中。

 
import { useEffect } from 'react';
export const useTimeout = (refreshCycle = 100, callback) => {    useEffect(() => {        if (refreshCycle <= 0) {            setTimeout(callback, 0);            return;        }
        const intervalId = setInterval(() => {            callback();        }, refreshCycle);
        return () => clearInterval(intervalId);    }, [refreshCycle, setInterval, clearInterval]);};
export default useTimeout;

现在需要使用setInterval时,都可以这样做:

 
const handleTimeout = () => ...;
useTimeout(100, handleTimeout);

现在你可以使用这个useTimeout Hook,而不必担心内存被泄露,这也是抽象化的好处。

2、事件监听

Web API提供了大量的事件监听器。在前面,我们讨论了setTimeout。现在来看看 addEventListener。

在这个事例中,我们创建一个键盘快捷键功能。由于我们在不同的页面上有不同的功能,所以将创建不同的快捷键功能。

 
function homeShortcuts({ key}) {    if (key === 'E') {        console.log('edit widget')    }}
// 用户在主页上登陆,我们执行document.addEventListener('keyup', homeShortcuts); 

// 用户做一些事情,然后导航到设置
function settingsShortcuts({ key}) {    if (key === 'E') {        console.log('edit setting')    }}
// 用户在主页上登陆,我们执行document.addEventListener('keyup', settingsShortcuts); 

看起来还是很好,除了在执行第二个 addEventListener 时没有清理之前的 keyup。这段代码不是替换我们的 keyup 监听器,而是将添加另一个 callback。这意味着,当一个键被按下时,它将触发两个函数。

要清除之前的回调,我们需要使用 removeEventListener :

 
document.removeEventListener(‘keyup’, homeShortcuts);

重构一下上面的代码:

 
function homeShortcuts({ key}) {    if (key === 'E') {        console.log('edit widget')    }}
// user lands on home and we executedocument.addEventListener('keyup', homeShortcuts); 

// user does some stuff and navigates to settings
function settingsShortcuts({ key}) {    if (key === 'E') {        console.log('edit setting')    }}
// user lands on home and we executedocument.removeEventListener('keyup', homeShortcuts); document.addEventListener('keyup', settingsShortcuts);

根据经验,当使用来自全局对象的工具时,需要灰常小心。

3、Observers

Observers 是一个浏览器的 Web API功能,很多开发者都不知道。如果你想检查html元素的可见性或大小的变化,这个就很强大了。

IntersectionObserver接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)。

尽管它很强大,但我们也要谨慎的使用它。一旦完成了对对象的观察,就要记得在不用的时候取消它。

看看代码:

 
const ref = ...const visible = (visible) => {  console.log(`It is ${visible}`);}
useEffect(() => {    if (!ref) {        return;    }
    observer.current = new IntersectionObserver(        (entries) => {            if (!entries[0].isIntersecting) {                visible(true);            } else {                visbile(false);            }        },        { rootMargin: `-${header.height}px` },    );
    observer.current.observe(ref);}, [ref]);

上面的代码看起来不错。然而,一旦组件被卸载,观察者会发生什么?它不会被清除,那内存可就泄漏了。我们怎么解决这个问题呢?只需要使用 disconnect 方法:

 
const ref = ...const visible = (visible) => {  console.log(`It is ${visible}`);}
useEffect(() => {    if (!ref) {        return;    }
    observer.current = new IntersectionObserver(        (entries) => {            if (!entries[0].isIntersecting) {                visible(true);            } else {                visbile(false);            }        },        { rootMargin: `-${header.height}px` },    );
    observer.current.observe(ref);
    return () => observer.current?.disconnect();}, [ref]);

4、Window Object

向 Window 添加对象是一个常见的错误。在某些场景中,可能很难找到它,特别是在使用 Window Execution上下文中的this关键字。看看下面的例子:

 
function addElement(element) {    if (!this.stack) {        this.stack = {            elements: []        }    }
    this.stack.elements.push(element);}

它看起来无害,但这取决于你从哪个上下文调用addElement。如果你从Window Context调用addElement,那就会越堆越多。

另一个问题可能是错误地定义了一个全局变量:

 
var a = 'example 1'; // 作用域限定在创建var的地方b = 'example 2'; // 添加到Window对象中

要防止这种问题可以使用严格模式:

 
"use strict"

通过使用严格模式,向JavaScript编译器暗示,你想保护自己免受这些行为的影响。当你需要时,你仍然可以使用Window。不过,你必须以明确的方式使用它。

严格模式是如何影响我们前面的例子:

  • 对于 addElement 函数,当从全局作用域调用时,this 是未定义的

  • 如果没有在一个变量上指定const | let | var,你会得到以下错误:

 
Uncaught ReferenceError: b is not defined

5、持有DOM引用

DOM节点也不能避免内存泄漏。我们需要注意不要保存它们的引用。否则,垃圾回收器将无法清理它们,因为它们仍然是可访问的。

用一小段代码演示一下:

 
const elements = [];const list = document.getElementById('list');
function addElement() {    // clean nodes    list.innerhtml = '';
    const divElement= document.createElement('div');    const element = document.createTextNode(`adding element ${elements.length}`);    divElement.appendChild(element);

    list.appendChild(divElement);    elements.push(divElement);}
document.getElementById('addElement').onclick = addElement;

注意,addElement 函数清除列表 div,并将一个新元素作为子元素添加到它中。这个新创建的元素被添加到 elements 数组中。

下一次执行 addElement 时,该元素将从列表 div 中删除,但是它不适合进行垃圾收集,因为它存储在 elements 数组中。

我们在执行几次之后监视函数:

在上面的截图中看到节点是如何被泄露的。那怎么解决这个问题?清除 elements 数组将使它们有资格进行垃圾收集。

总结

在这篇文章中,我们已经看到了最常见的内存泄露方式。很明显,JavaScript本身并没有泄漏内存。相反,它是由开发者方面无意的内存保持造成的。只要代码是整洁的,而且我们不忘自己清理,就不会发生泄漏。

了解内存和垃圾回收在JavaScript中是如何工作的是必须的。一些开发者得到了错误的意识,认为由于它是自动的,所以他们不需要担心这个问题。

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

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

相关文章

计算机毕业设计之java+ssm某地区精准扶贫网站

项目介绍 本精准扶贫网站管理系统主要包括系统用户管理模块、捐赠信息管理模块、投诉信息管理、扶贫资讯管理、登录模块、和退出模块等多个模块,系统采用了jsp的mvc框架,SSM(springMvcspringMybatis)框架进行开发,本系统使用mysql&#xff0c;独立运行,不依附于其他系统&#…

Redis数据类型之set

文章目录setⅠ. 基础操作Ⅱ. 随机数据Ⅲ. 交、并、差Ⅳ. 应用场景Ⅴ. 注意事项提示&#xff1a;以下是本篇文章正文内容&#xff0c;Redis系列学习将会持续更新 set ● 数据存储需求&#xff1a;存储大量的数据&#xff0c;在查询方面提供更高的效率。 ● 需要的存储结构&#…

家乡主题网页设计代码 旅游主题网页设计 html静态网页设计制作 dw静态网页成品模板素材网页 web前端网页设计与制作 div静态网页设计

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…

详细解读Spring Boot中@Import三种使用方式

需要注意的是&#xff1a;ImportSelector、ImportBeanDefinitionRegistrar这两个接口都必须依赖于Import一起使用&#xff0c;而Import可以单独使用。Import是一个非常有用的注解&#xff0c;它的长处在于你可以通过配置来控制是否注入该Bean&#xff0c;也可以通过条件来控制注…

CNN卷积神经网络

&#xff08;声明&#xff1a;本文章是在学习他人视频的学习笔记&#xff0c;图片出处均来自该up主&#xff0c;侵权删 up主链接&#xff1a;同济子豪兄的个人空间_哔哩哔哩_bilibili&#xff09; 卷积神经网络就像一个黑箱&#xff0c;有输入和输出&#xff0c;输入是一个图像…

Spring 中更加简单的 “存储“ 和 “读取“ 对象

目录 1. 更加简单的存储对象 1.1 配置扫描路径 1.2 使用五大类注解存储 bean 对象 1.2.1 五大类注解之间的关系 1.2.2 关于 bean 的命名规则 1.3 使用方法注解存储 bean 对象 1.3.1 bean 的重命名 2. 更加简单的获取对象 (DI) 2.1 属性注入 2.1.1 属性注入优缺点分析 …

三、图片的几何变换

目录一、图片缩放1 - 等比缩放2 - 最近领域插值3 - 双线性插值4 - 矩阵缩放二、图片剪切与位移1 - 图片剪切2 - 图片位移三、图片镜像四、图片仿射变换五、图片旋转一、图片缩放 1 - 等比缩放 # 1 load 2 info 3 resize 4 check import cv2img cv2.imread(image0.jpg, 1) im…

软件工程详细知识点复习(上)

文章目录一、软件工程概述1、软件与软件危机2、软件工程二、可行性研究三、需求分析四、概要设计五、详细设计一、软件工程概述 1、软件与软件危机 软件程序数据文档 1、软件危机的主要表现 软件不能满足用户需求软件开发成本严重超标&#xff0c;开发周期大大超过规定日期…

网络设备安装上线,你要知道的10个步骤

大家好&#xff0c;我是技福的小咖老师。在网络工程中设备的安装工作必不可少&#xff0c;你平时都是按哪些步骤完成的&#xff1f;今天给大家总结一下最常见的10个步骤。 安装流程 网络设备安装流程图 安装环境要求 1► 安装场景 为确保设备的正常运行&#xff0c;延长设备…

C. Infected Tree(思维+DFS)

Problem - 1689C - Codeforces Byteland是一片美丽的土地&#xff0c;因其美丽的树木而闻名。 米沙发现了一棵有n个顶点的二叉树&#xff0c;编号从1到n。二叉树是一个无环连接的双向图&#xff0c;包含n个顶点和n-1条边。每个顶点的度数最多为3&#xff0c;而根是数字为1的顶…

基于STM32G431嵌入式学习笔记——五、NVIC中断(以串口UART中断为例)

一、基础知识 1.专业术语 2.NVIC简介 ①在这里要注意&#xff0c;中断控制是分级处理的 ②是否请求中断是中断源控制的。 ③是否响应中断是响应方控制的。 ④以外部中断为例&#xff0c;外部中断请求顺序就是首先从请求的外部设备中选出优先级最高的一个设备待中断&#xff0c…

唐山盐碱滩成渤海明珠 国稻种芯·中国水稻节:河北曹妃甸大米

唐山盐碱滩成渤海明珠 国稻种芯中国水稻节&#xff1a;河北曹妃甸大米 新华社音视频部制作 记者 杨世尧 河北新闻网讯 王士波 赵诤国 新闻中国采编网 中国新闻采编网 谋定研究中国智库网 中国农民丰收节国际贸易促进会 国稻种芯中国水稻节 中国三农智库网-功能性农业农业大健康…

电脑怎么迁移游戏资源,数据迁移能把游戏数据迁移吗

概述&#xff1a;玩家们在打游戏的过程中&#xff0c;会产生很多数据&#xff0c;尤其是那些大型游戏的玩家&#xff0c;都会珍惜游戏数据。电脑怎么迁移游戏资源&#xff1f;如果您刚刚购买了一台新电脑&#xff0c;并且正在寻找将游戏迁移到新电脑的方法&#xff0c;相信本文…

8 张图 | 剖析 Eureka 的首次同步注册表

注册表对于注册中心尤为重要&#xff0c;所有的功能都是围绕这个注册表展开。比如服务 A 要想访问服务 B&#xff0c;就得知道服务 B 的 IP 地址和端口号吧。如下图所示&#xff0c;传统的方式就是服务 A 知道了服务 B 的地址后&#xff0c;发送 HTTP 请求到对应的 API 地址上。…

MySQL事务管理 MVCC,隔离性详解

目录事务管理事务背景什么是事务&#xff1f;事务的四个属性为什么会出现事务?MySQL支持事务的版本事务提交方式事务常见操作方式演示(体现原子性和持久性)操作注意事项结论事务隔离性查看与设置隔离性各种隔离性演示读未提交【Read Uncommitted】脏读读提交【Read Committed】…

【SNUT集训1】排序 二分 前缀和 差分(6 / 16)

目录 P1094 [NOIP2007 普及组] 纪念品分组 - 排序贪心双指针 P1571 眼红的Medusa - 哈希表 P1678 烦恼的高考志愿 P1024 [NOIP2001 提高组] 一元三次方程求解 1、二分法 2、暴力 P7585 [COCI2012-2013#1] LJUBOMORA - 二分 P4552 [Poetize6] IncDec Sequence- 差分思维…

【Vue学习之从入门到神经】

14天学习训练营导师课程&#xff1a; 郑为中《Vue和SpringBoot打造假日旅社管理系统》 目录 1. VUE介绍 2. Vue导入 3. VUE相关指令 4. 显示隐藏相关 5. Vue周边库 1. VUE介绍 VUE是目前最流行的前端框架, 基于MVVM设计模式VUE框架两种用法: 多页面应用, 在html页面中引入…

【HMS Core】游戏初始化

前提条件 实现游戏初始化前&#xff0c;必须已经完成AppGallery Connect的配置准备&#xff0c;参见AGC控制台准备。已完成集成SDK和配置混淆脚本。 注意事项 本场景中涉及的功能必须在应用启动时完成&#xff0c;而不是用户在进行登录、支付等操作时才完成&#xff0c;否则可…

无线传感器网络:物理层设计

文章目录Physical Layer TechnologiesRadio FrequenciesNarrow-Band CommunicationSpread SpectrumDSSSFHSSUltra Wide Band (UWB)Optical CommunicationAcoustic CommunicationMagnetic Induction CommunicationRF Wireless CommunicationWireless Channel EffectsAttenuation…

项目工作中,管理者如何合理安排任务优先级?

面对各种工作的时候&#xff0c;你是否总是会手忙脚乱&#xff1f; 在项目工作中&#xff0c;管理者每天面对各种工作&#xff1a;需求、沟通还有其他五花八门任务。 管理者应该如何合理安排自己任务的优先级呢&#xff1f; 在安排任务优先级的时候&#xff0c;我最常用方法…