JavaScript的垃圾回收機制與記憶體洩漏
常用的兩種演算法:
引用計數(新版瀏覽器已棄用,棄用原因:會出現迴圈引用的情況,無法進行垃圾回收,導致記憶體洩漏)
標記清除
引用計數法
引用計數,顧名思義一個物件是否有指向它的引用,即看棧中是否有指向要釋放的該塊堆記憶體中的地址,如果沒有,則該塊記憶體是不需要的,可以進行釋放,即垃圾回收
下面引用大佬的一個簡短例子來說明情況
1 // 建立一個物件person,他有兩個指向屬性age和name的引用 2 var person = { 3 age: 12, 4 name: 'aaaa' 5 }; 6 7 person.name = null; // 雖然name設定為null,但因為person物件還有指向name的引用,因此name不會回收 8 9 var p = person; 10 person = 1; //原來的person物件被賦值為1,但因為有新引用p指向原person物件,因此它不會被回收 11 12 p = null; //原person物件已經沒有引用,很快會被回收 13
缺點:引用計數有一個致命的問題,那就是迴圈引用
當兩個物件相互引用,儘管他們已不再使用,但是垃圾回收器不會進行回收,最終可能會導致記憶體洩露。
1 function cycle() { 2 var o1 = {};//1 3 var o2 = {};//1 4 o1.a = o2;//2 5 o2.a = o1; //2 6 return "cycle reference!" 7 } 8 9 cycle();
cycle
函式執行完成之後,物件o1
和o2
實際上已經不再需要了,但根據引用計數的原則,他們之間的相互引用依然存在,因此這部分記憶體不會被回收。所以現代瀏覽器不再使用這個演算法。
但是IE依舊使用。
1 var div = document.createElement("div"); 2 div.onclick = function() { 3 console.log("click"); 4 };
上面的寫法很常見,但是上面的例子就是一個迴圈引用。
變數div有事件處理函式的引用,同時事件處理函式也有div的引用,因為div變數可在函式內被訪問,所以迴圈引用就出現了。
標記清除(常用)
文章裡寫的是:標記清除演算法將“不再使用的物件”定義為“無法到達的物件”。即從根部(在JS中就是全域性物件)出發定時掃描記憶體中的物件,凡是能從根部到達的物件,保留。那些從根部出發無法觸及到的物件被標記為不再使用,稍後進行回收。
我這裡個人理解:不在原型鏈上的,不能從全域性物件鏈找到的物件,會被認為是無法到達的物件(也可能我自己理解有誤,忘讀者指出),比如說下面這個例子
1 var a = {} // 這裡的a是掛在全域性物件上的 2 3 a = null // 這裡a之前存放指向{}的地址變成了null 4 5 // 此時{}是無法找到的,通過全域性物件找到a也無法到達{},因此{}會被垃圾回收
無法觸及的物件包含了沒有引用的物件這個概念,但反之未必成立。
所以上面的例子就可以正確被垃圾回收處理了。
所以現在對於主流瀏覽器來說,只需要切斷需要回收的物件與根部的聯絡,就能進行垃圾回收
下面還是引用大佬的例子
最常見的記憶體洩露一般都與DOM元素繫結有關:
email.message = document.createElement(“div”); displayList.appendChild(email.message); // 稍後從displayList中清除DOM元素 displayList.removeAllChildren();
上面程式碼中,div
元素已經從DOM樹中清除,但是該div
元素還繫結在email物件中,所以如果email物件存在,那麼該div
元素就會一直儲存在記憶體中
參考文章:(https://www.muyiy.cn/blog/1/1.4.html#垃圾回收演算法)