一文帶你搞懂頁(yè)面泄露及其常出現(xiàn)場(chǎng)景

1. 前言
我們常常能看見(jiàn)一些 有關(guān)于 JS 垃圾回收的面試題,那么自然也會(huì)涉及到 內(nèi)存泄漏,那這內(nèi)存泄漏到底是啥?

本文框架



2. 內(nèi)存泄漏定義
首先,什么是內(nèi)存泄漏?
從字面上來(lái)理解,那就是申請(qǐng)的內(nèi)存沒(méi)能完全回收,泄漏了。我個(gè)人的理解深入一點(diǎn)點(diǎn)地來(lái)說(shuō),就是內(nèi)存資源得不到釋放,還失去了對(duì)他們的引用,導(dǎo)致最后沒(méi)法對(duì)這些資源進(jìn)行復(fù)用。
最后導(dǎo)致:它占用你的資源,卻不給你用,



也就是又吃你的草?,又不幫你跑~

3. 怎么會(huì)這樣?
眾所周知,我們 JS 不是有垃圾回收嗎?怎么還會(huì)有資源被占用?
我們簡(jiǎn)單的回顧一下,垃圾回收:

我們有個(gè)垃圾回收機(jī)制
它可以根據(jù)條件判斷你這個(gè)是不是垃圾
是垃圾,拿下!
ok,我想你已經(jīng)發(fā)現(xiàn)漏洞了,當(dāng)垃圾回收機(jī)制不認(rèn)為某個(gè)垃圾是垃圾的時(shí)候, 也就導(dǎo)致該塊垃圾不會(huì)回收了~ 所以就出現(xiàn)了內(nèi)存泄漏。
可以,你現(xiàn)在已經(jīng)知道內(nèi)存泄漏的根本原因了。

4. 到底是誰(shuí)干了什么導(dǎo)致的?
一個(gè)優(yōu)秀的 Web 開(kāi)發(fā),自然要避免這些糟糕的內(nèi)存泄漏,所以我們需要先了解一些哪些情況會(huì)導(dǎo)致這個(gè)東東,從而減少這樣的代碼

4.1 不當(dāng)?shù)娜肿兞?br>從學(xué)編程開(kāi)始,你就應(yīng)該知道怎么樣的變量生命周期最長(zhǎng),當(dāng)然就是 全局變量了。在 JS 中不出意外的話,全局變量只有在頁(yè)面關(guān)閉的時(shí)候才會(huì)釋放。所以當(dāng)你將一些不應(yīng)該掛載在全局的變量放到全局...



你可能不小心
當(dāng)然大部分時(shí)候你可能是不小心的,比如不小心地在變量聲明之前就給他賦值了~

function a() {
    t = new Array(10086).fill('*')
}

a()

就會(huì)這樣:



4.2 生產(chǎn)環(huán)境中的 console.log
很多代碼規(guī)范工具在生產(chǎn)模式下打包時(shí)都會(huì)關(guān)于 console.log 提出警告,這是因?yàn)檫@句只是為了 debug,所以沒(méi)必要嗎?
其實(shí)不止,console.log 甚至還會(huì)導(dǎo)致內(nèi)存泄漏,因?yàn)樾枰谀忝看未蛴〉臅r(shí)候都要能有信息給你查看,那他自然就要將其保存起來(lái)。

你可以這樣
你可以類(lèi)似這樣



又或者干脆用代碼規(guī)范工具幫你~






4.3 角落里的定時(shí)器
setTimeout 和setInterval 是需要瀏覽器專(zhuān)門(mén)提供線程來(lái)維護(hù)他們的。所以即使你銷(xiāo)毀了創(chuàng)建該定時(shí)器的組件,這定時(shí)器仍然會(huì)掛載在內(nèi)存中。并且如果你多次創(chuàng)建、銷(xiāo)毀組件,他就會(huì)原來(lái)越多~



你可以
總之,最好就別忘記別忘記清除它!比如在寫(xiě) React 組件的時(shí)候,在 useEffect 方法中狠狠地清除掉它!

4.4 角落里的網(wǎng)絡(luò)回調(diào)
一些時(shí)候,我們可能會(huì)在某個(gè)頁(yè)面中發(fā)送請(qǐng)求,并用一個(gè)回調(diào)函數(shù)callback處理一些相關(guān)的事件。通常這個(gè)回調(diào)函數(shù)會(huì)擁有該頁(yè)面的一些變量的引用 —— 因?yàn)樗緛?lái)就是要操作他們的。但是,當(dāng)頁(yè)面銷(xiāo)毀的時(shí)候,回調(diào)卻忘記注銷(xiāo)的話,也會(huì)導(dǎo)致一些資源無(wú)法回收~
所以在你注冊(cè)回調(diào)的時(shí)候,一定要記得在某個(gè)地方某個(gè)時(shí)刻將他們注銷(xiāo)掉~(yú)

4.5 奇怪的閉包
函數(shù)本身會(huì)持有它創(chuàng)建時(shí)所在詞法環(huán)境的引用,并且不出意外的話,會(huì)在使用完函數(shù)的時(shí)候?qū)⑵渖暾?qǐng)的所有內(nèi)存回收~

ok,你知道的,我又要說(shuō)意外了。

當(dāng)函數(shù) A 內(nèi)部再返回一個(gè)函數(shù) B 的時(shí)候,B 就可以擁有 A 的詞法環(huán)境,就形成了一個(gè)閉包。而當(dāng) B 掛載到擁有比 A 更長(zhǎng)的生命周期的東西上時(shí),就會(huì)導(dǎo)致 A 雖然執(zhí)行完了,但是 A 的內(nèi)存仍無(wú)法回收~
又或許不是返回 B 的情況,可能是 A 中創(chuàng)建的某個(gè)變量的引用一直無(wú)法回收...



不得不提
當(dāng)然,閉包生來(lái)是有很多大用處的,并不是只會(huì)帶來(lái)內(nèi)存泄漏。這種沒(méi)辦法的東西也只能說(shuō)是利用閉包的特性時(shí)無(wú)法避免的東西。當(dāng)然,寫(xiě)代碼的時(shí)候還是要注意一下,盡量別讓 B 擁有過(guò)長(zhǎng)的生命周期,這樣也就能使他們占用的資源在合適的時(shí)候回收。

4.6 角落里的 DOM 節(jié)點(diǎn)
根據(jù) W3C 的 HTML DOM 標(biāo)準(zhǔn),HTML 文檔中的所有內(nèi)容都是節(jié)點(diǎn),HTML 文檔也可被視為一棵樹(shù),也就是一棵 DOM 樹(shù)。而不出意外的話, DOM 節(jié)點(diǎn)的生命周期時(shí)間就是掛載在 DOM 樹(shù)上的時(shí)間。
不出意外的話,又要出意外了~
如果某處 js 擁有關(guān)于這個(gè)DOM節(jié)點(diǎn)的引用~

const tmp = document.querySelector('.hpapp')
const root = document.querySelector('body')
const a = () =>{root.removeChild(tmp)}
a()

html 我就不寫(xiě)了,這個(gè)很簡(jiǎn)單

實(shí)際開(kāi)發(fā)中,這個(gè)a可能是某個(gè)事件的回調(diào)~
像上面的代碼,就是移除了 tmp 節(jié)點(diǎn),但沒(méi)完全移除~
是不在DOM樹(shù)里了,但是全局變量 tmp 一直偷偷地保留著對(duì)他的引用~

你應(yīng)該
const root = document.querySelector('body')
const a = () =>{
    const tmp = document.querySelector('.hpapp')
    root.removeChild(tmp)
}
a()

現(xiàn)在就沒(méi)事了,因?yàn)?tmp 節(jié)點(diǎn)的引用會(huì)隨著 a 執(zhí)行完畢一起消失~

總結(jié)
思維導(dǎo)圖
現(xiàn)在你應(yīng)該看到思維導(dǎo)圖的分支就能理解了~



這篇文章,也算是圖文并茂的講了一下內(nèi)存泄漏~ 但是開(kāi)發(fā)中終有懈怠的時(shí)候,有時(shí)頁(yè)面莫名的卡頓,我們或許就需要去排查一下內(nèi)存泄漏,下次我們?cè)僦v講怎么排查~

文中措辭、知識(shí)點(diǎn)、格式如有疑問(wèn)或建議,歡迎評(píng)論~你對(duì)我很重要~

??如果有所幫助,歡迎點(diǎn)贊關(guān)注,一起進(jìn)步?這對(duì)我很重要~

關(guān)于本文

來(lái)自:前舟

https://juejin.cn/post/7102235689705537549

作者:前舟


歡迎關(guān)注微信公眾號(hào) :前端Q