内存泄漏| 青训营笔记

119 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的第3天

一、了解定义

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

注意:内存泄漏可能是网页卡顿的原因之一,这时候可以检查网页的内存是否泄漏。

内存分两种:

栈内存:存储简单数据类型(Number、String、Undefined、Null、Boolean)

堆内存:存储复杂对象数据类型(Object、Array、Set、Map等)

二、内存泄漏JS 内置的解决方法

垃圾自动回收机制。但不意味着开发人员不需要管理内存,有时候也是需要考虑手动清除某些变量的。

下面的第三点就列出了需要我们考虑内存泄漏的各种场景以及解决方案。

二、内存泄漏出现的各种场景,以及解决方案

1、意外的全局变量

全局变量一般是不会被垃圾回收掉的,也不是说不能设置全局变量,而是一些未定义先赋值的意外流失到全局的变量

function x(){
    x1 = new Array(9999) //未定义先赋值的变量,这么大的数组,意外的流失到全局,成为全局变量,于是会一直占用内存
    //或者使用this,也会意外成为全局变量
    this.x2 = 'abc'
}
x()

解决:

可以在JavaScript文件开头添加‘use strict’,使用严格模式,可以避免产生意外的全局变量;

同时开发时要特别注意临时存储或处理大量数据的全局变量,不再使用时需要手动设置为null,否则尽量使用局部变量。

2、闭包

函数在结束调用时,它们的引用也会被清除。但是闭包可以在函数执行完后,执行上下文和变量环境已经消除的情况下,依然保持对变量的引用。

这也意味着随着函数的多次调用,闭包所保留的数据可能会不断增长。

解决:

闭包是不可避免的,是JavaScript不可或缺的一种方式。但是我们可以在使用闭包时,根据闭包的预期寿命手动清除被引用的变量,或闭包本身。

3、被遗忘的定时器

function callBack() {
    const data = {
        count:0,
        arr: new Array(9999)
    }
    return function() {
        console.log(++data.count)
    }
}
let t = setInterval(callBack(), 5000); //每五秒会执行一次

回调函数中的data.arr虽然一直没有被使用,但只要定时器的回调函数是可调用的,回调中的对象就会一直保存在内存中。

解决:平时用计时器就一定要考虑到使用后的清除。

function callBack() {
    const data = {
        count:0,
        arr: new Array(9999)
    }
    return function() {
        console.log(++data.count)
        if(data.count>3){//某个清除定时器的条件
            clearInterval(t)
        }
    }
}
let t = setInterval(callBack(), 5000); //每五秒会执行一次

4、DOM删除时没有解绑事件

dom被删除时,dom上的监听事件也要使用removeEventListener 取消对事件的监听

5、没有清理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变量对该节点还存在引用,所以导致该节点的内存一直咩有被释放。

解决:

<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)
    //1、可以改为用childNodes代替新建一个child变量引用该节点
    root.removeChild(root.childNodes[0])
})
//2、或者使用完child后手动回收该变量
child = null
</script>

6、控制台的打印

当前页面运行时,浏览器会一直保存我们打印的信息

解决:

开发环境下可以使用控制台打印调试,但是在生产环境下尽可能不要在控制台打印。

  未完待续:如何查询网页内存占用情况,如何检查是否内存泄漏?