JavaScript具有自動(dòng)垃圾回收機(jī)制,執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過(guò)程中使用的內(nèi)存。
垃圾收集器必須跟蹤哪個(gè)變量有用,對(duì)于不再有用的變量打上標(biāo)記,以備將來(lái)收回其占用的內(nèi)存。用于標(biāo)識(shí)無(wú)用變量的策略因?qū)崿F(xiàn)而異,但具體到瀏覽器中的實(shí)現(xiàn),通常有兩個(gè)策略。
1、標(biāo)記清除
JavaScript中最常用的垃圾收集方式是標(biāo)記清除(mark-and-sweep)。當(dāng)變量進(jìn)入環(huán)境時(shí),就將這個(gè)變量標(biāo)記為“進(jìn)入環(huán)境”。從邏輯上講,永遠(yuǎn)不能釋放進(jìn)入環(huán)境的變量所占用的內(nèi)存,因?yàn)橹灰獔?zhí)行流進(jìn)入相應(yīng)的環(huán)境,就可能會(huì)用到它們。而當(dāng)變量離開(kāi)環(huán)境時(shí),則將其標(biāo)記為“離開(kāi)環(huán)境”。
垃圾收集器在運(yùn)行的時(shí)候會(huì)給存儲(chǔ)在內(nèi)存中的所有變量都加上標(biāo)記(當(dāng)然,可以使用任何標(biāo)記方式)。然后,它會(huì)去掉環(huán)境中的變量以及被環(huán)境中的變量引用的變量的標(biāo)記。而在此之后再被加上標(biāo)記的變量將被視為準(zhǔn)備刪除的變量,原因是環(huán)境中的變量已經(jīng)無(wú)法訪問(wèn)到這些變量了。最后,垃圾收集器完成內(nèi)存清除工作,銷毀那些帶標(biāo)記的值并回收他們所占用的內(nèi)存空間。
2、引用計(jì)數(shù)
另一種不太常用的垃圾收集策略叫做引用計(jì)數(shù)(reference counting)。引用計(jì)數(shù)的含義是跟蹤記錄每個(gè)值被引用的次數(shù)。當(dāng)聲明了一個(gè)變量并將一個(gè)引用類型值賦給該變量時(shí),則這個(gè)值的引用次數(shù)就是1.如果同一個(gè)值又被賦給另一個(gè)變量,則該值的引用次數(shù)加1。,相反,如果包含對(duì)這個(gè)引用的變量取得了另外一個(gè)值,則這個(gè)值的引用次數(shù)減1.當(dāng)這個(gè)值的引用次數(shù)變成0時(shí),則說(shuō)明沒(méi)有辦法再訪問(wèn)這個(gè)值了,因而就可以將其占用的內(nèi)存空間收回來(lái)。這樣當(dāng)垃圾回收器下次再運(yùn)行時(shí),它就會(huì)釋放那些引用次數(shù)為零的值所占用的內(nèi)存。
循環(huán)引用:對(duì)象A中包含一個(gè)指向?qū)ο驜的指針,而對(duì)象B中也包含一個(gè)指向?qū)ο驛的引用。如下:
在這個(gè)例子中,objectA 和 objectB 通過(guò)各自的屬性相互引用。也就是說(shuō),這兩個(gè)對(duì)象的引用次數(shù)都是2.在采用標(biāo)記清除策略的實(shí)現(xiàn)中,由于函數(shù)執(zhí)行后,這兩個(gè)對(duì)象都離開(kāi)了作用域,因此這種相互引用不是個(gè)問(wèn)題。但在采用引用計(jì)數(shù)策略的實(shí)現(xiàn)中,當(dāng)函數(shù)執(zhí)行完畢后,objectA 和 objectB 還將繼續(xù)存在,因此它們的引用次數(shù)永遠(yuǎn)不會(huì)是 0。假如這個(gè)函數(shù)被重復(fù)多次引用,將會(huì)導(dǎo)致大量?jī)?nèi)存得不到回收。
另外,IE中有一部分對(duì)象并不是原生 JavaScript 對(duì)象。例如,其BOM和DOM中的對(duì)象就是使用C++以COM(Component Object Model,組件對(duì)象模型)對(duì)象的形式實(shí)現(xiàn)的。而COM對(duì)象的垃圾收集機(jī)制采用的引用計(jì)數(shù)策略。因此即使IE的JavaScript引擎是使用標(biāo)記清除策略來(lái)實(shí)現(xiàn)的,但JavaScript訪問(wèn)的COM對(duì)象依然是基于引用計(jì)數(shù)策略的。
也就是說(shuō),只要在IE中涉及COM對(duì)象,就會(huì)存在循環(huán)引用的問(wèn)題。如下
這個(gè)例子中,一個(gè)DOM元素與一個(gè)原生JavaScript對(duì)象之間創(chuàng)建了循環(huán)引用。其中,變量myObject有一個(gè)名為element的屬性指向element對(duì)象;反之亦然。由于存在這個(gè)循環(huán)引用,即使將例子中的DOM從頁(yè)面中移除,它也永遠(yuǎn)不會(huì)被回收。
為了避免類似這樣的循環(huán)引用問(wèn)題,最好是在不使用它們的時(shí)候手動(dòng)斷開(kāi)原生 JavaScript對(duì)象與DOM元素之間的連接。