JavaScript具有自動垃圾回收機制,執行環境會負責管理代碼執行過程中使用的內存。
垃圾收集器必須跟蹤哪個變量有用,對於不再有用的變量打上標記,以備將來收回其占用的內存。用於標識無用變量的策略因實現而異,但具體到瀏覽器中的實現,通常有兩個策略。
1、標記清除
JavaScript中最常用的垃圾收集方式是標記清除(mark-and-sweep)。當變量進入環境時,就將這個變量標記為“進入環境”。從邏輯上講,永遠不能釋放進入環境的變量所占用的內存,因為只要執行流進入相應的環境,就可能會用到它們。而當變量離開環境時,則將其標記為“離開環境”。
垃圾收集器在運行的時候會給存儲在內存中的所有變量都加上標記(當然,可以使用任何標記方式)。然后,它會去掉環境中的變量以及被環境中的變量引用的變量的標記。而在此之后再被加上標記的變量將被視為准備刪除的變量,原因是環境中的變量已經無法訪問到這些變量了。最后,垃圾收集器完成內存清除工作,銷毀那些帶標記的值並回收他們所占用的內存空間。
2、引用計數
另一種不太常用的垃圾收集策略叫做引用計數(reference counting)。引用計數的含義是跟蹤記錄每個值被引用的次數。當聲明了一個變量並將一個引用類型值賦給該變量時,則這個值的引用次數就是1.如果同一個值又被賦給另一個變量,則該值的引用次數加1。,相反,如果包含對這個引用的變量取得了另外一個值,則這個值的引用次數減1.當這個值的引用次數變成0時,則說明沒有辦法再訪問這個值了,因而就可以將其占用的內存空間收回來。這樣當垃圾回收器下次再運行時,它就會釋放那些引用次數為零的值所占用的內存。
循環引用:對象A中包含一個指向對象B的指針,而對象B中也包含一個指向對象A的引用。如下:
function problem() { var objectA = new Object(); var objectB = new Object(); objectA.someOtherObject = objectB; objectB.anotherObject = objectA; }
在這個例子中,objectA 和 objectB 通過各自的屬性相互引用。也就是說,這兩個對象的引用次數都是2.在采用標記清除策略的實現中,由於函數執行后,這兩個對象都離開了作用域,因此這種相互引用不是個問題。但在采用引用計數策略的實現中,當函數執行完畢后,objectA 和 objectB 還將繼續存在,因此它們的引用次數永遠不會是 0。假如這個函數被重復多次引用,將會導致大量內存得不到回收。
另外,IE中有一部分對象並不是原生 JavaScript 對象。例如,其BOM和DOM中的對象就是使用C++以COM(Component Object Model,組件對象模型)對象的形式實現的。而COM對象的垃圾收集機制采用的引用計數策略。因此即使IE的JavaScript引擎是使用標記清除策略來實現的,但JavaScript訪問的COM對象依然是基於引用計數策略的。
也就是說,只要在IE中涉及COM對象,就會存在循環引用的問題。如下
var element = document.getElementById("some_element"); var myObject = new Object(); myObject.element = element; element.someObject = myObject;
這個例子中,一個DOM元素與一個原生JavaScript對象之間創建了循環引用。其中,變量myObject有一個名為element的屬性指向element對象;反之亦然。由於存在這個循環引用,即使將例子中的DOM從頁面中移除,它也永遠不會被回收。
為了避免類似這樣的循環引用問題,最好是在不使用它們的時候手動斷開原生 JavaScript對象與DOM元素之間的連接。
myObject.element = null; element.someObject = null;