JavaScript具備自動垃圾收集機制,也就是說,執行環境會負責管理代碼執行中使用的內存。在C和C++語言中,開發人員一項基本任務就是手工跟蹤內存的使用狀況,這是形成許多問題的一個根源。在編寫JavaScript程序時,開發人員不用在關心內存使用問題,所需內存的分配以及無用內存的回收徹底實現了自動管理。這種垃圾收集機制的原理其實很簡單:找出那些不在繼續使用的變量,而後釋放其佔用的內存。爲此垃圾收集器會按照固定的事件間隔(或代碼執行中預約的收集時間),週期性的執行這一操做。前端
記:既然有垃圾收集器,那麼自己垃圾收集也是耗用內存的,且宿主環境是瀏覽器時,自己得到的內存不會是所有內存,爲防止瀏覽器耗盡內存而系統崩潰。 web
下面來分析一下函數中局部變量的正常生命週期。局部變量只在函數執行過程當中存在。而在這個過程當中,會爲局部變量在棧(或堆)內存上分配相應的空間,以便存儲它們的值。而後在函數中使用這些變量,直至函數執行結束。此時,局部變量就沒有存在的必要了,所以能夠釋放它們的內存以供未來使用。在這種狀況下,很容易判斷變量是否還有存在的必要;但並不是因此狀況下都這麼容易得出結論。
垃圾收集器必須跟蹤哪一個變量有用哪一個變量沒用,對於再也不有用的變量打上標記,已備未來收回所佔用的內存。用於標識無用變量的策略可能會因實現而異。數組
總結:瀏覽器
通常狀況下,局部變量的生命週期爲函數(對象)執行到執行結束,全局變量的生命週期爲瀏覽器打開和關閉。函數
根據數據類型(值、引用)分配棧、堆內存。(這裏有一些爭議,認爲JavaScript這類高級語言用stack和heap解釋不是很準確。而且JS中的值類型實際上也是一種"對象")
前端基礎進階(一):內存空間詳細圖解性能
對於無用變量的回收採起先標記,後回收策略,具體執行垃圾收集器。優化
JavaScript中最經常使用的垃圾收集方式是標記清除(mark-and-sweep)。當變量進入環境(例如,在函數中聲明一個變量)時,將這個變量標記爲"進入環境"(進入執行棧?)。從邏輯上講,永遠不能釋放進入環境的變量所佔用的內存,由於只要執行流進入相應的環境,就可能用到它們。而當變量離開環境時,則將其標記爲"離開環境"。spa
可使用任何方式標記變量,如翻轉某個特殊的位來紀錄一個變量什麼時候進入環境,或者使用一個「進入環境的」變量列表及一個"離開環境的"變量列表來跟蹤哪一個變量發生變化。線程
垃圾收集器在運行時時候會給存儲在內存中全部變量都加上標記(問題:加標記這個動做是否也會佔用內存?)。而後,它會去掉環境中的變量以及被環境中的變量引用的變量的標記。而在此以後再被加上標記的變量將被視爲準備刪除的變量,緣由是環境中的變量已經沒法訪問到這些變量了。最後,垃圾收集器完成內存清除工做,銷燬那些帶標記的值並回收它們所佔用的內存空間。3d
徹底抽象不起來:)
另外一種不太常見的垃圾收集策略叫作引用計數(reference counting)。引用技術的含義是跟蹤紀錄每一個值被引用的次數。當聲明瞭一個變量並將一個引用類型值賦給該變量時,則這個值的引用次數就是1。若是同一個值又被賦給另外一個變量,則該值的引用次數加1。相反,若是包含對這個值引用的變量又取得了另一個值。,則這個值的引用次數減1。
本身抽象的圖:)
當這個值的引用次數變成0時,則說明沒有辦法在訪問這個值了,於是就能夠將其佔用的內存空間回收起來。這樣,當垃圾收集器下次運行時,它就會釋放那些引用次數爲0的值所佔用的內存。
《JS高程3》曾說JavaScript不容許直接訪問內存地址,而是經過對外引用內存地址(哈希表)來實現訪問,這個回收方式是否能夠當作是內存地址對外顯現的次數?
Netscpae Navigatior3.0是最先使用引用技術策略的瀏覽器,但很快它就遇到了一個嚴重的問題:循環引用。循環引用指的是對象A中一個指向對象B的指針,而對象B也包含一個指向對象A的引用。以下:
function problem() { var objA = new Object(); var objB = new Object(); objA.someOtherObject = objB; objB.anotherObject = objA; }
objA和objB經過各自的屬性相互引用;也就是說,這兩個對象的引用次數都是2。在採用標記清除策略的實現中,因爲函數執行以後,這兩個對象都離開了做用域,所以這種相互引用不是問題。
但在採用引用計數策略的實現中,當函數執行完畢後,由於它們的引用次數永遠不會是0,假如這個函數被重複屢次調用,就會致使大量內存得不到回收。爲此,Netscape在Navigation4.0中放棄了引用計數方式,轉而採用標記清除來實現其垃圾收集機制。但是,引用計數致使的麻煩並未就此終結。
咱們知道,IE中有一部分對象並非原生JavaScript對象。例如,其BOM和DOM中的對象就是使用C++以COM
(Component Object Model,組件對象模型)對象的形式實現的,而COM
對象的垃圾收集機制採用的就是引用計數策略。所以,即便IE的JavaScript引擎是使用標記清除策略實現的,但JavaScript訪問的COM對象依然是基於引用計數策略的。換句話說,只要在IE中涉及COM對象,就會存在循環引用的問題。
下面這個簡單的例子,展現了COM對象致使的循環引用問題:
var e = document.getElementById("some_element"); var myObject = new Object(); myObject.element = e; e.someObject = myObject;
一個DOM
元素(element
,也是對象)與一個原生JavaScript
對象(myObject
)之間建立了循環引用。
其中,變量myObject有一個名爲e的屬性指向element對象;而變量e也有一個屬性名叫someObject回指myObject。因爲存在這個循環引用,即便將例子中的DOM從頁面中移除,它也永遠不會被回收。
爲了不相似這樣的循環引用問題,最好是在不使用它們的時候手工斷開原生JavaScript對象與DOM元素之間的連接。例如,可使用下面的代碼消除前面例子建立的循環引用:
myObject.element = null; element.someObject = null;
將變量設置爲null意味着切斷變量與它此前引用的值之間的連接。當垃圾收集器下次運行時,就會刪除這些值並回收它們佔用的內存。
垃圾收集器週期性運行的,並且若是爲變量分配的內存數量可觀,那麼回收工做量也是至關大的。在這種狀況下,肯定垃圾收集器的時間間隔是一個很是重要的問題。
說道垃圾收集器多久時間運行一次,不由讓人聯想起IE所以而聲名狼藉的性能問題。IE的垃圾收集器是根據內存分配量運行的,具體一點說就是256個變量、4096個對象(或數組)字面量和數組元素或者64KB的字符串。達到上述任何一個臨界值,垃圾收集器都會運行。這種實現方式問題在於,若是一個腳本包含那麼多變量,那麼該腳本可能會在其生命週期中一直保持那麼多變量。而這樣一來,垃圾收集器不得不頻繁的運行。結果,由此引起的嚴重性能問題促使IE7重寫了其垃圾收集例程。
事實上,有點瀏覽器能夠觸發垃圾收集過程,但咱們不建議讀者這樣作。在IE中,調用window.CollectGarbage()
方法會當即執行垃圾收集。
JavaScript在進行內存管理及垃圾收集時面臨問題最主要的是,分配給web瀏覽器的可用內存數量比桌面應用程序的少,防止運行JavaScript網頁耗盡系統內存而致使系統崩潰。內存限制問題不只會影響給變量分配內存,同時還會影響調用棧以及一個線程中可以同時執行的語句數量。
確保佔用最少的內存可讓頁面得到更好的性能。而優化內存佔用的最佳方式,就是爲執行中的代碼只保存必要的數據。一旦數據再也不有用,最好經過將其值設置爲null來釋放其引用——這個作法叫作解除引用(dereferencing)。這一作法用於大多數全局變量和全局對象的屬性。局部變量會在它們離開執行環境時自動解除引用:
function createPerson(name) { var localPerson = new Object(); localPerson.name = name; return localPerson; } var globalPerson = createPerson("Nicholas"); // 手工解除globalPerson的引用 globalPerson = null;
localPerson在函數執行完畢就離開執行環境,所以無需咱們顯式地去爲它解除引用。可是對於全局變量globalPerson而言,則須要咱們在不使用它的時候手工爲它解除引用,設置爲null。
解除一個值的引用並不意味着自動回收該值所佔用的內存。解除引用的真正做用是讓值脫離執行環境,以便垃圾收集器下次運行時將其回收。