原文地址: 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 變量