一.垃圾收集 JavaScript具有自動垃圾收集功能,也就是說,執行環境會負責管理代碼所占用的記憶體. 不同於C和類C語言,這些語言都需要手動監聽記憶體的使用情況.JavaScript實現了自動管理記憶體,我們無需擔心這個問題. 這種垃圾收集器的原理也很簡單,就是找出不再繼續使用的變數,然後釋放其占用的 ...
一.垃圾收集
JavaScript具有自動垃圾收集功能,也就是說,執行環境會負責管理代碼所占用的記憶體.
不同於C和類C語言,這些語言都需要手動監聽記憶體的使用情況.JavaScript實現了自動管理記憶體,我們無需擔心這個問題.
這種垃圾收集器的原理也很簡單,就是找出不再繼續使用的變數,然後釋放其占用的記憶體.垃圾收集器會按照固定的時間間隔(或者代碼的執行時間)來周期性的重覆這個操作.
對於局部變數,我們都知道一旦在函數運行完成之後,函數和局部變數就立即被銷毀,所以,此時局部變數也就沒有存在的意義了.因此,我們可以釋放局部變數的記憶體.垃圾收集器會自動為無用的變數做上標記,然後回收其占用的記憶體.具體所用到的策略會因瀏覽器而異.但總的來說,就只有二個策略.
1.標記清除
標記清除是JavaScript中最常見的垃圾收集方式了.當變數進入一個執行環境,這個變數就會被標記為進入環境,因此,永遠都不能釋放進入環境的變數,因為只要執行流進入相應的環境,還有可能會運用到它們,當變數離開環境之後,就會將其標記為離開環境..
可以使用任意方式標記一個變數,因此,如何標記變數並不重要,重要的是採用什麼策略.
垃圾收集器在運行的時候會給存儲在記憶體中的所有變數加上標記.然後,它會去掉環境中的變數以及被環境中變數引用的變數的標記.在此之後,加上標記的變數就是被視為準備刪除的變數,因為環境中的變數已經無法訪問到這些變數了,最後垃圾收集器完成清除記憶體的工作,銷毀哪些帶標記的值並回收它們所占用的記憶體空間.
2.引用計數
另一種不太常見的垃圾收集策略叫做引用計數.引用計數的含義就是跟蹤每個值被引用的次數.當聲明一個變數並將這個變數賦一個引用類型的值時,引用次數就為1,如果將相同的值又被賦予另一個變數,則引用次數記為2,如果包含這個值引用的變數又引用其它值,則減1.當這個值的引用次數變成0時,則說明無法訪問這個值了,就會回收這個值所占用的記憶體.這樣,當垃圾收集器下次運行時,就會自動釋放那些引用次數為0的值所占用的記憶體.
Netscape Navigator3.0是最早使用引用計數策略的瀏覽器,然而很快這個策略就暴露了一個嚴重的問題——迴圈引用.所謂迴圈引用,指的就是一個對象A包含指向一個對象B的指針時,對象B也有包含指向對象A的指針引用.如下圖一個示例:
如上圖所示,person對象和people對象都通過各自的屬性相互引用,也就是說這兩個對象的引用次數始終都是2.如果是採用標記清除的策略,因為函數執行完成之後,這兩個對象都離開了作用域,因此這種相互引用還並不是問題.但在採用引用次數策略的時候,由於引用次數始終都是2,始終都不為0,假如這個函數被多次調用的話,就會占用大量記憶體得不到回收.
所以,Netscap4.0就放棄了引用計數的策略,而採用標記策略,但這個問題並沒有終結.
因為IE中有一部分對象並不是原生JavaScript對象,比如BOM和DOM的對象都是使用C++以COM(Component Object Model,組件對象模型)對象的形式實現的,COM對象的垃圾收集策略就是採用引用計數實現的.也就是說,只要在IE中涉及到COM對象,就會存在迴圈引用的問題.
請看如下一個示例:
這個示例就是在DOM對象與原生JavaScript對象中間創建了一個迴圈引用.變數obj有一個屬性指向變數div的DOM對象,而變數div也有一個屬性回指obj.由於存在迴圈引用,因此即使將DOM移除,也不會被回收掉.
為了避免這種問題,最好是在不使用它們的時候手工斷開它們之間的鏈接,即將它們的值各自設置為null即可.如下圖所示:
IE9將BOM和DOM都轉換成了真正的JavaScript對象.這樣也就解決了兩種垃圾演算法並存導致的問題,還解決了記憶體泄漏現象.
二.性能問題
JavaScript垃圾收集器是周期性的運行,,如果為變數分配的記憶體數量很可觀,回收工作量也是相當大的.因此,在這種情況下,確定垃圾收集器的時間間隔也成為了一個重要的問題.
IE的垃圾收集器是根據記憶體分配量運行的,具體點說,就是達到256個變數,4096個對象(或數組)字面量和數組元素(slot)或者64kb的字元串,這樣的臨界值,垃圾收集器就會運行.這樣造成的一個問題就是垃圾收集器不得不頻繁的運行,因為一旦一個腳本包含許多變數,該腳本就會在其生命周期內保存許多變數,由此便引發了嚴重的性能問題.
IE7的發佈改進了這個問題,它改變了垃圾收集的工作方式.達到臨界值被調整為動態修正,初始值與IE6是一樣的,如果記憶體分配量低於15%,則臨界值就會加倍.如果回收了85%的記憶體分配量,則重置臨界值為預設值.這樣調整大大提升了性能.
其實,在有的瀏覽器中可以觸發垃圾收集過程,在IE中,調用window.CollectGarbage()方法可立即執行.在Opera7及更高版本中,調用window.opera.collect()也會啟動垃圾收集常式.
三.管理記憶體
儘管開發人員可以不用擔心記憶體管理的問題,在使用具備垃圾收集機制的語言編寫程式.但是JavaScript在進行記憶體管理及垃圾收集時面臨的問題還是有點不同的.其中最主要的一個問題就是分配給web瀏覽器的可用記憶體數量通常要比分配給桌面應用程式的少.
這樣的意義在於對安全方面考慮,目的是防止JavaScript的網頁耗盡全部系統記憶體而導致系統崩潰.記憶體限制問題不僅會影響給變數分配記憶體,同時還會影響調用棧以及在一個線程中能夠同時執行的語句數量.
所以,確保最少的記憶體可以給頁面帶來更好的性能,優化記憶體最好的方式,就是可以為執行中的代碼保存必要的數據.一旦數據不再有用,那麼最好將其值設置為null來釋放其引用,這個做法也被叫做解除引用.這個做法適合大多數全局變數和全局對象的屬性,當然局部環境的變數會自動解除其引用,所以不必如此做.
來看下圖一個示例:
上圖示例,變數o獲取到了函數返回的值,而在createName()函數內部,通過傳入一個參數name,可以賦給局部變數obj的firstname屬性,當函數執行完畢,變數obj就會立即被銷毀,也就是自動解除了引用,但對於變數o而言,因為是全局變數,所以需要我們手動去解除引用,將值設置為null就行了.當然解除一個引用並不是自動回收它占用的記憶體.解除引用的真正作用是讓值脫離執行環境,從而方便垃圾收集器下次運行時回收.