其實無論在開發(fā)什么,或多或少都會遇到內存泄漏。但是究其根本,問題大多都是存在于代碼的緣故,作為一名有追求的開發(fā)人員,我們不僅要追求功能,更要追求代碼的性能。今天我就抽點時間來談談所謂的內存泄漏。
一、 什么是內存泄漏
有點程序基礎的知道,程序的運行是需要分配內存空間的。而對于一個持續(xù)使用的網(wǎng)頁端來說,如果一些不能用到的內存沒有被及時釋放,這就叫內存泄漏。但是我們都知道,我們網(wǎng)頁端的承載量是有限的,不斷的往一個氣球里面不斷吹氣,氣球總有吹爆的一刻。瀏覽器的表現(xiàn)就是瀏覽器崩潰。

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

三、內存泄漏的識別辦法
對于內存泄漏識別辦法,我主要是從阮大神的博客里面學習到的。主要有:瀏覽器和命令行兩種
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è)知識積累。
