垃圾回收機制
自動垃圾收集機制
- javascript具有自動垃圾收集機制猴贰,也就是說河狐,執(zhí)行環(huán)境會負責管理代碼執(zhí)行過程中使用的內(nèi)存瑟捣。即開發(fā)人員不需要當心內(nèi)存使用的問題栅干,所需內(nèi)存的分配以及無用內(nèi)存的回收完全實現(xiàn)了自動管理碱鳞。這種垃圾回收機制的原理:找出那些不再繼續(xù)使用的變量,然后釋放其內(nèi)存窿给。為此崩泡,垃圾收集器會按照固定的時間間隔,周期性地執(zhí)行這一操作呛伴。
局部變量的生命周期
- 局部變量只在函數(shù)執(zhí)行的過程中存在谒所。在這個過程中,會為局部變量在棧內(nèi)存或堆內(nèi)存上分配相應(yīng)的空間褐隆,以便存儲他們的值剖踊。然后再函數(shù)中使用這些變量,直至函數(shù)執(zhí)行結(jié)束歇攻。此時梆造,局部變量就沒有存在的必要了,因此可以釋放他們的內(nèi)存以供將來使用屡穗。
javascript中的2中垃圾回收機制
標記清除(Mark-and-sweep)
- 包括標記階段和清除階段忽肛。
-
標記階段:垃圾回收器創(chuàng)建了一個
roots
列表屹逛。Roots
通常是代碼中全局變量的引用汛骂。javascript中评腺,window
對象是一個全局變量,被當作root
图张。window
對象總是存在诈悍,因此垃圾回收器可以檢查他和他所有子對象是否存在(即不是垃圾)。所有的roots
被檢查和標記為激活(即不是垃圾)适袜。所有的子對象也被遞歸檢查舷夺,檢查從root
開始的所有對象,如果是可達的疫萤,則打上標記敢伸。標記階段到此結(jié)束池颈。 - 清除階段:標記階段完成時,被標記的對象被視為“存活”對象躯砰。然后重新進入遞歸掃描階段琢歇,將沒有標記的對象進行回收。這一階段稱為清除階段揭保。在掃描的同時涌矢,還需要將存活的對象的標記清除掉,以便下一次GC操作做準備塔次。
引用計數(shù)(reference counting)
- 引用計數(shù)的含義是跟蹤記錄每個值被引用的次數(shù)名秀。當聲明了一個變量并將一個引用類型賦給該變量時匕得,則這個值的引用次數(shù)就是1。如果同一個值又被賦給另一個變量汁掠,則將該引用次數(shù)再加1考阱。相反,如果包含對這個值得引用變量又取得了另一個值乞榨,則這個值的引用次數(shù)就減1吃既。當這個值得引用次數(shù)變?yōu)?時,則說明沒有辦法在訪問這個值了河质,因而久可以將其占用的內(nèi)存空間回收回來震叙。這樣,垃圾回收器下次再運行時淫半,會釋放那些引用次數(shù)為0的值所占用的內(nèi)存匣砖。
內(nèi)存泄漏
- 內(nèi)存泄漏是指應(yīng)用程序使用過的內(nèi)存片段猴鲫,在不需要的時候,不能返回到操作系統(tǒng)或可用的內(nèi)存池中的情況拂共。javascript中的內(nèi)存泄漏同理宜狐,當一個值不再需要被引用了蛇捌,但卻無法被垃圾回收器回收的情況咱台,就是內(nèi)存泄露回溺。
javascript中3種常見的內(nèi)存泄漏
1. 意外的全局變量
舉個例子(普通變量):
function fun(){
globalVar = "this is a hidden global variable";
}
//在函數(shù)體內(nèi)部定義變量,如果沒有使用var聲明萍恕,則定義的變量是全局變量车要,即成為window的一個屬性
//而window總是存在,因此不會被回收
再舉個例子(由this創(chuàng)建的變量):
function foo() {
this.variable = "potential accidental global";
console.log(this)
}
// Foo 調(diào)用自己维哈,this 指向了全局對象(window)
// 而不是 undefined
foo();
-
解決方案:在js文件頭部加上(ES5)
use strict
阔挠,使用嚴格模式脑蠕,可以避免此類錯誤的發(fā)生谴仙。如果必須使用全局變量存儲大量數(shù)據(jù)時,確保用完以后把它設(shè)置為 null 或者重新定義揩局。
2.被遺忘的計數(shù)器或者回調(diào)函數(shù)
舉個例子:
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
// 處理 node 和 someResource
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);
//重復(fù)計數(shù)器并沒有使用 clearInterval()終止掀虎,導致內(nèi)存無法回收
- 對于觀察者的例子,一旦它們不再需要(或者關(guān)聯(lián)的對象變成不可達)驰怎,明確地移除它們非常重要二打。老的 IE 6 是無法處理循環(huán)引用的。如今症杏,即使沒有明確移除它們鸳慈,一旦觀察者對象變成不可達喧伞,大部分瀏覽器是可以回收觀察者處理函數(shù)的。
3. 脫離DOM的引用
- 有時翁逞,保存 DOM 節(jié)點內(nèi)部數(shù)據(jù)結(jié)構(gòu)很有用溉仑。假如你想快速更新表格的幾行內(nèi)容浊竟,把每一行 DOM 存成字典(JSON 鍵值對)或者數(shù)組很有意義。此時必怜,同樣的 DOM 元素存在兩個引用:一個在 DOM 樹中后频,另一個在字典中卑惜。將來你決定刪除這些行時,需要把兩個引用都清除更米。
var elements = {
button: document.getElementById('button'),
image: document.getElementById('image'),
text: document.getElementById('text')
};
function doStuff() {
image.src = 'http://some.url/image';
button.click();
console.log(text.innerHTML);
// 更多邏輯
}
function removeButton() {
// 按鈕是 body 的后代元素
document.body.removeChild(document.getElementById('button'));
// 此時毫痕,仍舊存在一個全局的 #button 的引用
// elements 字典所引用的button 元素仍舊在內(nèi)存中,不能被 GC 回收眶痰。
}
4.閉包(正常情況下不會造成內(nèi)存泄漏)
由于IE9 之前的版本對JScript 對象和COM 對象使用不同的垃圾收集梯啤。因此閉包在IE 的這些版本中會導致一些特殊的問題。具體來說七婴,如果閉包的作用域鏈中保存著一個HTML 元素打厘,那么就意味著該元素將無法被銷毀。
reference -- javascript高級程序設(shè)計第三版
舉個例子:
function closure(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
- 以上代碼創(chuàng)建了一個作為element 元素事件處理程序的閉包嵌施,而這個閉包則又創(chuàng)建了一個循環(huán)引用吗伤。由于匿名函數(shù)保存了一個對closure()的活動對象的引用硫眨,因此就會導致無法減少element 的引用數(shù)。只要匿名函數(shù)存在礁阁,element 的引用數(shù)至少也是1巧号,因此它所占用的內(nèi)存就永遠不會被回收。
解決方案:把element.id 的一個副本保存在一個變量中姥闭,從而消除閉包中該變量的循環(huán)引用同時將element變量設(shè)為null丹鸿。
function closure(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
- 因此可以看出,閉包并不會引起內(nèi)存泄漏泣栈,只是由于IE9之前的版本對JScript對象和COM對象使用不同的垃圾收集卜高,從而導致內(nèi)存無法進行回收,這是IE的問題南片,所以正常使用閉包的情況下不會導致內(nèi)存泄漏掺涛。