内存泄露引发的卡顿

前言

内存泄露是前端不可避免的一个问题,内存泄露也会导致卡顿?


让我们一起来探探究竟。

一、提出背景

当一个页面出现卡顿的时候,你会想到有哪些原因呢?
常见原因:

可以分为两大块

渲染不及时,页面掉帧

  1. 检查是否网络请求过多,导致返回数据较慢,可以适当的做一些缓存,静态资源可以使用cdn缓存。
  2. 也有可能是某块资源的bundle太大,可以使用拆分。
  3. 排查一下js代码,是否有过多循环导致占用主线程时间过长。
  4. 浏览器某帧渲染的东西太多,导致的卡顿。
  5. 在页面渲染过程中,有可能有很多重复的重排重绘。

网页内存占用过高,运行卡顿

  1. 内存泄漏导致内存过大

二、什么是内存泄露

内存泄露的定义

那什么是内存泄漏呢?借助别的大佬给出的定义,内存泄漏就是指由于疏忽或者程序的某些错误造成未能释放已经不再使用的内存的情况。简单来讲就是假设某个变量占用100M的内存,而你又用不到这个变量,但是这个变量没有被手动的回收或自动回收,即仍然占用100M的内存空间,这就是一种内存的浪费,即内存泄漏。

js的数据存储

JavaScript的内存空间分为栈内存和堆内存,前者用来存放一些简单变量,后者用来存放复杂对象

  • 简单变量指的是JS的基本数据类型,例如:String、Number、Boolean、null、undefined、Symbol、BigInt
  • 复杂对象指的是JS的引用数据类型,例如:Object、Array、Function…

js垃圾回收机制

根据内存泄漏的定义,有些变量或数据不再被使用或不需要了,那么它就是垃圾变量或垃圾数据,如果其一直保存在内存中,最终可能会导致内存占用过多的情况。那么此时就需要对这些垃圾数据进行回收,这里引入了垃圾回收机制的概念。

垃圾回收的机制分为手动和自动两种
例如C/C++采用的就是手动回收的机制,即先用代码为某个变量分配一定的内存,然后在不需要了后,再用代码手动释放掉内存
而JavaScript采用的则是自动回收的机制,即我们不需要关心何时为变量分配多大的内存,也不需要关心何时去释放内存,因为这一切都是自动的。但这不表示我们不需要关心内存的管理!!!!否则也不会有本文讨论的内存泄露了。

js的垃圾回收有引用计数和标记清除两种方式。

Chrome devTools查看内存情况

在了解一些常见的内存泄漏的场景之前,先简单介绍一下如何使用Chrome的开发者工具来查看js内存情况。
首先打开Chrome的无痕模式,这样做的目的是为了屏蔽掉Chrome插件对我们之后测试内存占用情况的影响。
在这里插入图片描述
然后打开开发者工具,找到Performance这一栏,可以看到其内部带着一些功能按钮,例如:开始录制按钮;刷新页面按钮;清空记录按钮;记录并可视化js内存、节点、事件监听器按钮;触发垃圾回收机制按钮等等
在这里插入图片描述
简单录制一下百度页面,看看我们能获得什么,如下动图所示:

在这里插入图片描述

从上图中我们可以看到,在页面从零到加载完成这个过程中JS Heap(js堆内存)、documents(文档)、Nodes(DOM节点)、Listeners(监听器)、GPU memory(GPU内存)的最低值、最高值以及随时间的走势曲线,这也是我们主要关注的点
再来看看开发者工具中的Memory一栏,其主要是用于记录页面堆内存的具体情况以及js堆内存随加载时间线动态的分配情况
在这里插入图片描述
堆快照就像照相机一样,能记录你当前页面的堆内存情况,每快照一次就会产生一条快照记录,
在这里插入图片描述

有哪些场景会发生内存泄露及对应的解决方案

  1. 意外的全局变量引起的内存泄漏
  2. 闭包引起的内存泄漏
  3. 被遗忘的定时器 循环引用
  4. DOM删除时没有解绑事件
  5. 没有清理的DOM元素引用
  6. 控制台的打印

那我们就来讲讲各种情况吧:

意外的全局变量引起的内存泄漏

全局的变量一般是不会被垃圾回收掉的。当然这并不是说变量都不能存在全局,只是有时候会因为疏忽而导致某些变量流失到全局,例如未声明变量,却直接对某变量进行赋值,就会导致该变量在全局创建,如下所示:

function fn1() {
    // 此处变量name未被声明
    name = new Array(99999999)
}

fn1()

闭包引起的内存泄漏

被遗忘的定时器 循环引用

其实定时器也是平时很多人会忽略的一个问题,比如定义了定时器后就再也不去考虑清除定时器了,这样其实也会造成一定的内存泄漏。来看一个代码示例:

简单总结一下: 大家在平时用到了定时器,如果在用不到定时器后一定要清除掉,否则就会出现本例中的情况。除了setTimeout和setInterval,其实浏览器还提供了一个API也可能就存在这样的问题,那就是requestAnimationFrame

DOM删除时没有解绑事件

没有清理的DOM元素引用

<div id="root">
    <div class="child">我是子元素</div>
    <button>移除</button>
</div>
<script>

    let btn = document.querySelector('button')
    let child = document.querySelector('.child')
    let root = document.querySelector('#root')
    
    btn.addEventListener('click', function() {
        root.removeChild(child)
    })

</script>

该代码所做的操作就是点击按钮后移除.child的节点,虽然点击后,该节点确实从dom被移除了,但全局变量child仍对该节点有引用,所以导致该节点的内存一直无法被释放,可以尝试用Memory的快照功能来检测一下。

控制台的打印

控制台的打印也会造成内存泄漏吗????是的呀,如果浏览器不一直保存着我们打印对象的信息,我们为何能在每次打开控制的Console时看到具体的数据呢?先来看一段测试代码:

<button>按钮</button>
<script>
    document.querySelector('button').addEventListener('click', function() {
        let obj = new Array(1000000)

        console.log(obj);
    })
</script>

简单总结一下:在开发环境下,可以使用控制台打印便于调试,但是在生产环境下,尽可能得不要在控制台打印数据。所以我们经常会在代码中看到类似如下的操作:

// 如果在开发环境下,打印变量obj
if(isDev) {
    console.log(obj)
}

总结

在项目过程中,如果遇到了某些性能问题可能跟内存泄漏有关时,就可以参照本文列举的几种情况去排查,一定能找到问题所在并给到解决办法的。
虽然JavaScript的垃圾回收是自动的,但我们有时也是需要考虑要不要手动清除某些变量的内存占用的,例如你明确某个变量在一定条件下再也不需要,但是还会被外部变量引用导致内存无法得到释放时,你可以用null对该变量重新赋值就可以在后续垃圾回收阶段释放该变量的内存了。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值