JavaScript以内存泄漏【四】

接着上文闭包,咱们来聊聊内存泄漏,看完下面文章,你将了解到:html

一、什么是内存泄漏
二、有哪些code会致使内存泄漏
三、如何规避内存泄漏
复制代码

1、探究内存泄漏以前,咱们先了解下垃圾回收机制(GC)前端

垃圾回收机制(GC):算法

因为字符串、对象和数组没有固定大小,全部当他们的大小已知时,才能对他们进行动态的存储分配。
JavaScript程序每次建立字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要
像这样动态地分配了内存,最终都要释放这些内存以便他们可以被再用,不然,JavaScript的解
释器将会消耗完系统中全部可用的内存,形成系统崩溃。--《JavaScript权威指南(第四版)》
复制代码

对以上简单的总结就是:垃圾收集器会按期(周期性)找出那些不在继续使用的变量,而后释放其内存。下面就让咱们一块儿看看垃圾收集器是如何回收内存的:数组

垃圾回收机制(gc)分为:标记-清除算法引用计数垃圾收集bash

一、标记-清除算法最为经常使用 在JavaScript中,标记清除是最经常使用的方式. 例如:在函数中声明一个变量,就将这个变量定义为“进入环境”。从逻辑上讲 进入环境的变量其内存不能被释放,由于只要有流执行,这些变量均可能被用到。当变量离开环境后,解释器标记其为“离开环境”,此时,当垃圾收集器,在下个周期时就会回收这部分的内存.闭包

function foo(){
    let num=1;//被标记,进入环境
    let count=60;//被标记,进入环境
}

foo();//函数执行完毕,num,count被标记为离开环境,被gc回收
复制代码

二、引用计数垃圾收集 dom

这个算法把“对象是否再也不须要”简化定义为“对象是否能够得到”

这个算法假定设置一个叫作根(root)的对象(在Javascript里,根是全局对象)。按期的,垃圾回收器将从根开始,找全部从根开始引用的对象,而后找这些对象引用的对象……从根开始,垃圾回收器将找到全部能够得到的对象和全部不能得到的对象。能够得到的对象,将不会被垃圾回收器回收,不能得到的对象将会被垃圾回收器回收,从而达到释放内存的目的. 具体实现:ide

function obj(){
    let a={};//定义a引用对象 初始化 a引用次数为0
    let b=a;//a被引用次数1
    let c=a;//a被引用次数2
    let b={};//因b引用被切断,此时上下文a的引用次数减一,引用次数变为1
    let c=[];//同上此时a的引用次数为0,内存等待回收
}
复制代码

看了上面的例子,咱们再看下理论:函数

解释器经过记录每一个值被引用的次数,来断定该值是否再也不须要.优化

当声明了一个变量(这里为b)并将一个引用类型(function,object,array)值(这里为a),赋值给该变量(b)时,则这个值(a)的引用次数就是1.当这个变量又被赋值给另一个变量时,此时这个值(a)的引用计数加1,一次类推.但当某一个变量,又从新被赋值别的变量(好比b,原来是引用a此时,被赋值一个对象),则原引用类型的值(a),引用此时减一,以此类推...

如上code:a的引用计数为0,对象再也不被使用,等待垃圾收集器回收. 理论上,若是按照以上的原理进行coding,内存是可以及时被回收掉,有效的节省内存资源,但每每事与愿违,下面让咱们一探内存泄漏

2、内存泄漏

内存泄漏(Memory Leak)顾名思义.就是指程序中已动态分配的堆内存因为某种缘由程序未释放或没法释放【gc没法进行回收,好比因:闭包、全局变量的引用、循环引用、子dom的引用、以及事件、定时没有被及时的释放等都会引发内存泄漏】,形成系统内存的浪费,致使程序运行速度减慢甚至系统崩溃等严重后果的现象。

下面咱们来看看,有哪些方面的内存泄漏:

一、首当其冲的是闭包引发的内存泄漏,最为常见

闭包能够维持函数内部变量驻留内存,使其得不到释放.

function onclick(){
    let obj=document.getElementById("button");
    obj.onclick=function(){
        //.
    }
}
复制代码

上例,函数内部定义了一个变量,这个变量的事件引用了内部函数,而且这个事件回调函数的引用外暴了,造成闭包.

解决方法一: 将事件处理函数定义在函数外部,解除闭包.

//defeat out
function onClickHandeler(){
    //to do something
}
function onclick(){
    let obj=document.getElementById("button");
    obj.onclick=onClickHandeler;
}
复制代码

解决方法二: 删除对dom的引用,解除绑定

function(){
    let obj=document.getElementById("button");
    obj.onclick=function onClickHandeler(){
    //to do something
    }
    obj = null;
}
复制代码

二、意外的全局变量引发的内存泄漏

function(){
    menu='navite';//menu为一个全局变量,全局变量常驻内存
}
复制代码

三、没有清理的dom元素的引用

//dom still exist
function click(){
    const button=document.getElementById('button');
    butto.click();
}
// button has removed
function removeButton(){
    document.body.removeChild(document.getElementById('button'));
}
复制代码

四、定时器没有被及时的被销毁

var conentByName=getConentByName();
setInterval(function(){
    let content=document.getElemetById('key_id');
    if(content){
        content.text = conentByName;
    }
},1000);
复制代码

若是key_id这个元素从dom元素移除,那么这个定时仍然存在.由于函数中包含对conentByName的引用,外部对象也得不到释放. 解决办法:在调用以前,对定时进行清除.

clearInterval();
var conentByName=getConentByName();
setInterval(function(){
    let content=document.getElemetById('key_id');
    if(!!content){
        content.text = conentByName;
        return;
    }
    clearInterval();
    conentByName=null;
},1000);
复制代码

五、子元素存在引发的内存泄漏

<div id='parent'>
<div>
<ul>
<li><li>
<li><li>
<li><a class='child'></a><li>
</ul>
</div>
</div>
复制代码
function(){
    let parentById=document.getElementById('parent');
    let childByName=document.getElementByName('child');
    document.body.removeChild(parentById);
}
复制代码

parentById,childByName直接被JavaScript变量引用,中间dom链被间接引用.因此 虽然,parentById层dom元素被删除,但因为childByName被间接引用,致使childByName这条dom链仍然存在.都不会被删除.

解决方案: 把不须要的子元素进行遍历删除

六、监听事件形成的泄漏

this.filterWidget = new FilterWidget(this, {
        categoryList: [{
            title: '0',
            startIndex: 0,
            list: ['zjl','lisi']
        }]
    });
this.filterWidget.on('EVENT_FILTER',  this);
复制代码

上面事件,若是不及时off掉,会形成事件监听的重复注册.

解决办法:调用以前,off掉而且在页面离开时,在生命周期钩子中销毁掉 this.filterWidget.off('EVENT_FILTER', this);

七、对Promise对象,在不用的时候及时的reject掉

以下:

define(function (require) {
    'use strict';
    var View = require('core/View'),
    return View.extend({
        template: templates['page'],
        onInit: function (option) {
            this.providerPromise = this.Provider().done(function (html) {
                    // to do html
                }).fail(function(){
                }); 
        },
        onDestroy:function(){
             this.providerPromise && this.providerPromise.reject();
             this.filterWidget.on('EVENT_FILTER',  this);
        });
});
复制代码

八、循环引用形成的泄漏,只有老的IE版本才有,目前ie9及以上已经优化了这个bug,这里就不作探讨了.

3、规避内存泄漏

一、谨慎使用闭包
a、在业务不须要用到的内部函数,能够重构在函数外,实现解除闭包.
b、闭包内,局部变量使用后或再也不须要,及时的清除掉
二、减小没必要要的全局变量,若是用了,最好在声明周期钩子中或再函数调用以前,及时的清除掉.
三、减小生命周期较长的对象,及时对无用的数据进行释放销毁.
四、避免建立过多的对象,对不用的对象及时的释放.
五、对注册的事件,再不用的时候,及时的解耦.释放资源.
复制代码

JavaScript之Promise

欢迎关注,【前端突击】 猎鹰突击,迎难而上,文章会不断的完善,期待你的加入...

相关文章
相关标签/搜索