前言
JS之memoization,memoization 的原理是以參數(shù)作為 key,函數(shù)結(jié)果作為 value, 用對(duì)象進(jìn)行緩存起來,以內(nèi)存空間換 CPU 執(zhí)行事件。memoization 的潛在陷阱即是嚴(yán)格意義的緩存有著完善的過期策略,而普通對(duì)象的鍵值對(duì)并沒有。
用閉包進(jìn)行緩存的對(duì)象的內(nèi)存空間,不會(huì)在函數(shù)執(zhí)行完后被清除,在執(zhí)行量大和參數(shù)多樣性的情況下,會(huì)造成內(nèi)存占用且得不到釋放。
于是,本篇文章就來講講 JS 的垃圾回收。
JS 的垃圾回收機(jī)制的基本原理是:
找出那些不再繼續(xù)使用的變量,然后釋放其占用的內(nèi)存,垃圾收集器會(huì)按照固定的時(shí)間間隔周期性地執(zhí)行這一操作。
那我們?cè)趺粗雷兞渴遣皇窃诶^續(xù)使用呢?
首先,局部變量的生存周期是在函數(shù)聲明和執(zhí)行階段,函數(shù)執(zhí)行完畢后,局部變量就沒有存在的必要了。全局變量會(huì)在瀏覽器關(guān)閉或進(jìn)程關(guān)閉才能釋放。
但還有一些場景,比如閉包,通過作用域鏈訪問到函數(shù)外部的自由變量,使得自由變量保存在內(nèi)存中,不會(huì)隨著函數(shù)執(zhí)行完畢而結(jié)束,以及對(duì)象的相互引用等,垃圾收集器就沒這么容易判斷哪個(gè)變量有用,哪個(gè)變量沒用了。
// 經(jīng)典閉包function closure() {var name = "innerName";return function() {console.log(name);}}var inner = closure();inner(); // innerName;所以,對(duì)于標(biāo)識(shí)無用的變量的策略可能會(huì)實(shí)現(xiàn)不同,但目前在瀏覽器中,通常有兩種策略:標(biāo)記清除和引用計(jì)數(shù)。
二、標(biāo)記-清除(Mark-Sweep)
從2012年起,所有現(xiàn)代瀏覽器都使用了標(biāo)記-清除垃圾回收算法, 那什么叫標(biāo)記-清除呢?
當(dāng)變量進(jìn)入執(zhí)行環(huán)境時(shí),就標(biāo)記這個(gè)變量為“進(jìn)入環(huán)境”。當(dāng)變量離開環(huán)境時(shí),則將其標(biāo)記為“離開環(huán)境”。從邏輯上講,永遠(yuǎn)不能釋放進(jìn)入環(huán)境的變量所占用的內(nèi)存,因?yàn)橹灰獔?zhí)行流進(jìn)入相應(yīng)的環(huán)境,就可能會(huì)用到他們。
垃圾收集器在運(yùn)行的時(shí)候會(huì)給存儲(chǔ)在內(nèi)存中的所有變量都加上標(biāo)記。
然后,它會(huì)去掉環(huán)境中的變量以及被環(huán)境中的變量引用的標(biāo)記。而在此之后再被加上標(biāo)記的變量將被視為準(zhǔn)備刪除的變量,原因是環(huán)境中的變量已經(jīng)無法訪問到這些變量了。
最后,垃圾收集器完成內(nèi)存清除工作,銷毀那些帶標(biāo)記的值,并回收他們所占用的內(nèi)存空間。
另外,標(biāo)記-清除有一個(gè)問題,就是在清除之后,內(nèi)存空間是不連續(xù)的,即出現(xiàn)了內(nèi)存碎片。如果后面需要一個(gè)比較大的連續(xù)的內(nèi)存空間時(shí),那將不能滿足要求。而標(biāo)記-整理(Mark-Compact)方法可以有效地解決這個(gè)問題。標(biāo)記階段沒有什么不同,只是標(biāo)記結(jié)束后,標(biāo)記-整理方法會(huì)將活著的對(duì)象向內(nèi)存的一端移動(dòng),最后清理掉邊界的內(nèi)存。
新聞熱點(diǎn)
疑難解答
圖片精選