说说内存泄漏的常见场景

258 阅读3分钟

什么是内存泄露

内存泄露也称作"存储泄漏",用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元,直到程序结束。简单说就是内存空间使用完毕之后未回收。

内存泄露的常见场景:

循环引用

当两个或多个对象彼此引用时,即使没有其它代码引用他们,即使没有其它代码引用他们,他们也无法被回收。这种情况常见于对象之间的相互引用或闭包的引用

// 就如上文垃圾回收机制说的引用计数法导致的循环引用问题一样,两个或多个对象彼此引用导致内存泄漏
// 内存泄漏示例
    function fun(){
        var objA={};
        var objB={};
        objA.oneProp=objB;
        objB.anoterProp=objA;
    }
    
// 正确写法 
    function fun(){
        var objA={};
        var objB={};
        objA.oneProp=objB;
    // 修改为null或undefined
        objB.anoterProp=null;
    // 或者objB.anoterProp=undefined;
    }

解决:改为null或undefined

闭包

闭包引起的内存泄漏是指由于引用链的存在,变量无法被垃圾回收机制及时释放造成的内存浪费问题。当一个函数引用了外部的变量,并且该函数被其他对象所引用时,就会形成一个闭包,而这个闭包会保留对外部变量的引用,使得这些变量无法被垃圾回收机制回收。

    function createClosure() {
      var bigData = new Array(1000000).join('*');
      return function() {
        console.log(bigData);
      }
    }

    var myClosure = createClosure();

// 正确写法
// 如果不手动解除引用  myClosure 并不能被垃圾回收 导致 bigData 内存无法释放
    myClosure = null;

解决:手动解除引用,在不需要使用闭包时,可以显式解除对闭包的引用。将闭包赋值为 null 或者其他非引用值,以便让垃圾回收机制回收相关资源

意外的全局变量

JS中未声明的变量会默认定义在全局变量window上,只有关闭窗口或者刷新页面时,全局变量才会被释放。如果未声明的变量存储了大量的数据,这样就会造成内存泄露

// 内存泄漏示例
    function fun(){
        globalVar='全局变量';
    }

// 正确写法
    function fun(){
        // 使用var声明变量,在方法内部是局部变量,在方法外部是全局变量
        var localVar='局部变量';
    }

解决:使用var、let或const声明的变量,避免创建全局变量

定时器未清理

如果在使用定时器函数,未正确清除时,定时器的函数或闭包会一直存在内存中,直到定时器被取消或到期

// 内存泄漏示例
    function timer(){
        setInterval(function(){
            console.log(1);
        },1000)
    }
    
//正确写法
    var timerId=setInterval(function(){
        console.log(1);
    })
// 清除定时器
    clearInterval(timerId);

解决:清除定时器:clearTimeout()或clearInterval()

DOM元素的引用

在JavaScript中,对DOM元素的引用会阻止该元素被垃圾回收,如果不再需要这些引用而没有及时解绑,就可能导致内存泄露

// 内存泄漏示例
    var element = document.getElementById('myElement');
    var references = [];

    function createReference() {
      references.push(element);
    }
    
// 正确写法
    var element = document.getElementById('myElement');
    var references = [];

    function createReference() {
      references.push(element);
    }
// 移除引用事件
    function removeReference() {
      references.splice(references.indexOf(element), 1);
    }

解决:及时移除引用事件

事件监听

同一个事件重复监听,但是忘记移除,会导致内存泄露


// 内存泄漏示例
    var element = document.getElementById('myElement');
    element.addEventListener('click', function handleClick() {
         console.log(1)
    });

// 正确写法
    var element = document.getElementById('myElement');
    function handleClick() {
      console.log(1)
    }
    element.addEventListener('click', handleClick);
// 在不需要监听事件时,解除绑定
    element.removeEventListener('click', handleClick);

解决:及时在不需要监听事件时,移除监听事件