原文在https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management.基本保持了平譯,並在一些地方作了概念解釋。(轉載請註明出處)javascript
高級語言,好比C,有底層的內存管理原語如malloc()和free()。可是對於Javascript來講,變量(string、object等等)被建立的時候分配內存,等變量再也不被使用的時候,內存被"自動"釋放。這個自動釋放的過程叫作"垃圾回收"。「自動」這個詞常令人困惑而且給了javascript(以及其餘的高級語言)開發者他們不須要關心內存管理的印象。這是錯誤的。java
忽略具體的變成語言,內存的生命週期一般是這樣的:程序員
第二部分在全部的語言裏都是明顯的。第一和第三部分在低級語言裏是明顯的,可是在高級語言如JavaScript裏是很是隱祕的。算法
爲了避免讓程序的書寫者操心內存分配問題,JavaScript隨着變量的聲明自動分配了內存。數組
var n = 123; //爲1個數字分配了內存 var s = 'azerty';//爲一個字符串分配了內存 var o = { a:1, b:null };//爲對象和裏邊的鍵值分配了內存 //(和對象同樣),爲數組和裏邊的元素分配了內存 var a = [1,null,'abra']; function f(a){ return a + 2; }//爲一個函數分配了內存(函數是可調用對象) //函數表達式也做爲1個對象被分配了內存 //譯者注:函數的表達式指的是下面的匿名函數的定義部分,JavaScript的函數能夠做爲函數的參數傳入,由於他是一種特殊的對象 someElement.addEventListener('click', function() { someElement.style.backgroundColor = 'blue'; }, false);
一些函數調用的結果分配了內存瀏覽器
var d = new Date(); // 建立了一個Date對象,並分配了內存 var e = document.createElement('div'); // 建立了一個Dom元素,分配了內存
一些函數會分配新的變量或對象:併發
var s = 'azerty'; var s2 = s.substr(0, 3); // s2 是一個新的字符串 // 由於字符串是不可變對象, // JavaScript 可能不會再爲s2分配內存, 而僅僅讓s2記錄0-3這個區間 var a = ['ouais ouais', 'nan nan']; var a2 = ['generation', 'nan nan']; var a3 = a.concat(a2); // 新的數組s3有4個元素,鏈接了a和a2
使用內存基本上就是讀寫已分配的內存。好比讀寫變量的值,對象的屬性,又或者給函數傳遞參數(譯者:這裏是說變量做爲參數傳遞到函數的行爲,就是使用這個變量內存的過程)。dom
這個階段會產生許多內存管理的問題。最難的問題是找到何時「已分配的內存再也不須要」,這要求開發者去檢測什麼地方的內存再也不須要,而後釋放它們。函數
高級語言嵌入了一個叫作「垃圾回收」的功能,它的做用是追蹤內存分配,當發現一塊已分配的內存再也不須要的時候,自動釋放它。這是一個近似的過程,由於「判斷一段內存是否還須要保留」是不可斷定的(undecidable .can't be solved by an algorithm.這裏能夠理解爲,分配的內存還用不用只有程序員主觀的知道,若是他忘了,會致使內存泄漏,若是程序員手賤,釋放的早了,再訪問這塊內存就會致使內存訪問錯誤。語言的垃圾回收機制更像是一個第三者,雖然他很聰明,算法很強大,很能揣測程序意圖,可是畢竟不是本人)。code
垃圾回收算法的主要理論依據是一個叫「引用」的概念。在內存管理的上下文中,若是一個對象有權利訪問另外一個對象(的內存,不管是顯式仍是隱式),會讓前者去引用後者。好比,Javascript對象有對他的原型隱式的引用,和對它屬性顯式的引用。
在內存管理的上下文中,「對象」的概念比一般的JavaScript的對象(Object)更爲寬泛,甚至包含函數做用域,或者全局的詞法做用域。
這是最簡單的垃圾回收算法。這種算法將「一個內存中對象再也不被須要」簡化爲"一個內存對象再也不被別的對象引用"。若是一個對象被引用的數量爲0,這個對象就被認爲應該被回收了。(譯者注:大多數此類算法都要求被管理的全部對象都有一個相似referenceCount的屬性,標識本身被引用了多少次)。
var o = { a:{ b:2 } }; //2個對象被建立出來,1給被引用爲對象的屬性,而後外部的對象被變量o引用。顯然他們都不會被回收 var o2 = o;//變量o2又引用了o引用的對象 o = 1;//如今變量o再也不引用以前的對象,以前的對象只被o2引用了 var oa = o2.a;//如今做爲對象屬性的對象,又被變量oa引用了 o2 = 'yo';//如今最初被o引用的對象已經再被引用了,能夠被回收,可是它的a屬性還被oa引用,因此還不能被釋放 oa = null;//如今做爲a屬性的對象也再也不被引用了,最初被o引用的對象能夠被回收釋放了。
當引用出現循環,這種算法就會出現限制。下面的例子裏,2個對象被建立出來,而且彼此被另外一個引用,致使循環引用。函數執行完成後,他們會離開函數做用域,再也不起做用了,能夠被釋放了。可是,引用計數算法認爲他們2個都被引用了一次(函數接收後,變量o,o2會解掉對對象的引用),不能被回收。
function f() { var o = {}; var o2 = {}; o.a = o2; // o引用的對象的屬性a 引用 o2 o2.a = o; // o2引用的對象的屬性a 引用 o return 'azerty'; } f();
IE6和IE7使用引用計數管理DOM對象,因此循環引用是常見的致使內存泄漏的錯誤。
var div; window.onload = function() { div = document.getElementById('myDivElement'); div.circularReference = div; div.lotsOfData = new Array(10000).join('*'); };
這個例子裏,dom元素"myDivElement"被本身的circularReference屬性所引用(div.circularReference = div;)。若是這個屬性沒有明確的移除或者置爲null,即便myDivElement這個元素從DOM樹裏移除了,引用計數垃圾收集器還將老是擁有至少一個這個元素的引用,而且一直存在於內存中。若是DOM元素持有大量數據(好比這個例子裏的lotsOfData屬性),這個數據消耗的內存將不會被釋放回收。
這個算法將"一個內存中對象再也不被須要"簡化爲"一個內存對象不可達"。(譯者注:原文是an object is unreachable。內存中的對象,若是程序沒法經過一些線索,如引用關係找到它,它就沒有存在的意義,是不可達的)。這種算法假定知道一組叫作根的對象,週期性的從這些「根」開始,查找到全部被根引用的對象,而後從這些對象開始,遞歸的查找下去。所以,從根開始查找的過程當中,垃圾收集器將會找到全部可達的內存對象和不可達的內存對象。
這個算法比上一個要好,由於「一個被0引用」的內存對象能夠推導出這個內存對象不可達,相反,出現循環引用的時候,反推卻不成立。
截止2012年,全部的現代瀏覽器都搭載了標記-清除垃圾回收器。過去幾年裏,JavaScript 的垃圾回收領域(分代/增量/併發/並行垃圾回收)技術的更新實現了這種算法的進步,可是對垃圾回收算法自己:何時"一個內存對象再也不須要"這個概念的處理,並無多大進步。(原文:but not improvements over the garbage collection algorithm itself nor its reduction of the definition of when "an object is not needed anymore).
第一個例子裏,函數結束後,2個對象再也不被由全局對象可達的對象所引用,所以,他們被垃圾回收器認爲是不可達的。
即便這被標註爲一個限制,可是在實踐中不多遇到,這也是爲何不多有人會常常關注垃圾收集的緣由(說的是,在當前標記清除的技術下,程序員們再也不關注垃圾回收這個問題了)。