瀏覽器的垃圾回收機(jī)制(Garbage collection
),簡(jiǎn)稱(chēng)GC烦味,它會(huì)周期性運(yùn)行以釋放那些不需要的內(nèi)存聂使,否則,JavaScript的解釋器將會(huì)耗盡全部系統(tǒng)內(nèi)存而導(dǎo)致系統(tǒng)崩潰谬俄。
具體到瀏覽器中的實(shí)現(xiàn)柏靶,通常有兩個(gè)策略:標(biāo)記清除和引用計(jì)數(shù)。
引用計(jì)數(shù)法
此算法把“對(duì)象是否不再需要”簡(jiǎn)化定義為“對(duì)象有沒(méi)有其他對(duì)象引用到它”溃论。如果沒(méi)有引用指向該對(duì)象(零引用)屎蜓,對(duì)象將被垃圾回收機(jī)制回收。
let car = {
logo: 'luhu',
price: 100
} // car 是第一個(gè)對(duì)這個(gè)對(duì)象的引用
let jeep = car // jeep是第二個(gè)對(duì)這個(gè)對(duì)象的引用
car.logo = null // logo屬性雖然設(shè)置為null 但此屬性還在被jeep引用 不會(huì)被回收
car = '' // 此時(shí)這個(gè)對(duì)象還有有jeep一個(gè)引用
jeep = null // jeep設(shè)置為null 那個(gè)對(duì)象是零引用了 可以被回收了
引用計(jì)數(shù)法是最初級(jí)的垃圾收集算法钥勋,如果某對(duì)象沒(méi)有其他對(duì)象指向它了炬转,那就說(shuō)明它可以被回收辆苔。但是它無(wú)法處理循環(huán)引用的問(wèn)題。
循環(huán)引用的限制
// 引用計(jì)數(shù)法的限制
function f(){
let o1 = {}
let o2 = {}
o1.a = o2
o2.a = o1
return 100
}
f()
我們執(zhí)行f函數(shù)扼劈,它返回了一個(gè)數(shù)字驻啤,和內(nèi)部的o1,o2沒(méi)什么關(guān)系,但是對(duì)引用計(jì)數(shù)法來(lái)說(shuō)测僵,o1,o2它們之間還存在著相互引用街佑,并不會(huì)被回收。這就造成了內(nèi)存泄漏捍靠。
標(biāo)記清除法
這個(gè)算法把“對(duì)象是否不再需要”簡(jiǎn)化定義為“對(duì)象是否可以獲得”沐旨。
從2012年起,所有現(xiàn)代瀏覽器都使用了標(biāo)記-清除垃圾回收算法榨婆。標(biāo)記清除法假定存在一個(gè)根對(duì)象(相當(dāng)于js的全局對(duì)象)磁携,垃圾回收器將定期從根對(duì)象開(kāi)始查找,凡是從根部出發(fā)能掃描到的都會(huì)保留良风,掃描不到的將被回收谊迄。
從根對(duì)象開(kāi)始掃描
右側(cè)的部分無(wú)法到達(dá),它將會(huì)被回收
就像是一桶水我們把它從根對(duì)象潑下去烟央,水流過(guò)的地方都沒(méi)事统诺,沒(méi)沾上水的對(duì)象就該回收了。
內(nèi)部流程
- 垃圾收集器找到所有的根疑俭,并“標(biāo)記”(記琢改亍)它們。
- 然后它遍歷并“標(biāo)記”來(lái)自它們的所有引用钞艇。
- 然后它遍歷標(biāo)記的對(duì)象并標(biāo)記 它們的 引用啄寡。所有被遍歷到的對(duì)象都會(huì)被記住,以免將來(lái)再次遍歷到同一個(gè)對(duì)象哩照。
- ……如此操作挺物,直到所有可達(dá)的(從根部)引用都被訪(fǎng)問(wèn)到。
- 沒(méi)有被標(biāo)記的對(duì)象都會(huì)被刪除飘弧。
幾種常見(jiàn)的內(nèi)存泄漏
- 全局變量
全局變量什么時(shí)候需要自動(dòng)釋放內(nèi)存空間很難判斷识藤,所以在開(kāi)發(fā)中盡量避免使用全局變量,以提高內(nèi)存有效使用率次伶。
- 未移除的事件綁定
dom元素雖然被移除了蹋岩,但元素綁定的事件還在,如果不及時(shí)移除事件綁定学少,在IE9以下版本容易導(dǎo)致內(nèi)存泄漏。現(xiàn)代瀏覽器不存在這個(gè)問(wèn)題了秧骑,了解一下即可版确。
let div = document.querySelector(".div");
let name = 'lee'
let handler = function () {
console.log(name);
}
div.addEventListener('click', handler, false)
div.parentNode.removeChild(div) // 在IE9以下的老版本事件還在
- 無(wú)效的dom引用
有時(shí)候?qū)om作為對(duì)象的key存儲(chǔ)起來(lái)很有用扣囊,但是在不需要該dom時(shí),要記得及時(shí)解除對(duì)它的引用绒疗。
var ele = {
node: document.getElementById('node')
};
document.body.removeChild(document.getElementById('node')); // 此時(shí)ele中還存在對(duì)node的引用
- 定時(shí)器setInterval/setTimeout
看下面的一段定時(shí)器代碼侵歇,一旦我們?cè)谄渌胤揭瞥薾ode節(jié)點(diǎn),定時(shí)器的回調(diào)便失去了意義吓蘑,然而它一直在執(zhí)行導(dǎo)致callback無(wú)法回收惕虑,進(jìn)而造成callback內(nèi)部掉數(shù)據(jù)resData也無(wú)法被回收。所以我們應(yīng)該及時(shí)clear定時(shí)器磨镶。
let resData = 100
let callback = function () {
let node = document.querySelecter('.p')
node && (node.innerHTML = resData)
}
setInterval (callback, 1000)
另外單獨(dú)說(shuō)一下閉包溃蔫,閉包和內(nèi)存泄漏沒(méi)有半毛錢(qián)關(guān)系,只是由于IE9之前的版本垃圾收集機(jī)制的原因琳猫,導(dǎo)致內(nèi)存無(wú)法進(jìn)行回收伟叛,這是IE的問(wèn)題,現(xiàn)代瀏覽器基本都不存在這個(gè)問(wèn)題脐嫂。當(dāng)然閉包要是使用不當(dāng)肯定是會(huì)造成內(nèi)存泄漏统刮。
WeakMap、WeakSet
es6的WeakMap和Map類(lèi)似账千,都是用于生成鍵值對(duì)的集合侥蒙,不同的是WeakMap是一種弱引用,它的鍵名所指向的對(duì)象匀奏,不計(jì)入垃圾回收機(jī)制鞭衩,另外就是WeakMap只接受對(duì)象作為鍵名(null除外),而Map可以接受各種類(lèi)型的數(shù)據(jù)作為鍵攒射。
WeakMap這種結(jié)構(gòu)有助于防止內(nèi)存泄漏醋旦,一旦消除對(duì)鍵的引用,它占用的內(nèi)存就會(huì)被垃圾回收機(jī)制釋放会放。WeakMap 保存的這個(gè)鍵值對(duì)饲齐,也會(huì)自動(dòng)消失。包括WeakSet也是類(lèi)似的咧最,內(nèi)部存儲(chǔ)的都是弱引用對(duì)象捂人,不會(huì)被計(jì)入垃圾回收。
看一個(gè)阮一峰ES6文檔上舉的例子:
let myWeakmap = new WeakMap();
myWeakmap.set(
document.getElementById('logo'),
{timesClicked: 0})
;
document.getElementById('logo').addEventListener('click', function() {
let logoData = myWeakmap.get(document.getElementById('logo'));
logoData.timesClicked++;
}, false);
上面代碼中矢沿,我們將dom對(duì)象作為鍵名滥搭,每次點(diǎn)擊,我們就更新一下?tīng)顟B(tài)捣鲸。我們將這個(gè)狀態(tài)作為鍵值放在WeakMap里瑟匆。一旦這個(gè)DOM節(jié)點(diǎn)刪除,該狀態(tài)就會(huì)自動(dòng)消失栽惶,不存在內(nèi)存泄漏風(fēng)險(xiǎn)愁溜。
WeakSet和WeakMap類(lèi)似疾嗅,它和set結(jié)構(gòu)的區(qū)別也是兩點(diǎn):
- WeakSet中的對(duì)象都是弱引用,不會(huì)被計(jì)入垃圾回收
- 成員只能是對(duì)象冕象,而不能是其他類(lèi)型的值
所以從垃圾回收的角度來(lái)看代承,合理的使用WeakMap和WeakSet,能幫助我們避免內(nèi)存泄漏渐扮。
小結(jié)
js的垃圾回收機(jī)制我們無(wú)法人為干預(yù)论悴,瀏覽器會(huì)定期巡查,js引擎在內(nèi)部做了很多優(yōu)化墓律,使其可以執(zhí)行的更快膀估,現(xiàn)代瀏覽器基本都采用標(biāo)記清楚的方法進(jìn)行垃圾回收。了解內(nèi)存泄漏的原因以及如何去規(guī)避只锻,防止翻車(chē)事故的發(fā)生??
參考資料: developer.mozilla.org玖像、javascript.info