垃圾回收
javascript不同於c、c++的一個特點是:具有自動的垃圾回收機制,這就意味著,開發人員可以專注於業務,而不必把過多精力放在記憶體的管理上,提高開發效率。
所謂的垃圾回收就是找出那些不再繼續使用的變數,然後釋放其佔用的記憶體。為此,垃圾收集器會按照固定的時間間隔(或程式碼執行中預定的收集時間),週期性地執行這一操作。
目前在瀏覽器中,實現垃圾回收的方式主要有兩種:
1.標記清除
也是javascript最常用的垃圾回收的方式。
在標記清除的方式中有兩個概念:『進入環境』和『離開環境』。『進入環境』指變數進入執行的環境。『離開環境』指變數完成任務,離開了執行的環境。
垃圾收集器在執行的時候會給儲存在記憶體中的所有變數都加上標記。然後,它會去掉環境中的變數以及被環境中的變數引用的變數的標記。而在此之後再被加上標記的變數將被視為準備刪除的變數,原因是環境中的變數已經無法訪問到這些變數了。最後,垃圾收集器完成記憶體清除工作,銷燬那些帶標記的值並回收它們所佔用的記憶體空間。
2.引用計數
引用計數的含義是跟蹤記錄每個值被引用的次數。當宣告瞭一個變數並將一個引用型別值賦給該變數時,則這個值的引用次數就是1。如果同一個值又被賦給另一個變數,則該值的引用次數加 1。相反,如果包含對這個值引用的變數又取得了另外一個值,則這個值的引用次數減 1。當這個值的引用次數變成 0 時,則說明沒有辦法再訪問這 個值了,因而就可以將其佔用的記憶體空間回收回來。這樣,當垃圾收集器下次再執行時,它就會釋放那 些引用次數為零的值所佔用的記憶體。
這種機制其實在js中並不常用,因為這種機制會產生迴圈引用的問題,『迴圈引用』指的是物件 A 中包含一個指向物件 B 的指標,而物件 B 中也包含一個指向物件 A 的引用。對於像js類的自動回收機制的語言來說,需要額外手動的去釋放記憶體,其實並不友好。例如,在下面的例子中:
function garbage(){
var A = new Object();
var B = new Object();
A.property = B;
B.property = A;
}
由於物件A、B相互引用,引用次數都為2,所以其佔用的記憶體並不能被自動釋放。
在IE的BOM和DOM中就採用了此類的引用計數,在IE9以後,微軟轉而使用了標記清除的方式來管理記憶體,從而避免了兩種方式並存的局面。
記憶體管理
由於瀏覽器和其他桌面程式相比,系統分配給瀏覽器的記憶體相對較少,這樣做的目的主要是出於安全方面的考慮,防止執行 JavaScript 的網頁耗盡全部系統記憶體而導致系統崩潰。記憶體限制問題不僅會影響給變數分配記憶體,同時還會影響呼叫棧以及在一個執行緒中能夠同時執行的語句數量。
因此,確保佔用最少的記憶體可以讓頁面獲得更好的效能。而優化記憶體佔用的最佳方式,就是為執行中的程式碼只儲存必要的資料。一旦資料不再有用,最好通過將其值設定為 null 來釋放其引用——這個做法叫做解除引用(dereferencing)。這一做法適用於大多數全域性變數和全域性物件的屬性。區域性變數會在它們離開執行環境時自動被解除引用。
function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
}
var globalPerson = createPerson("Nicholas"); // 手工解除 globalPerson 的引用
globalPerson = null;
不過,解除一個值的引用並不意味著自動回收該值所佔用的記憶體。解除引用的真正作用是讓值脫離 執行環境,以便垃圾收集器下次執行時將其回收。
小結
JavaScript 是一門具有自動垃圾收集機制的程式語言,開發人員不必關心記憶體分配和回收問題。可 以對 JavaScript 的垃圾收集例程作如下總結。
離開作用域的值將被自動標記為可以回收,因此將在垃圾收集期間被刪除。
“標記清除”是目前主流的垃圾收集演算法,這種演算法的思想是給當前不使用的值加上標記,然
後再回收其記憶體。
另一種垃圾收集演算法是“引用計數”,這種演算法的思想是跟蹤記錄所有值被引用的次數。JavaScript
引擎目前都不再使用這種演算法;但在 IE 中訪問非原生 JavaScript 物件(如 DOM 元素)時,這種
演算法仍然可能會導致問題。
當程式碼中存在迴圈引用現象時,“引用計數”演算法就會導致問題。
解除變數的引用不僅有助於消除迴圈引用現象,而且對垃圾收集也有好處。為了確保有效地回
收記憶體,應該及時解除不再使用的全域性物件、全域性物件屬性以及迴圈引用變數的引用。
—參考《JavaScript高階程式設計第3版》