談一談我在前端開發(fā)的時候遇到的過的內存泄漏

其實無論在開發(fā)什么,或多或少都會遇到內存泄漏。但是究其根本,問題大多都是存在于代碼的緣故,作為一名有追求的開發(fā)人員,我們不僅要追求功能,更要追求代碼的性能。今天我就抽點時間來談談所謂的內存泄漏。

一、 什么是內存泄漏

有點程序基礎的知道,程序的運行是需要分配內存空間的。而對于一個持續(xù)使用的網(wǎng)頁端來說,如果一些不能用到的內存沒有被及時釋放,這就叫內存泄漏。但是我們都知道,我們網(wǎng)頁端的承載量是有限的,不斷的往一個氣球里面不斷吹氣,氣球總有吹爆的一刻。瀏覽器的表現(xiàn)就是瀏覽器崩潰。

不斷鼓氣的氣球

二、 js的垃圾回收機制

js中的內存回收機制采用的是引用計數(shù):垃圾回收器會定期掃描內存,當某個內存中的值被引用為零時就會將其回收。當前變量已經(jīng)使用完畢但依然被引用,導致垃圾回收器無法回收這就造成了內存泄漏。

引用計數(shù)示意圖

三、內存泄漏的識別辦法

對于內存泄漏識別辦法,我主要是從阮大神的博客里面學習到的。主要有:瀏覽器和命令行兩種

3.1、 瀏覽器

  • 首先打開chrome,然后按下f12(windows)/option+command+i打開調試工具,選擇memory。
  • 根據(jù)下面的視圖我們看到一共有Heap snapshot(JS堆快照),Allocation instrumentation on timeline(JS堆分配時間線),Allocation sampling三種堆快照類型
  • 開始錄制前先點擊垃圾回收,再點擊錄制,單如果是js堆內存動態(tài)分配時間線的話,結束之前要再次點擊下垃圾回收,再結束錄制。


3.1.1

  • Summary 總覽視圖:按構造函數(shù)分組。用于捕捉對象及其使用的內存。對于定位DOM內存泄露特別有用。
  • Comparison 對比視圖:對比兩個快照。用于對比不同操作之后的堆快照,查看內存的釋放及引用計數(shù),來分析內存是否泄露及其原因。
  • Containment 內容視圖:查看堆內容。更適合查看對象結構,有助于分析對象的引用情況。適用于分析閉包以及深入分析對象。
  • Statistics 統(tǒng)計視圖:總覽堆的統(tǒng)計信息。
3.1.1.1、 Summary總覽視圖
  • Constructor:構造函數(shù),節(jié)點下的對象都是由改構造函數(shù)創(chuàng)建而來。

  • Distance:與根節(jié)點的距離。

  • Objects Count:對象個數(shù)及百分占比。

  • Shallow size:對象的直接內存總數(shù),直接內存是指對象自身占用的內存大小。

  • Retained size:對象的最大保留內存,保留內存是指對象被刪除后可以釋放的那部分內存。

    點擊展開構造函數(shù),可以看到所有構造函數(shù)相關的對象實例,@后面的數(shù)字是該對象實例的唯一標識符。

    常見的頂層構造函數(shù):

  • (global property):全局對象和普通對象的中間對象,和常規(guī)思路不同。比如在Window上定義了一個Person對象,那么他們之間的關系就是[global] => (global property) => Person。之所以使用中間對象,是出于性能的考慮。

  • (closure):使用函數(shù)閉包的對象。

  • (array, string, number, regexp):一系列對象類型,其屬性指向Array/String/Number/Regexp。

  • HTMLDivElement/HTMLAnchorElement/DocumentFragment:元素的引用或者代碼引用的指定文檔對象。

3.1.2 Comparasion對比視圖

為了驗證特定操作會不會引起內存泄露,對比快照的步驟如下:
1、無任何操作,拍第一個堆快照
2、執(zhí)行你覺得可能造成內存泄露的操作,再執(zhí)行相反操作
3、拍第二個堆快照,切換到對照視圖,并且指定與第一個堆快照對比

3.1.2 JS堆分配時間線

通過Allocation instrumentation on timeline可以持續(xù)的記錄堆分配的情況,顯示了對象在什么時候被創(chuàng)建、什么時候存在內存泄漏等。



上面的柱條表示堆中生成的新對象。高度表示這個對象的大小,顏色表示這個對象的內存釋放情況:藍色柱表示這個對象在timeline中生成,結束前仍然存在;灰色柱表示這個對象在timeline中生成,但結束前已經(jīng)被回收了。
我們可以重復執(zhí)行某個動作,如果最后有不少藍色柱被保留,這些藍色柱就是潛在的內存泄露問題。
如果左邊的意料之外的藍條,那么極有可能存在內存泄露。

上面是Vue項目反復切換兩個錄制的堆分配行為,我們可以聚焦到某一次堆分配,查看具體對象信息??梢栽谥鶢顖D中滑動鼠標滾輪查看某段時間的堆分配。比如上面發(fā)現(xiàn)有三個VueComponent沒有回收。點擊展開查看詳細信息。發(fā)現(xiàn)這三個組件的信息都是一樣的,那就是組件沒有釋放。首先確認組件是否被銷毀。如果已銷毀,確認事件是否解綁、定時器是否取消,特別注意事件總線綁定的事件一定要解綁。

3.2、 命令行

命令行可以使用 Node 提供的process.memoryUsage方法。
process.memoryUsage返回一個對象,包含了 Node 進程的內存占用信息。該對象包含四個字段,單位是字節(jié),含義如下。

  • rss(resident set size):所有內存占用,包括指令區(qū)和堆棧。
  • heapTotal:"堆"占用的內存,包括用到的和沒用到的。
  • heapUsed:用到的堆的部分。
  • external: V8 引擎內部的 C++ 對象占用的內存。

判斷內存泄漏,以heapUsed字段為準。

四、vue中存在的內存泄漏

由于在vue單頁面中,頁面進行跳轉的時候沒有刷新頁面,這就造成了內存泄漏不斷堆積,導致頁面卡頓或者頁面崩潰。這里主要介紹我在開發(fā)的時候遇到的一些內存泄漏情況。

4.1、 閉包

function closure(){
    var element = document.getElementById('mydiv');//element用完之后一直駐留在內存中
    var test = element.innerHTML;
    element.onclick = function () {
        alert(test);//這里用element導致內存泄露
    };
}
closure();

function closure(){
    var element = document.getElementById('mydiv');
    var test = element.innerHTML;
    element.onclick = function () {
         alert('test');
    };
    element = null;//這里直接回收了
}

4.2、 意外的全局變量

function foo(arg) {
    bar = "aaaaa";
}
 
實際上等價于
function foo(arg) {
    window.bar = "aaaaa";
}

這種情況是很多人都會見到的,我建議是使用es6的let或者是使用嚴格模式。

4.3、 定時器

var data=getData();  
setInterval(function(){  
    var node=document.getElementById("name");  
    if(node){  
        node.innerHTML=JSON.stringify(data)  
    }  
},1000)  

4.4、 在生命周期中使用全局事件,然后沒有做釋放處理

onCreate(){
  bus.%on('')
},
beforeDestroy() {
  bus.$off('****');
}

4.5、 dom對象和js對象的互相引用,未置空

var obj = {}; 
document.getElementById('idname').property = testObject;  //obj會一直存在直到dom被回收,這可能會造成內存泄漏

解決辦法:

window.onunload=function(){
    document.getElementById('idname').property = null;         //釋放內存
};

4.6、 keep-alive組件

在移除元素時的時候,如果你打算在內存中保留狀態(tài)和元素該怎么做呢?這種情況下,你可以使用內建的 keep-alive組件。
當你用 keep-alive 包裹一個組件后,它的狀態(tài)就會保留,因此就留在了內存里。

<button @click="show = false">Hide</button>
<keep-alive>
  <!-- `<my-component>` 即便被刪除仍會刻意保留在內存里 -->
  <my-component v-if="show"></my-component>
</keep-alive>

這個技巧可以用來提升用戶體驗。例如,設想一個用戶在一個文本框中輸入了評論,之后決定導航離開。如果這個用戶之后導航回來,那些評論應該還保留著。

一旦你使用了 keep-alive,那么你就可以訪問另外兩個生命周期鉤子:activated 和 deactivated。如果你想要在一個 keep-alive 組件被移除的時候進行清理或改變數(shù)據(jù),可以使用 deactivated 鉤子。

deactivated: function () {
  // 移除任何你不想保留的數(shù)據(jù)
}

4.7、echarts引起的內存泄漏

解決方案

   chart.dispose(myChart)
   myChart = chart.init(document.getElementById(dom));
  myChart.setOption(option);

說在最后

其實所謂的內存泄漏,無非就是用過的東西沒有及時的回收 導致部分內存長期被占用,一個優(yōu)秀的程序員是不回讓內存長期保持高占用的狀態(tài)的。雖然前端只管實現(xiàn)和頁面效果,但是深究起頁面性能還是有點說法的。作為一名有追求的程序員,還是希望自己能夠多多注意這些方面,多多提升自己在敲代碼的時候的專業(yè)知識積累。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容