最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

前端檢查內(nèi)存泄露完整示例

 更新時(shí)間:2026年05月03日 10:59:35   作者:遺憾隨她而去.  
內(nèi)存泄漏是指應(yīng)用程序中的內(nèi)存被錯(cuò)誤地分配和釋放,導(dǎo)致無(wú)法再次使用的內(nèi)存堆積,這通常是由于開(kāi)發(fā)者忘記釋放不再使用的對(duì)象或數(shù)據(jù)而引起的,這篇文章主要介紹了前端檢查內(nèi)存泄露的相關(guān)資料,需要的朋友可以參考下

前言

前端應(yīng)用的內(nèi)存泄露,指不再使用內(nèi)存未被釋放,導(dǎo)致頁(yè)面占用內(nèi)存持續(xù)增長(zhǎng),輕則引發(fā)頁(yè)面卡頓,加載緩慢,重則導(dǎo)致瀏覽器崩潰, 尤其在單頁(yè)應(yīng)用SPA中,路由切換頻繁但內(nèi)存不回收,問(wèn)題會(huì)被無(wú)限放大,比如用戶(hù)長(zhǎng)時(shí)間使用某后臺(tái)管理系統(tǒng),可能出現(xiàn)操作響應(yīng)式延遲,甚至需要強(qiáng)制刷新才能恢復(fù),這很可能是內(nèi)存泄露在"作祟"

一. 前端常見(jiàn)的內(nèi)存泄露場(chǎng)景

1. 意外的全局變量:未聲明的變量(如a = 10而非let a = 10)會(huì)掛載到window上,頁(yè)面不刷新就不會(huì)釋放;

2. 閉包濫用:閉包會(huì)保留對(duì)外部作用域的引用,若長(zhǎng)期持有 DOM 或大型對(duì)象,會(huì)導(dǎo)致內(nèi)存無(wú)法回收(如未清理的事件監(jiān)聽(tīng)回調(diào)) ;

3. 未清理的 DOM 引用:刪除 DOM 節(jié)點(diǎn)后,仍保留其引用(如let el =       document.getElementById('test'),刪除el對(duì)應(yīng)的 DOM 后未置el = null);

4. 定時(shí)器 / 事件監(jiān)聽(tīng)未銷(xiāo)毀:setInterval未用clearInterval清除、addEventListener未用removeEventListener解綁,尤其在組件掛載 / 卸載時(shí)容易遺漏;

5.第三方庫(kù) / 插件殘留:部分第三方庫(kù)(如圖表庫(kù)、播放器)使用后未調(diào)用銷(xiāo)毀方法,導(dǎo)致內(nèi)部資源無(wú)法釋放;

6.數(shù)組 / 對(duì)象無(wú)限增長(zhǎng):全局緩存對(duì)象未設(shè)置過(guò)期機(jī)制,或數(shù)組持續(xù) push 數(shù)據(jù)但未清理無(wú)效項(xiàng)。

二. 核心工具: 瀏覽器開(kāi)發(fā)者工具(Chrome DevTools )

1.第一步: 復(fù)現(xiàn)內(nèi)存泄露場(chǎng)景

首先需要穩(wěn)定復(fù)現(xiàn)泄露行為,比如: 

路由切換(SPA 中反復(fù)切換 A→B→A→B);

點(diǎn)擊按鈕觸發(fā)某操作(如打開(kāi)彈窗后關(guān)閉);

長(zhǎng)時(shí)間滾動(dòng)或輪詢(xún)請(qǐng)求數(shù)據(jù)。

2.第二步

1. 打開(kāi)Chrome 開(kāi)發(fā)者工具

2. 勾選面板頂部的「Memory」選項(xiàng)(同時(shí)可保留默認(rèn)的「CPU」「Network」);

3.點(diǎn)擊左上角的「錄制」按鈕(圓形紅點(diǎn)),然后執(zhí)行復(fù)現(xiàn)步驟(如切換路由 5 次);

4. 執(zhí)行完成后點(diǎn)擊「停止」,等待面板生成報(bào)告。

關(guān)鍵觀察點(diǎn): 

報(bào)告中[Memory]曲線(xiàn)若持續(xù)上升且不回落(即使操作停止后,內(nèi)存仍未下降),則大概率存在泄露

若曲線(xiàn)在操作后能回落至初始水平,說(shuō)明內(nèi)存已正?;厥?無(wú)泄露

3.第三步

Memory 面板定位泄露對(duì)象,主要是用于記錄頁(yè)面堆內(nèi)存的具體情況以及js堆內(nèi)存隨加載時(shí)間線(xiàn)動(dòng)態(tài)的分配情況

Performance 面板確認(rèn)泄露后,用 Memory 面板進(jìn)一步定位 “哪些對(duì)象未被回收”:

1. 常用快照類(lèi)型

Memory面板支持3中快照類(lèi)型,按需選擇:

  • Heap snapshot(堆快照):捕獲當(dāng)前內(nèi)存中所有對(duì)象的快照,可對(duì)比不同快照的差異,適合定位未回收的對(duì)象;
  • Allocation instrumentation on timeline(時(shí)間線(xiàn)分配記錄):實(shí)時(shí)記錄內(nèi)存分配過(guò)程,適合觀察某操作期間的內(nèi)存分配細(xì)節(jié);
  • Allocation sampling(分配采樣):低開(kāi)銷(xiāo)的內(nèi)存采樣,適合快速排查大面積泄露,無(wú)需精確到單個(gè)對(duì)象。

2. 堆快照對(duì)比操作

  • 打開(kāi) DevTools → 切換到「Memory」面板;
  • 選擇「Heap snapshot」,點(diǎn)擊「Take snapshot」拍攝初始快照(記為快照 1,此時(shí)未執(zhí)行任何操作);
  • 執(zhí)行一次泄露復(fù)現(xiàn)步驟(如切換一次路由);
  • 再次點(diǎn)擊「Take snapshot」拍攝快照 2;
  • 重復(fù)復(fù)現(xiàn)步驟 N 次(如再切換 3 次路由),拍攝快照 3;
  • 在 Memory 面板左側(cè)的快照列表中,點(diǎn)擊快照 2/3 的下拉框,選擇「Comparison」(對(duì)比),并選擇 “與快照 1 對(duì)比”; (下圖對(duì)比我用的是 5 和 6 快照進(jìn)行對(duì)比)

這時(shí)候就能看出變占用的內(nèi)存大小,是那些操作造成了內(nèi)存泄露

堆快照就像照相機(jī)一樣,能記錄你當(dāng)前頁(yè)面的堆內(nèi)存情況,每快照一次就會(huì)產(chǎn)生一條快照記錄

如上圖所示,剛開(kāi)始執(zhí)行了一次快照,記錄了當(dāng)時(shí)堆內(nèi)存空間占用為59.4MB,然后我們點(diǎn)擊了頁(yè)面中某些按鈕,又執(zhí)行一次快照,記錄了當(dāng)時(shí)堆內(nèi)存空間占用為59.7MB。并且點(diǎn)擊對(duì)應(yīng)的快照記錄,能看到當(dāng)時(shí)所有內(nèi)存中的變量情況(結(jié)構(gòu)、占總占用內(nèi)存的百分比...)

在開(kāi)始記錄后,我們可以看到圖中右上角有起伏的藍(lán)色與灰色的柱形圖,其中藍(lán)色表示當(dāng)前時(shí)間線(xiàn)下占用著的內(nèi)存; 灰色表示之前占用的內(nèi)存空間已被清除釋放

用Allocation on timeline 

三. 示例

1. 閉包使用不當(dāng)引起內(nèi)存泄漏

使用Performance和Memory來(lái)查看一下閉包導(dǎo)致的內(nèi)存泄漏問(wèn)題在退出fn1函數(shù)執(zhí)行上下文后,該上下文中的變量a本應(yīng)被當(dāng)作垃圾數(shù)據(jù)給回收掉,但因fn1函數(shù)最終將變量a返回并賦值給全局變量res,其產(chǎn)生了對(duì)變量a的引用,所以變量a被標(biāo)記為活動(dòng)變量并一直占用著相應(yīng)的內(nèi)存,假設(shè)變量res后續(xù)用不到,這就算是一種閉包使用不當(dāng)?shù)睦?/p>

<button onclick="myClick()">執(zhí)行fn1函數(shù)</button>
<script>
function fn1 () {
let a = new Array(10000) // 這里設(shè)置了一個(gè)很大的數(shù)組對(duì)象
let b = 3
function fn2() {
let c = [1, 2, 3]
}
fn2()
return a
}
let res = []
function myClick() {
res.push(fn1())
}
</script>

設(shè)置了一個(gè)按鈕,每次執(zhí)行就會(huì)將fn1函數(shù)的返回值添加到全局?jǐn)?shù)組變量res中,是為了能在performacne的曲線(xiàn)圖中看出效果 

使用Performance和Memory來(lái)查看一下閉包導(dǎo)致的內(nèi)存泄漏問(wèn)題

錄制的時(shí)候去執(zhí)行fn1 函數(shù)

  • 在每次錄制開(kāi)始時(shí)手動(dòng)觸發(fā)一次垃圾回收機(jī)制,這是為了確認(rèn)一個(gè)初始的堆內(nèi)存基準(zhǔn)線(xiàn),便于后面的對(duì)比,然后我們點(diǎn)擊了幾次按鈕,即往全局?jǐn)?shù)組變量res中添加了幾個(gè)比較大的數(shù)組對(duì)象,最后再觸發(fā)一次垃圾回收,發(fā)現(xiàn)錄制結(jié)果的JS Heap曲線(xiàn)剛開(kāi)始成階梯式上升的,最后的曲線(xiàn)的高度比基準(zhǔn)線(xiàn)要高,說(shuō)明可能是存在內(nèi)存泄漏的問(wèn)題
  • 在得知有內(nèi)存泄漏的情況存在時(shí),我們可以改用Memory來(lái)更明確得確認(rèn)問(wèn)題和定位問(wèn)題
  • 首先可以用Allocation instrumentation on timeline來(lái)確認(rèn)問(wèn)題,如下圖所示:

先選擇這個(gè) 

然后去錄制 

在我們每次點(diǎn)擊按鈕后,動(dòng)態(tài)內(nèi)存分配情況圖上都會(huì)出現(xiàn)一個(gè)藍(lán)色的柱形,并且在我們觸發(fā)垃圾回收后,藍(lán)色柱形都沒(méi)變成灰色柱形,即之前分配的內(nèi)存并未被清除,即之前分配的內(nèi)存并未被清除

所以此時(shí)我們就可以更明確得確認(rèn)內(nèi)存泄露的問(wèn)題是存在的了,,接下來(lái)就精準(zhǔn)定位問(wèn)題,可以利用Heap snapshot來(lái)定位問(wèn)題,如圖所示:

  • 第一次先點(diǎn)擊快照記錄初始的內(nèi)存情況,然后我們多次點(diǎn)擊按鈕后再次點(diǎn)擊快照,記錄此時(shí)的內(nèi)存情況,發(fā)現(xiàn)從原來(lái)的1.2 M內(nèi)存空間變成了1.7 M內(nèi)存空間,然后我們選中第二條快照記錄,可以看到右上角有個(gè)All objects的字段,其表示展示的是當(dāng)前選中的快照記錄所有對(duì)象的分配情況,而我們想要知道的是第5條快照與第一條快照的區(qū)別在哪,所以選擇Object allocated between Snapshot1 and Snapshot2即展示第一條快照和第二條快照存在差異的內(nèi)存對(duì)象分配情況,此時(shí)可以看到Array的百分比很高,初步可以判斷是該變量存在問(wèn)題,點(diǎn)擊查看詳情后就能查看到該變量對(duì)應(yīng)的具體數(shù)據(jù)了

以上就是一個(gè)判斷閉包帶來(lái)內(nèi)存泄漏問(wèn)題并簡(jiǎn)單定位的方法了

2.全局變量

全局的變量一般是不會(huì)被垃圾回收掉的當(dāng)然這并不是說(shuō)變量都不能存在全局,只是有時(shí)候會(huì)因?yàn)槭韬龆鴮?dǎo)致某些變量流失到全局,例如未聲明變量,卻直接對(duì)某變量進(jìn)行賦值,就會(huì)導(dǎo)致該變量在全局創(chuàng)建,如下所示:

function fn1() {
// 此處變量name未被聲明
name = new Array(99999999)
}
fn1()
  • 此時(shí)這種情況就會(huì)在全局自動(dòng)創(chuàng)建一個(gè)變量name,并將一個(gè)很大的數(shù)組賦值給name,又因?yàn)槭侨肿兞浚栽搩?nèi)存空間就一直不會(huì)被釋放
  • 解決辦法的話(huà),自己平時(shí)要多加注意,不要在變量未聲明前賦值,或者也可以開(kāi)啟嚴(yán)格模式,這樣就會(huì)在不知情犯錯(cuò)時(shí),收到報(bào)錯(cuò)警告,例如

3.分離的DOM節(jié)點(diǎn)

假設(shè)你手動(dòng)移除了某個(gè)dom節(jié)點(diǎn),本應(yīng)釋放該dom節(jié)點(diǎn)所占用的內(nèi)存,但卻因?yàn)槭韬鰧?dǎo)致某處代碼仍對(duì)該被移除節(jié)點(diǎn)有引用,最終導(dǎo)致該節(jié)點(diǎn)所占內(nèi)存無(wú)法被釋放,例如這種情況

<div id="root">
<div class="child">我是子元素</div>
<button>移除</button>
</div>
<script>
let btn = document.querySelector('button')
let child = document.querySelector('.child')
let root = document.querySelector('#root')
btn.addEventListener('click', function() {
root.removeChild(child)
})
</script>

該代碼所做的操作就是點(diǎn)擊按鈕后移除.child的節(jié)點(diǎn),雖然點(diǎn)擊后,該節(jié)點(diǎn)確實(shí)從dom被移除了,但全局變量child仍對(duì)該節(jié)點(diǎn)有引用,所以導(dǎo)致該節(jié)點(diǎn)的內(nèi)存一直無(wú)法被釋放,可以嘗試用Memory的快照功能來(lái)檢測(cè)一下,如圖所示

同樣的先記錄一下初始狀態(tài)的快照,然后點(diǎn)擊移除按鈕后,再點(diǎn)擊一次快照,此時(shí)內(nèi)存大小我們看不出什么變化,因?yàn)橐瞥墓?jié)點(diǎn)占用的內(nèi)存實(shí)在太小了可以忽略不計(jì),但我們可以點(diǎn)擊第二條快照記錄,在篩選框里輸入detached,于是就會(huì)展示所有脫離了卻又未被清除的節(jié)點(diǎn)對(duì)象

解決辦法如下圖所示:

<div id="root">
<div class="child">我是子元素</div>
<button>移除</button>
</div>
<script>
let btn = document.querySelector('button')

btn.addEventListener('click', function() {
let child = document.querySelector('.child')
let root = document.querySelector('#root')

root.removeChild(child)
})

</script>

改動(dòng)很簡(jiǎn)單,就是將對(duì).child節(jié)點(diǎn)的引用移動(dòng)到了click事件的回調(diào)函數(shù)中,那么當(dāng)移除節(jié)點(diǎn)并退出回調(diào)函數(shù)的執(zhí)行上文后就會(huì)自動(dòng)清除對(duì)該節(jié)點(diǎn)的引用,那么自然就不會(huì)存在內(nèi)存泄漏的情況了,我們來(lái)驗(yàn)證一下,如下圖所示:

結(jié)果很明顯,這樣處理過(guò)后就不存在內(nèi)存泄露的情況了 

4. 控制臺(tái)打印

<button>按鈕</button>
<script>
document.querySelector('button').addEventListener('click', function() {
let obj = new Array(1000000)
console.log(obj);
})
</script>

我們?cè)诎粹o的點(diǎn)擊回調(diào)事件中創(chuàng)建了一個(gè)很大的數(shù)組對(duì)象并打印,用performance來(lái)驗(yàn)證一下

開(kāi)始錄制,先觸發(fā)一次垃圾回收清除初始的內(nèi)存,然后點(diǎn)擊三次按鈕,即執(zhí)行了三次點(diǎn)擊事件,最后再觸發(fā)一次垃圾回收。查看錄制結(jié)果發(fā)現(xiàn)JS Heap曲線(xiàn)成階梯上升,并且最終保持的高度比初始基準(zhǔn)線(xiàn)高很多,這說(shuō)明每次執(zhí)行點(diǎn)擊事件創(chuàng)建的很大的數(shù)組對(duì)象obj都因?yàn)閏onsole.log被瀏覽器保存了下來(lái)并且無(wú)法被回收

接下來(lái)注釋掉console.log,再來(lái)看一下結(jié)果

<button>按鈕</button>
<script>
document.querySelector('button').addEventListener('click', function() {
let obj = new Array(1000000)
// console.log(obj);
})
</script>

可以看到?jīng)]有打印以后,每次創(chuàng)建的obj都立馬被銷(xiāo)毀了,并且最終觸發(fā)垃圾回收機(jī)制后跟初始的基準(zhǔn)線(xiàn)同樣高,說(shuō)明已經(jīng)不存在內(nèi)存泄漏的現(xiàn)象了

5.遺忘的定時(shí)器

定時(shí)器也是平時(shí)很多人會(huì)忽略的一個(gè)問(wèn)題,比如定義了定時(shí)器后就再也不去考慮清除定時(shí)器了,這樣其實(shí)也會(huì)造成一定的內(nèi)存泄漏。來(lái)看一個(gè)代碼示例:

<button>開(kāi)啟定時(shí)器</button>
<script>
function fn1() {
let largeObj = new Array(100000)
setInterval(() => {
let myObj = largeObj
}, 1000)
}
document.querySelector('button').addEventListener('click', function() {
fn1()
})
</script>

這段代碼是在點(diǎn)擊按鈕后執(zhí)行fn1函數(shù),fn1函數(shù)內(nèi)創(chuàng)建了一個(gè)很大的數(shù)組對(duì)象largeObj,同時(shí)創(chuàng)建了一個(gè)setInterval定時(shí)器,定時(shí)器的回調(diào)函數(shù)只是簡(jiǎn)單的引用了一下變量largeObj,我們來(lái)看看其整體的內(nèi)存分配情況吧:

按道理來(lái)說(shuō)點(diǎn)擊按鈕執(zhí)行fn1函數(shù)后會(huì)退出該函數(shù)的執(zhí)行上下文,緊跟著函數(shù)體內(nèi)的局部變量應(yīng)該被清除,但圖中performance的錄制結(jié)果顯示似乎是存在內(nèi)存泄漏問(wèn)題的,即最終曲線(xiàn)高度比基準(zhǔn)線(xiàn)高度要高,那么再用Memory來(lái)確認(rèn)一次:

  • 在我們點(diǎn)擊按鈕后,從動(dòng)態(tài)內(nèi)存分配的圖上看到出現(xiàn)一個(gè)藍(lán)色柱形,說(shuō)明瀏覽器為變量largeObj分配了一段內(nèi)存,但是之后這段內(nèi)存并沒(méi)有被釋放掉,說(shuō)明的確存在內(nèi)存泄漏的問(wèn)題,原因其實(shí)就是因?yàn)閟etInterval的回調(diào)函數(shù)內(nèi)對(duì)變量largeObj有一個(gè)引用關(guān)系,而定時(shí)器一直未被清除,所以變量largeObj的內(nèi)存也自然不會(huì)被釋放
  • 那么我們?nèi)绾蝸?lái)解決這個(gè)問(wèn)題呢,假設(shè)我們只需要讓定時(shí)器執(zhí)行三次就可以了,那么我們可以改動(dòng)一下代碼:
<button>開(kāi)啟定時(shí)器</button>
<script>
function fn1() {
let largeObj = new Array(100000)
let index = 0

let timer = setInterval(() => {
if(index === 3) clearInterval(timer);
let myObj = largeObj
index ++
}, 1000)
}

document.querySelector('button').addEventListener('click', function() {
fn1()
})
</script>

現(xiàn)在我們?cè)偻ㄟ^(guò)performance和memory來(lái)看看還不會(huì)存在內(nèi)存泄漏的問(wèn)題

  • performance

  • memory
  • 后我們點(diǎn)擊了按鈕,看到又出現(xiàn)了一個(gè)藍(lán)色柱形,此時(shí)就是為fn1函數(shù)中的變量largeObj分配了內(nèi)存,3s后該內(nèi)存又被釋放了,即變成了灰色柱形。所以我們可以得出結(jié)論,這段代碼不存在內(nèi)存泄漏的問(wèn)題

總結(jié) 

到此這篇關(guān)于前端檢查內(nèi)存泄露的文章就介紹到這了,更多相關(guān)前端檢查內(nèi)存泄露內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

临桂县| 南溪县| 库尔勒市| 林口县| 双柏县| 五莲县| 黄浦区| 扎赉特旗| 南通市| 博湖县| 贵定县| 兴文县| 凤山市| 札达县| 静安区| 星座| 蒙阴县| 策勒县| 留坝县| 都兰县| 奇台县| 孝义市| 搜索| 海城市| 郧西县| 遵化市| 海安县| 五寨县| 永城市| 永城市| 铁岭市| 清涧县| 都江堰市| 长春市| 伊金霍洛旗| 宁海县| 平顺县| 武强县| 陇南市| 汉沽区| 木兰县|