JS垃圾回收机制和内存泄漏

一、浏览器内存泄漏

浏览器封装的V8引擎支持对JS进行解析,当程序运行(runtime)时,只要程序提出需要内存,例如声明赋值字符串、对象、数组等变量时;操作系统就必须给分配内存使用,对于持续运行的服务进程,变量不断增加,并且内存没有得到及时的释放,内存占有会越来越大,轻则影响系统性能,重则直接导致系统崩溃。

1、闭包加引用

    let fn = () =>{
        let a = 0
        return () => {
            return a++
        }
    }

    let b = fn()
    console.log(b())    // 0
    console.log(b())    // 1

闭包在IE6下会造成内存泄漏,但是现在已经无须考虑了。虽然变量a无法被回收(相当于全局变量),但是这点内存无须特别考虑,值得注意的是闭包本身不会造成内存泄漏,但闭包过多很容易导致内存泄漏。
2、相互引用

let aa = {}
let bb = {}
aa.c = bb
bb.c = aa

解决办法:不要互相引用变量
4、全局未声明的变量

    function fn1() {
        aa = 1  //  window.aa
        console.log(aa)
    }
    fn2()

    function fn2() {
        this.bb = 1 //  window.bb
        console.log(bb)
    }
    fn2()

函数fn1和fn2均未声明,即将变量绑定到了全局对象window下,这样函数执行完毕均不能释放变量内存。

解决办法:使用严格模式,在js顶部加上‘use strict’

5、定时器和回调函数

    let a = 2
    setInterval(() =>{
        console.log(a++)
    }, 1000)

当不需要setInterval或者setTimeout时,定时器没有被clear,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。

6、DOM对象添加属性

document.getElementById('id')[property] = obj

DOM如果存在会一直绑定这个事件,解决办法如下:

window.onunload = function(){
  document.getElementById('id')[property] = null
}

7、vue中的定时器,事件监听,$on等
解决办法:在beforeDestroy钩子中移除或关闭相关事件

借鉴链接内存泄漏

二、垃圾回收机制

变量的生命周期:当一个变量的生命周期结束之后它所指向的内存就应该被释放。JS有两种变量,全局变量和在函数中产生的局部变量(不包括被引用的变量)。局部变量的生命周期在函数执行过后就结束了,此时便可将它引用的内存释放(即垃圾回收),但全局变量生命周期会持续到浏览器关闭页面。垃圾收集器会检测不再继续使用的变量,然后释放其占用的内存,并按照固定的时间间隔周期性的执行。

现代浏览器垃圾回收机制主要有两种:标记清除和引用计数。
1、标记清除(mark-and-sweep)
目前主流IE,Firefox,Opera,Chrome和Safari等浏览器均使用标记清除的垃圾收集策略。
工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。

工作流程:
1.垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。
2.去掉全局中的变量以及被环境中的变量引用的变量的标记。
3.被加上标记的会被视为准备删除的变量。
4.垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。

2、引用计数(reference counting)
引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1,如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值的引用的变量又取得了另外一个值,则这个值的引用次数减1,当这个值的引用次数为0时,则说明没有办法再访问这个值了,因此就可以将其占用的内存空间回收回来。

let aa = []   // 数组[]引用1次
let bb = aa  //  数组引用2次
bb = null  //  释放内存,引用还剩下1次,即变量aa的引用还存在

这样简单的垃圾回收机制,非常容易出现循环引用问题导致内存不能被回收, 进行导致内存泄露等问题; 如:

let aa = {}
let bb = {}
aa.c = bb
bb.c = aa
var wraper = document.querySelector('#btn');
wraper.onclick = handle;
wraper = null;

该方法并不能解除dom点击事件,因为释放的是wraper引用的这个变量,实际对象DOM已经绑定了这个btn点击事件,举个例子

    let a = {}
    let b = a
    b.btn = () =>{}
    b = null
    console.log(a)
对象引用.png
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容