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

歡迎關注,【前端突擊】 獵鷹突擊,迎難而上,文章會不斷的完善,期待你的加入...

相關文章
相關標籤/搜索