垃圾回收與內存泄漏

原文地址: https://www.xingkongbj.com/blog/js/garbage-collection.htmlhtml

垃圾回收--引用計數

將資源的被引用次數保存起來,當被引用次數變爲零時就將其釋放的過程。node

會致使更多的內存泄漏,已不被採用。數組

致使的特殊內存泄漏

循環引用致使內存不能正常被回收瀏覽器

// 函數 a 執行完後,原本 x, y 對象都應該在垃圾回收階段被回收, 但是因爲存在循環引用,也不能被回收。

function a () {
    var x = {};
    var y = {};
    x.z = y;
    y.z = x;
}
a();

IE 6, 7 對DOM對象進行引用計數回收,這樣簡單的垃圾回收機制,很是容易出現循環引用問題致使內存不能被回收, 進行致使內存泄露等問題。閉包

!function(){
    // IE 6, 7 中下列代碼會致使 btn 不能被回收
    var btn = document.getElementsByTagName('button');
    btn.onclick = function(){
        console.log(btn.innerHTML);
    };
}();

垃圾回收--標記清除

標記清除的方式須要對程序的對象進行兩次掃描,第一次從根(Root)開始掃描,被根引用了的對象標記爲不是垃圾,不是垃圾的對象引用的對象一樣標記爲不是垃圾,以此遞歸。全部不是垃圾的對象的引用都掃描完了以後。就進行第二次掃描,第一次掃描中沒有獲得標記的對象就是垃圾了,對此進行回收。app

大多數瀏覽器採用此回收機制。dom

內存泄漏

意外的全局變量

function foo(arg) {
    // 沒有 var 時,默認全局
    bar = {};
}

偶然建立全局變量

function foo() {
    this.bar = {};
}
// 此時執行 foo 函數,this 實際上是指代 window
foo();

沒有及時清除的定時器和回調函數

// 未清除定時器
!function(){
    var node = document.getElementById('search');
    setInterval(function() {
        console.log(node);
        if(node) {
            node.innerHTML = new Date();
        }
    }, 1000);
    // 移除node節點
    node.remove();
}();

// 未清除回調函數
!function(){
    var element = document.getElementById('button');
    function onClick(event) {
        element.innerHtml = 'text';
    }
    element.addEventListener('click', onClick);
    // 在 IE六、IE7 瀏覽器上,移除 node 以前須要手動的 removeEventListener,這樣才能保證 element 被正常回收(其實就是循環引用)
    element.removeEventListener('click', onClick);
    element.parentNode.removeChild(element);
}();

DOM以外的引用

<div id="root">
    <div id="app">
        <div id="son"></div>
    </div>
</div>

var app = document.getElementById('app');
var son = document.getElementById('son');
app.remove(); // 移除 app 節點
app = null; // 釋放 app 引用
console.log(son.parentNode); // app 可訪問,son 在全局的引用,致使 app 的 dom 沒有被釋放

閉包被外部引用

var replaceThing = function () {
      // 爲了方便觀察內存狀況,建立了一個很大的字符串,這樣數組自己會佔用很大的內存
      var originalThing = new Array(100000000).join('*');
      var outer = 'outer str';
      console.log('new...');
      return function () {
        if (originalThing)
          console.log(outer);
      };
    };
// 因爲生成的函數被 closureFn 引用,因此內存不釋放
var closureFn = replaceThing();
closureFn();
// 使用如下方式能夠獲得釋放
replaceThing()();

多重閉包

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing)
      console.log('hi');
  };
  theThing = {
    // 爲了方便觀察內存狀況,建立了一個很大的字符串,這樣數組自己會佔用很大的內存
    longStr: new Array(100000000).join('*'),
    someMethod: function () {
      console.log('123');
    }
  };
};
// 每1秒執行一次,看內存變化
setInterval(replaceThing, 1000);
// 因爲 unused 和 theThing.someMethod 在一個閉包內,致使共享一個閉包做用域,從而關聯兩個函數,可是不會形成對 originalThing 的關聯。originalThing 的關聯是由 unused 內部引用致使的。因而可知 replaceThing 內部的變量都被緊密聯繫在一塊兒。

如何避免內存泄漏

  • 儘可能避免使用全局變量,例如使用當即執行函數的形式。
  • 使用「嚴格模式」開發,避免由於咱們的疏忽致使意外產生全局變量。
  • 對於一些佔用內存較大的對象,在變量不在使用後,手動將其賦值爲 null,例如前面例子中的超大的數組。
  • 儘可能避免把閉包內部與外部產生關聯引用,例如上面例子中的 theThing 變量
相關文章
相關標籤/搜索