也議 js閉包和ie內存泄露原理

能夠, 但當心使用.

閉包也許是 JS 中最有用的特性了. 有一份比較好的介紹閉包原理的文檔.html

有一點須要牢記, 閉包保留了一個指向它封閉做用域的指針, 因此, 在給 DOM 元素附加閉包時, 極可能會產生循環引用, 進一步致使內存泄漏. 好比下面的代碼:算法

function foo(element, a, b) {
  element.onclick = function() { /* uses a and b */ };
}

這裏, 即便沒有使用 element, 閉包也保留了 elementa 和 b 的引用, . 因爲 element 也保留了對閉包的引用, 這就產生了循環引用, 這就不能被 GC 回收. 這種狀況下, 可將代碼重構爲:閉包

function foo(element, a, b) {
  element.onclick = bar(a, b);
}

function bar(a, b) {
  return function() { /* uses a and b */ }
}
————— 谷歌js編碼規範

這是谷歌js編碼規範裏的。"閉包",是個繞不開的話題,我查閱了很多資料,各類解釋都有,關於爲何會形成內存泄漏,也都介紹的很晦澀,沒有把原理講透,在這裏,我就這兩個問題詳細講一下。
首先講閉包。閉包簡而言之,就是一個函數(fa),的內部函數(fb)被fa外的變量引用,就造成了一個閉包;下面給出兩種事例:
1)
function fa() {
        function fb() {
            alert("hello word");
        }
        return fb;
    }
    var myfun = fa();
    myfun();

   

2)
function fa() {
        var e = document.getElementById("id");
        e.event = function () {
            alert("hello word");
        };
    }

這兩種形式的造成閉包的機制不一樣,一個經過一個return 返回這個內部函數,從而被外引用,而另外一個則是經過 e,這個docment這個宿主對象的事件而完成外部引用。這兩種形式造成的結果就是fa這個函數的的內部函數能夠被fa的外部變量所引用,這就造成了閉包。
閉包講到這裏我想你們琢磨一下應該很清楚了,下面咱們來分析下這個內存泄漏是怎麼造成的。不少資料都說循環引用,IE的計數式的垃圾回收機制,但我相信,這些概念很模糊,究竟是怎麼們回事,咱們下面詳細來剖析。

垃圾回收機制如今很成熟了,但早期的IE版本里(ie4-ie6),對宿主對象(也就是document對象)採用是計數的垃圾回收機制,閉包致使內存泄漏的一個緣由就是這個算法的一個缺陷。循環引用會致使無法回收,這個循環引用只限定於有宿主對象參與的循環引用,而js對象之間即便造成循環引用,也不會產生內存泄漏,由於對js對象的回收算法不是計數的方式。

首先咱們明確下內存泄漏的概念:內存裏不能被回收也不能被利用的空間即爲內存泄漏。爲何不能被回收呢?不符合內存回收的算法;爲何不能被利用呢?在棧上沒有指向他的指針。在這裏我簡單的講一下堆和棧的關係:

    function fa() {
        var o = new Object();
    }
    fa();
咱們看這段代碼執行的時候發生了什麼
咱們看到,棧上只是存了一個指針,指針就是堆上對象的的地址;咱們的程序經過這個指針句能夠操做堆上的對象。棧上的這個指針是自動管理的,當函數退出後,就銷燬了;這樣程序就在沒辦法訪問到堆上的這個對象了,而堆上的這個對象這個時候就會被咱們的GC自動回收了;若是回收不了,就是內存泄漏了。

講到這裏你們對內存泄露應該是有所瞭解了,對於計數回收方式你們查下資料,相信你們根據上面講的應該能夠看明白了,這裏再也不詳細描述。下面咱們着重描述下內存泄露的緣由,你們先看下面的代碼:
  function fa() {
        var a = "hello word";
        return function () {
            alert(a);
        }
    }
    var o = fa();
    o();

    這段代碼輸出hello word,這說明什麼?說明在堆上的」hello word‘ 沒有被回收,什麼緣由?由於o這個函數還要引用這個變量。下面咱們用計數的GC方式來逐句分析程序的代碼函數

    在堆上有兩個對象 一個是 hello word 咱們叫作O1,匿名函數function(){alert(a);}咱們叫作O2編碼


當執行
var a = "hello word"; 的時候 O1的計數爲1;
當執行
return function () {
            alert(a);
        }
的時候 O2的計數變爲1;
當執行完fa這個函數後 棧上的 var a 會被銷燬,同是他指向的對象計數減1,這樣問題就來了,這樣O1的計數變爲0了,那不被gc回收了嘛?怎麼還會輸出"hello word"?
原來在執行
return function () {
            alert(a);
        }
這個函數的時候,爲了保持函數對這個變量的引用,在這個匿名函數的做用域鏈上加了一個對O1的引用,這樣 其實 O1的計數在變成了2,在a被銷燬後,O1減變成了1而不是0.
那麼O1時候被回收呢?當O2被回收的時候。O2何時被回收呢?當指向他的var o 從棧上消失的時候。

好,講到這裏,原理咱們講完了下面咱們就看下
function fa() {
        var e = document.getElementById("id");
        e.event = function () {
            alert("hello word");
        };
    }

這段代碼爲何會形成內存泄露spa

var e = document.getElementById("id"); 執行這段代碼的時候 右邊(O1)的對象計數變爲了1

執行這段代碼的時候指針

e.event = function () {
            alert("hello word");
        };

匿名函數(O2)的計數變了1;對象O1的計數變了2;code

當函數fa執行完畢時 棧上的指針var e 消失,他指向的對象 O1的計數減1變爲了1;這樣當函數執行完畢,O一、O2的這兩個對象的計數都爲1,根據計數的回收算法,就都留在內存裏了不能被GC回收了,就形成了內存泄漏。htm

上面說法不徹底正確,實際上執行完fa後O2的計數是2,這個你們能夠想一下緣由。對象

 其實fa裏面的宿主對象只是真正對象一個副本,當執行

e.event 這句指令的時候 作了兩件事,一個是副本的對象指向O2 這時O2的計數加1,真正的宿主對象又指向這個O2,這個O2的計數再加1 變爲了2
因此 在fa的外面執行 e.event=null 的時候,這時O2計數減1變爲了1, 這時候,棧上再沒有指向O2的指針了,因此O2的計數再沒有減小的機會了。這樣O2就永遠存在了,O2存在,那麼O2的做用域鏈指向O1的指針就永遠存在了,因此O1也就永遠存了,這樣O一、O2 就再沒機會釋放了,就形成了內存泄漏。
相關文章
相關標籤/搜索