JS前端性能優(yōu)化解決內(nèi)存泄漏頁(yè)面崩潰
發(fā)生什么事了?
一個(gè)與往常一樣的上午,當(dāng)我沉浸在業(yè)務(wù)需求中不可自拔時(shí),突然被拉入到一個(gè)事故大群。一臉懵逼的我還以為和之前的每次線上bug一樣僅僅是個(gè)小問(wèn)題時(shí),殊不知是我簡(jiǎn)單了...
看著群里的聊天記錄,瞬間一種不好的預(yù)感涌上心頭。不會(huì)是哪個(gè)頁(yè)面寫了死循環(huán)了吧?
咋了?這是咋了?
死去的頁(yè)面突然攻擊我?
因?yàn)轫?xiàng)目本身過(guò)于龐大,且用戶反饋不特定頁(yè)面崩潰,這使得問(wèn)題定位難度較大。
經(jīng)過(guò)團(tuán)隊(duì)的討論認(rèn)為可能是該用戶的組織架構(gòu)接口數(shù)據(jù)量過(guò)大,當(dāng)數(shù)據(jù)到達(dá)頁(yè)面后需要經(jīng)過(guò)遞歸處理,導(dǎo)致內(nèi)存不足,且通過(guò)調(diào)試發(fā)現(xiàn)并非初次調(diào)用該接口就會(huì)導(dǎo)致崩潰,而是需要多次調(diào)用才會(huì)出現(xiàn)該問(wèn)題。
而且在調(diào)試的過(guò)程中還發(fā)現(xiàn)因數(shù)據(jù)量過(guò)大,該接口需要長(zhǎng)達(dá) 30s 的時(shí)間才能返回。而這么長(zhǎng)的時(shí)間用戶可能已經(jīng)離開這個(gè)頁(yè)面了。我們嘗試以這個(gè)場(chǎng)景進(jìn)行操作發(fā)現(xiàn),用戶離開頁(yè)面并沒(méi)有取消已經(jīng)發(fā)出的請(qǐng)求,當(dāng)該請(qǐng)求響應(yīng)后會(huì)導(dǎo)致內(nèi)存占用飆升。
因該接口為項(xiàng)目全局接口,經(jīng)過(guò)討論我們決定先修復(fù)這個(gè)離開頁(yè)面不取消請(qǐng)求的問(wèn)題,測(cè)試能否解決崩潰問(wèn)題。
陷入僵局
當(dāng)我們?cè)陔x開頁(yè)面時(shí)取消未完成的請(qǐng)求后,測(cè)試反饋仍然會(huì)不定時(shí)崩潰,此時(shí)的我們有點(diǎn)無(wú)從下手了。因?yàn)槲覀儼l(fā)現(xiàn)問(wèn)題貌似有點(diǎn)大了。
此時(shí)我們已經(jīng)開始懷疑出現(xiàn)了內(nèi)存泄漏,于是我們祭出了 chrome-devtool
使用 Memory
來(lái)進(jìn)行內(nèi)存占用分析。
經(jīng)過(guò)內(nèi)存分析發(fā)現(xiàn),內(nèi)存中存在大量的分離元素未能及時(shí)回收。我按照內(nèi)存快照的指引開始了漫長(zhǎng)的修改。兩天的修改后發(fā)現(xiàn),雖然修復(fù)了不少的問(wèn)題,但是仍然不能有效的降低頁(yè)面的內(nèi)存占用,每當(dāng)我們跳轉(zhuǎn)新的頁(yè)面時(shí)某些頁(yè)面仍然不能正常釋放。此時(shí)我為了高效定位問(wèn)題,開始使用絕招——刪代碼。
將頁(yè)面中的組織架構(gòu)樹刪除——內(nèi)存占用沒(méi)降下來(lái)...
將頁(yè)面的列表刪除——很棒空頁(yè)面果真降下來(lái)了
將組織架構(gòu)樹加上,列表刪除——內(nèi)存又起來(lái)了...
此時(shí)的我認(rèn)為組織架構(gòu)樹的寫法有問(wèn)題,所以花了兩天的時(shí)間過(guò)了一遍組織架構(gòu)樹的代碼,未發(fā)現(xiàn)有什么異常的地方。
為了確認(rèn)是組織架構(gòu)樹的問(wèn)題還是頁(yè)面列表的問(wèn)題,我在項(xiàng)目中新建了兩個(gè)空白的路由頁(yè)面。在這個(gè)頁(yè)面中單獨(dú)使用這兩個(gè)組件,都沒(méi)有問(wèn)題。
問(wèn)題陷入了僵局...
垂死病中驚坐起
在我們修復(fù)這個(gè)問(wèn)題的期間,另一個(gè)用戶也反饋了相同的問(wèn)題過(guò)來(lái),不過(guò)用戶說(shuō)他用的是edge瀏覽器。此時(shí)同事說(shuō)edge好像可以撐的更久一點(diǎn)。??哇,真的嗎?難道和瀏覽器有關(guān)系?
于是我又打開了edge瀏覽器,點(diǎn)開了devtool點(diǎn)開了Memory...哎?這是什么
edge的devtool這么好嗎,專門有獨(dú)立的標(biāo)簽用來(lái)查找分離的元素啊,蠻人性化的嘛??墒沁@和chrome不一樣呀,咋用呢?(戳這里)
它真的好智能,竟然已經(jīng)把所有泄漏的dom按照結(jié)構(gòu)組織好了,這樣就不用我再?gòu)囊欢褍?nèi)存信息中一個(gè)個(gè)找它們的關(guān)聯(lián)關(guān)系了。
從上圖我發(fā)現(xiàn)原來(lái)是有一個(gè)全局的tip()
方法導(dǎo)致了整個(gè)頁(yè)面沒(méi)能正?;厥?,用戶每進(jìn)一次這個(gè)頁(yè)面就會(huì)在內(nèi)存里多一份這個(gè)頁(yè)面的dom節(jié)點(diǎn)。而這個(gè)頁(yè)面又有完整的組織架構(gòu)樹,組織架構(gòu)樹又很大...
查看了tip()
方法的源碼發(fā)現(xiàn),這是一個(gè)全局的指令,這個(gè)指令每次都會(huì)創(chuàng)建一個(gè)新的tip對(duì)象,而這個(gè)tip對(duì)象與頁(yè)面上的dom節(jié)點(diǎn)關(guān)聯(lián),且永遠(yuǎn)不會(huì)被銷毀。
類似的問(wèn)題在列表中有個(gè)對(duì)表格行拖拽排序的功能,使用了第三方包sortablejs
而未調(diào)用destory()
方法銷毀sortable
實(shí)例,進(jìn)而導(dǎo)致表格與頁(yè)面也未能正確釋放。
勿以善小而不為
到此,大概的原因已經(jīng)明確了。未及時(shí)銷毀的無(wú)用對(duì)象導(dǎo)致了大面積的內(nèi)存泄漏,疊加用戶的配置較低(8G內(nèi)存,且開啟了很多頁(yè)面),導(dǎo)致了頁(yè)面的崩潰。
類似以上的問(wèn)題在本次修復(fù)中還發(fā)現(xiàn)不少,如:
- 公共彈窗組件將傳入的子組件持有,在關(guān)閉彈窗后未能銷毀子組件。
- 全局的
EventBus
實(shí)例未及時(shí)調(diào)用$off
方法銷毀事件,而事件回調(diào)與頁(yè)面又產(chǎn)生關(guān)聯(lián)(如:$refs、$parents
) - 組件接受來(lái)自頁(yè)面的方法,而該組件未能在頁(yè)面離開時(shí)銷毀(如在組件內(nèi)將組件的this綁定在window上),導(dǎo)致整個(gè)頁(yè)面不能釋放
- 為了在頁(yè)面resize時(shí)能夠調(diào)節(jié)echarts大小,在window上綁定了resize回調(diào),離開頁(yè)面時(shí)為清除resize事件
- 組件實(shí)例化時(shí)在document上綁定click事件后,銷毀組件時(shí)為清除click事件
以上的每一個(gè)問(wèn)題都是小問(wèn)題,僅僅是因?yàn)闆](méi)用在beforeDestroy
中調(diào)用Element.removeEventListener()
或者未調(diào)用destroy()
方法。但是會(huì)導(dǎo)致很大的后患。
修復(fù)后的頁(yè)面已經(jīng)能夠達(dá)到較為正常的內(nèi)存占用了,在此給大家放一張測(cè)試同學(xué)提供的修復(fù)前后的內(nèi)存占用對(duì)比圖,相較于修復(fù)前的內(nèi)存占用只增不減(峰值4G,然后崩潰)到現(xiàn)在的能及時(shí)回收(最復(fù)雜的頁(yè)面峰值500M,普通頁(yè)面100M以內(nèi)),已經(jīng)有了極大的提升。
修改參考
const handleResizeWindow = () => { myChart.resize(); } window.addEventListener("resize", handleResizeWindow); this.$once('hook:beforeDestroy', () => { myChart.dispose() // 銷毀echart實(shí)例 window.removeEventListener('resize', handleResizeWindow) })
created () { window.removeEventListener('beforeunload', this.closePage) }, ... beforeDestroy () { window.removeEventListener('beforeunload', this.closePage) }
mounted() { this.$eventBus.$on("eventName", this.handleEvent); this.$once('hook:beforeDestroy', () => { this.$eventBus.$off('eventName', this.handleEvent) }) }
以上就是JS前端性能優(yōu)化解決內(nèi)存泄漏頁(yè)面崩潰的詳細(xì)內(nèi)容,更多關(guān)于JS內(nèi)存泄漏頁(yè)面崩潰的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序之選項(xiàng)卡的實(shí)現(xiàn)方法
這篇文章主要介紹了 微信小程序之選項(xiàng)卡的實(shí)現(xiàn)方法的相關(guān)資料,希望大家通過(guò)本文能實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下2017-09-09JavaScript的function函數(shù)詳細(xì)介紹
這篇文章主要介紹了JavaScript的function函數(shù)詳細(xì),而我們的JavaScript腳本語(yǔ)言比較特殊,相對(duì)于C語(yǔ)言,它的參數(shù)是不需要數(shù)據(jù)類型加持的。返回值return,我就不過(guò)多描述,他是和 C語(yǔ)言通的,如果沒(méi)寫他就會(huì)自動(dòng)返回undefined,下面一起來(lái)看看文章內(nèi)容,需要的朋友可以參考一下2021-11-11微信小程序中的onLoad詳解及簡(jiǎn)單實(shí)例
這篇文章主要介紹了微信小程序中的onLoad詳解及簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-04-04TypeScript 內(nèi)置高級(jí)類型編程示例
這篇文章主要為大家介紹了TypeScript 內(nèi)置高級(jí)類型編程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09用Move.js配合創(chuàng)建CSS3動(dòng)畫的入門指引
這篇文章主要介紹了用Move.js配合創(chuàng)建CSS3動(dòng)畫的入門指引,文中介紹了這個(gè)JavaScript庫(kù)中的一些基本方法的使用,需要的朋友可以參考下2015-07-07