JavaScript內(nèi)存泄漏

JavaScript是一門非常靈活的動態(tài)語言,和Java一樣从媚,JavaScript也具有動態(tài)內(nèi)存回收機制(垃圾回收)逞泄。也就是說,如果一個對象沒有任何人引用了拜效,內(nèi)存就會被自動釋放喷众,不需要像C語言那樣手動調(diào)用free()方法。

所以紧憾,避免JavaScript內(nèi)存泄漏的唯一黃金法則就是:

不要引用不再需要的變量

但現(xiàn)實情況是到千,你一不小心就內(nèi)存泄漏了。如果程序運行變慢赴穗,或者瀏覽器直接崩潰父阻,那肯定是有內(nèi)存泄漏問題了愈涩。內(nèi)存泄漏的罪魁禍首無外乎下面三種情況(代碼見GitHub):

  • 全局變量
  • 全局監(jiān)聽
  • 定時器

下面我們就分別用實例介紹這三種內(nèi)存泄漏情況,并給出了修復內(nèi)存泄漏的方法加矛,以及用Chrome開發(fā)者工具查找內(nèi)存泄漏的方法履婉。

全局變量

全局變量是所有編程語言中最慎用的功能,JavaScript中有兩種聲明全局變量的方式:

  • 在任何函數(shù)外部聲明變量
<script>
var leakObject = new LeakObject();
</script>
  • 給window添加屬性
function addLeak () {
  window.leakObject = new LeakObject();
}

除了這兩種主動聲明全局變量的方式外斟览,還有下面這兩種非常隱蔽的方式也會聲明全局變量毁腿,也就是最常見很隱蔽的內(nèi)存泄漏的方式:

  • 不聲明變量,直接給變量賦值(相當于給window增加了屬性)
function addLeak () {
  leakObject = new LeakObject();
}
  • 函數(shù)體內(nèi)給this賦值屬性苛茂,但調(diào)用函數(shù)時不用對象去調(diào)用已烤,而是直接調(diào)用函數(shù)(這時候this是window)
function addLeak () {
  this.leakObject = new LeakObject();
}

addLeak()

不過上面這2種情況可以通過設(shè)置“嚴格模式”避免,關(guān)于“嚴格模式”妓羊,可以參考MDN:Strict mode胯究。

那主動聲明的全局變量如何釋放內(nèi)存了?答案就是主動將全局變量賦值成null:

function releaseLeak () {
  window.leakObject = null;
}

下面是完整代碼1-global-variable.html

<!DOCTYPE html>
<html>
<head>
<script>
'use strict';

function LeakObject () {
  this.value = new Array(1024 * 1024).join('X');
}

function addLeak () {
  window.leakObject = new LeakObject();
}

function releaseLeak () {
  window.leakObject = null;
}
</script>
</head>
<body>
<button onclick="addLeak()">Add Leak</button>
<button onclick="releaseLeak()">Release Leak</button>
</body>
</html>

打開Chrome開發(fā)者工具躁绸,切換到Memory頁簽裕循,選擇“Record allocation timeline”,點擊開始净刮。然后多次點擊界面的“Add Leak"按鈕剥哑,你會發(fā)現(xiàn)藍色的內(nèi)存分配的進度條在移動。這是因為每次點擊按鈕后淹父,分配了1M的內(nèi)存(為了測試效果明顯new Array(1024 * 1024).join('X'))株婴,但覆蓋了之前全局變量的值,所以之前的藍色進度條變灰了暑认,也就是內(nèi)存被回收了困介。點擊左上角的紅色停止按鈕,然后在搜索欄輸入LeakObject蘸际,就會過濾出我們內(nèi)存泄漏的變量逻翁。最后可以刷新瀏覽器,重新來一次捡鱼,這此點擊網(wǎng)頁中的"Release Leak"按鈕試試,你會發(fā)現(xiàn)這次就沒有內(nèi)存泄漏了酷愧。

1-global-variable.png

全局監(jiān)聽

全局監(jiān)聽是指加在window或者document對象上的監(jiān)聽驾诈,有兩種添加方式:

  • window.on***,比如window.onresize
  • window.addEventListener

下面是全局監(jiān)聽的示例2-global-listener

<!DOCTYPE html>
<html>
<head>
<script>
'use strict';

function LeakObject () {
  this.value = new Array(1024 * 1024).join('X');
}

function addLeak () {
  var leakObject = new LeakObject();
  window.onresize = function () {
    console.log(leakObject.value.length);
  };
}

function releaseLeak () {
  window.onresize = null;
}
</script>
</head>
<body>
<button onclick="addLeak()">Add Leak</button>
<button onclick="releaseLeak()">Release Leak</button>
</body>
</html>

給window.onresize賦值的匿名函數(shù)中用到了變量leakObject溶浴,只要這個函數(shù)還有人引用(比如window.onresize)乍迄,leakObject不會被自動回收。想要修復內(nèi)存泄漏的問題士败,就需要將全局監(jiān)聽刪除闯两。對于window.onresize的形式褥伴,直接將window.onresize賦值為null;對于window.addEventListener的形式漾狼,需要將匿名函數(shù)用變量保存起來(比如listener)重慢,然后調(diào)用window.removeEventListener(listener)。

定時器

有兩類定時器:

  • setInterval 一般用于定時請求后臺數(shù)據(jù)逊躁,刷新界面
  • requestAnimationFrame 一般用于動畫似踱,IE10或以上版本才支持。雖然setInverval也能用于實現(xiàn)動畫功能稽煤,但requestAnimationFrame更有優(yōu)勢核芽,比如瀏覽器切換到其他Tab頁后,動畫會被暫停

定時器示例如下3-setInterval

<!DOCTYPE html>
<html>
<head>
<script>
'use strict';

function LeakObject () {
  this.value = new Array(1024 * 1024).join('X');
}

function addLeak () {
  var leakObject = new LeakObject();
  window._intervalId = setInterval(function () {
    console.log(leakObject.value.length);
  }, 1000);
}

function releaseLeak () {
  clearInterval(window._intervalId);
  window._intervalId = null;
}
</script>
</head>
<body>
<button onclick="addLeak()">Add Leak</button>
<button onclick="releaseLeak()">Release Leak</button>
</body>
</html>

計時器的回調(diào)函數(shù)中用到了leakObject酵熙,所以只要這個回調(diào)函數(shù)有人引用(window對象有引用)轧简,leakObject就不會被回收,修復方法是調(diào)用clearInterval匾二、cancelAnimationFrame清除定時器哮独。

定時器引起的內(nèi)存泄漏,在內(nèi)存快照中搜索不出來假勿,但從內(nèi)存分配中能看到借嗽。如下圖,點擊2次Add Leak按鈕后转培,內(nèi)存分配為2M恶导,但搜索不到LeakObject:

3-setInterval.png

剛才的全局監(jiān)聽器和定時器都是由于閉包(Clousure)中引用了外部變量導致的內(nèi)存泄漏,這里有個更明顯的由于閉包引起的內(nèi)存泄漏的例子4-closure

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
'use strict';

function LeakObject () {
  this.value = new Array(1024 * 1024).join('X');
}

var leakObject = null;

function addLeak() {
  var oldObj = leakObject;
  leakObject = {
    leakObj: new LeakObject(),
    closure: function () {
      console.log(oldObj);
    }
  };
}

function releaseLeak () {
  leakObject = null;
}
</script>
</head>
<body>
<button onclick="addLeak()">Add Leak</button>
<button onclick="releaseLeak()">Release Leak</button>
</body>
</html>

上面addLeak函數(shù)的代碼相當于如下代碼浸须,也就是構(gòu)造了一個對象鏈表:

function addLeak() {
  var oldObj = leakObject;
  leakObject = {
    leakObj: new LeakObject(),
    oldObj: oldObj
  };
}

不停的調(diào)用addLeak函數(shù)后惨寿,會引發(fā)一連串的內(nèi)存泄漏:

4-closure.png

最后,IE6删窒、7在對象相互引用時也會存在內(nèi)存泄漏裂垦,相信現(xiàn)在沒人用IE6、7了肌索,這里就不介紹了蕉拢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市诚亚,隨后出現(xiàn)的幾起案子晕换,更是在濱河造成了極大的恐慌,老刑警劉巖站宗,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闸准,死亡現(xiàn)場離奇詭異,居然都是意外死亡梢灭,警方通過查閱死者的電腦和手機夷家,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門蒸其,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人库快,你說我怎么就攤上這事摸袁。” “怎么了缺谴?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵但惶,是天一觀的道長。 經(jīng)常有香客問我湿蛔,道長膀曾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任阳啥,我火速辦了婚禮添谊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘察迟。我一直安慰自己斩狱,他們只是感情好,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布扎瓶。 她就那樣靜靜地躺著所踊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪概荷。 梳的紋絲不亂的頭發(fā)上秕岛,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天,我揣著相機與錄音误证,去河邊找鬼继薛。 笑死,一個胖子當著我的面吹牛愈捅,可吹牛的內(nèi)容都是我干的遏考。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蓝谨,長吁一口氣:“原來是場噩夢啊……” “哼灌具!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起譬巫,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤咖楣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缕题,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡胖腾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年烟零,在試婚紗的時候發(fā)現(xiàn)自己被綠了瘪松。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡锨阿,死狀恐怖宵睦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情墅诡,我是刑警寧澤壳嚎,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站末早,受9級特大地震影響烟馅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜然磷,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一郑趁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧姿搜,春花似錦寡润、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至致份,卻和暖如春变抽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背知举。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工瞬沦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雇锡。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓逛钻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锰提。 傳聞我的和親對象是個殘疾皇子曙痘,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容