JavaScript中的內(nèi)存泄漏以及如何處理


title: JavaScript中的內(nèi)存泄漏以及如何處理
date: 2017-11-21 21:27:04
tags:


隨著現(xiàn)代編程語言功能越來越成熟钱慢、復(fù)雜,內(nèi)存管理也容易被大家忽略诫咱。本文將討論Javascript中的內(nèi)存泄露以及如何處理莫辨,方便大家在使用JavaScript編碼時(shí),更好的應(yīng)對(duì)內(nèi)存泄露帶來的問題结啼。

概述

當(dāng)創(chuàng)建對(duì)象和字符串等時(shí)掠剑,JavaScript就會(huì)分配內(nèi)存,并在不使用時(shí)自動(dòng)釋放內(nèi)存郊愧,這種機(jī)制被稱為垃圾收集朴译。這種釋放資源看似是“自動(dòng)”的井佑,但本質(zhì)是混淆的,這也給JavaScript的開發(fā)人員產(chǎn)生了可以不關(guān)心內(nèi)存管理的錯(cuò)誤印象眠寿。其實(shí)這是一個(gè)大錯(cuò)誤躬翁。

什么是內(nèi)存泄露

內(nèi)存泄露是應(yīng)用程序使用過的內(nèi)存片段,在不再需要時(shí)澜公,不能返回到操作系統(tǒng)或可用內(nèi)存池中的情況姆另。

編程語言有各自不同的內(nèi)存管理方式。但是是否使用某一段內(nèi)存坟乾,實(shí)際上是一個(gè)不可判定的問題迹辐。換句話說,只有開發(fā)人員明確的知道是否需要將一塊內(nèi)存返回給操作系統(tǒng)甚侣。

四種常見的JavaScript內(nèi)存泄露

1.全局變量

JavaScript以一種有趣的方式來處理未聲明的變量:當(dāng)引用未聲明的變量時(shí)明吩,會(huì)在全局對(duì)象中創(chuàng)建一個(gè)新變量。在瀏覽器中殷费,全局對(duì)象將是window印荔,這意味著

function foo(arg){
    bar = "some text";
}

相當(dāng)于:

function foo(arg){
    window.bar = "some text";
}   

bar只是foo函數(shù)中引用一個(gè)變量。如果你不適用var聲明详羡,將會(huì)創(chuàng)建u多余的全局變量仍律。在上述情況下,不會(huì)造成很大的問題实柠。但是水泉,若是下面的這種情況。你可能不小心創(chuàng)建一個(gè)全局變量this:

function foo(){
    this.val1 = "potential accidental global";
}
// Foo called on its own, this points to the global object (window) rather than being undefined.
foo();

你可以通過在JavaScript文件的開始處添加‘use strict’窒盐;來避免這種錯(cuò)誤草则,這種方式將開啟嚴(yán)格的解析JavaScript模式,從而防止意外創(chuàng)建全局變量蟹漓。

意外的全局變量當(dāng)然是一個(gè)問題炕横。更多的時(shí)候,你的代碼會(huì)受到顯示的全局變量的影響葡粒,而這些全局變量在垃圾收集器中是無法收集份殿。需要特別注意用于臨時(shí)存儲(chǔ)和處理大量信息的全局變量。如果必須使用全局變量來存儲(chǔ)數(shù)據(jù)嗽交,那么確保將其分配為空值伯铣,或者在完成后重新分配。

2.被遺忘的定時(shí)器或回調(diào)

下面列舉setInterval的例子轮纫,這也是經(jīng)常在JavaScript中使用。

對(duì)于提供監(jiān)視的庫和其他接收回調(diào)的工具焚鲜,通常在確保所有回調(diào)的引用在其實(shí)例無法訪問時(shí)掌唾,會(huì)變成無法訪問的狀態(tài)放前。但是下面的代碼卻是一個(gè)例外:

var serverData = loadData();
setInterval(function(){
    var renderer = document.getElementById('render');
    if(renderer){
        renderer.innerHTML = JSON.stringify(serverData);
    }
},5000); // This will be executed every ~5 seconds.

上面的代碼片段顯示了使用引用節(jié)點(diǎn)或不再需要的數(shù)據(jù)的定時(shí)器的結(jié)果。
該renderer對(duì)象可能會(huì)在某些時(shí)候被替換或刪除糯彬,這會(huì)使interval處理程序封裝的塊變得冗余凭语。如果發(fā)生這種情況,那么處理程序及其依賴項(xiàng)都不會(huì)被收集撩扒,因?yàn)閕nterval需要先停止似扔。這一切都?xì)w結(jié)為存儲(chǔ)和處理負(fù)載數(shù)據(jù)的serverData不會(huì)被收集的原因。

當(dāng)使用監(jiān)視器時(shí)搓谆,你需要確保做了一個(gè)明確的調(diào)用來刪除它們炒辉。
幸運(yùn)的是,大多數(shù)現(xiàn)代瀏覽器都會(huì)為你做這件事:即使你忘記刪除監(jiān)聽器泉手,當(dāng)監(jiān)測(cè)對(duì)象變得無法訪問黔寇,它們就會(huì)自動(dòng)收集監(jiān)測(cè)處理器。這是過去的一些瀏覽器無法處理的情況(例如舊的IE6)斩萌。

看下面的例子:

var element = document.getElementById('launch-button');
var counter = 0;
function onclick(event){
    counter++;
    element.innerHTML = 'text '+ counter;
}
element.addEventListener('click',onClick);

// Do stuff 

element.removeEventListener('click',onClick);
element.parentNode.removeChild(element);

// Now when element goes out of scope, both element and onClick will be collected event in old browsers that don't handle cycles well.

由于現(xiàn)代瀏覽器支持垃圾回收機(jī)制缝裤,所以當(dāng)某個(gè)節(jié)點(diǎn)變得不能訪問時(shí),你不再需要調(diào)用removeEventListener,因?yàn)槔厥諜C(jī)制會(huì)恰當(dāng)?shù)奶幚磉@些節(jié)點(diǎn)颊郎。

如果你正在使用jQueryAPI(其他庫和框架也支持這一點(diǎn))憋飞,那么也可以在節(jié)點(diǎn)不用之前刪除監(jiān)聽器。即使應(yīng)用程序在較舊的瀏覽器版本下運(yùn)行姆吭,庫也會(huì)確保沒有內(nèi)存泄露榛做。

3.閉包

JavaScript開發(fā)的是一個(gè)關(guān)鍵方面是閉包。閉包是一個(gè)內(nèi)部函數(shù)猾编,可以訪問外部(封閉函數(shù))函數(shù)的變量瘤睹。由于JavaScript運(yùn)行時(shí)的實(shí)現(xiàn)細(xì)節(jié),可能存在一下形式內(nèi)存泄露:

var theThing = null;
var replaceThing = function(){
    var originalThing = theThis;
    var unused = function(){
        if(originalThing) // 對(duì)‘originalThing’的引用
        console.log('hi');
    }
    theThing = {
        longStr: new Array (1000000).join('*'),
        someMethod: function(){
            console.log("message");
        }
    };
};

setInterval(replaceThing,1000);

一旦replaceThing被調(diào)用答倡,theThing會(huì)獲取由一個(gè)大數(shù)組和一個(gè)新的閉包(someMthod)組成的新對(duì)象轰传。然而,originalThing會(huì)被unused變量所持有的閉包所引用(這是theThing從以前的調(diào)用變量replaceThing)瘪撇。需要記住的是获茬,一旦在同一父作用域中為閉包創(chuàng)建了閉包的作用域,作用域就被共享了倔既。

在這種情況下恕曲,閉包創(chuàng)建的范圍會(huì)將someMethod共享給unused。然而渤涌,unused有一個(gè)originalThing引用佩谣。即使unused從未使用過,someMethod也可以通過theThing在整個(gè)范圍之外使用replaceThing实蓬。而且someMethod通過unused共享了閉包范圍茸俭,unused必須引用originalThing以便使其它保持活躍(兩封閉之間的整個(gè)共享范圍)吊履。這就阻止了它被收集。

所有這些都可能導(dǎo)致相當(dāng)大的內(nèi)存泄露调鬓。當(dāng)上面的代碼片段一遍又一遍地運(yùn)行時(shí)艇炎,你會(huì)看到內(nèi)存使用率的不斷上升。當(dāng)垃圾收集器運(yùn)行時(shí)腾窝,其內(nèi)存大小不會(huì)縮小缀踪,這種情況會(huì)創(chuàng)建一個(gè)閉包的鏈表,并且每個(gè)閉包范圍都帶有對(duì)大數(shù)組的間接引用虹脯。

4.超出DOM引用

在某些情況下驴娃,開發(fā)人員會(huì)在數(shù)據(jù)結(jié)構(gòu)中存儲(chǔ)DOM節(jié)點(diǎn),例如你想快速更新表格中的幾行內(nèi)的情況归形。如果在字典或數(shù)組中存儲(chǔ)對(duì)每個(gè)DOM行的引用托慨,則會(huì)有兩個(gè)對(duì)同一個(gè)DOM元素的引用:一個(gè)在DOM樹中,另一個(gè)在字典中暇榴。如果你不再需要這些行厚棵,則需要使兩個(gè)引用都無法訪問。

var elements = {
    button:document.getElementById('button'),
    image:document.getElementById('image'),
};

function doStuff(){
    elements.image.src = "http://example.com/image_name.png";
}

function removeImage(){
    // The image is a direct child of the body element.
    document.body.removeChild(document.getElementById('image'));
    // At this point, we still have a reference to #button in the global elements object, In other words, the button element is still in memory and cannot be collected by the GC.
}

在涉及DOM樹內(nèi)的內(nèi)部節(jié)點(diǎn)或葉節(jié)點(diǎn)時(shí)蔼紧,還有一個(gè)額外的因素需要考慮婆硬。如果你在代碼中保留對(duì)表格單元格(標(biāo)簽)的引用,并決定從DOM中刪除該表格奸例,還需要保留對(duì)該特定單元格的引用彬犯,則可能會(huì)出現(xiàn)嚴(yán)重的內(nèi)存泄露,你可能會(huì)認(rèn)為垃圾收集器會(huì)釋放除了那個(gè)單元之外的所有東西查吊,但情況并非如此谐区。由于單元格是表格的一個(gè)子節(jié)點(diǎn),并且子節(jié)點(diǎn)保留著對(duì)父節(jié)點(diǎn)的引用逻卖,所以對(duì)表格單元格的這種引用宋列,會(huì)將整個(gè)表格保存在內(nèi)存中。

總結(jié)

以上內(nèi)容是對(duì)JavaScript常見的四種內(nèi)存泄露分析评也。希望對(duì)JavaScript編程人員有用炼杖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市盗迟,隨后出現(xiàn)的幾起案子坤邪,更是在濱河造成了極大的恐慌,老刑警劉巖罚缕,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艇纺,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)黔衡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門消约,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人员帮,你說我怎么就攤上這事〉妓牵” “怎么了捞高?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)渣锦。 經(jīng)常有香客問我硝岗,道長(zhǎng),這世上最難降的妖魔是什么袋毙? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任型檀,我火速辦了婚禮,結(jié)果婚禮上听盖,老公的妹妹穿的比我還像新娘胀溺。我一直安慰自己,他們只是感情好皆看,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布仓坞。 她就那樣靜靜地躺著,像睡著了一般腰吟。 火紅的嫁衣襯著肌膚如雪无埃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天毛雇,我揣著相機(jī)與錄音嫉称,去河邊找鬼。 笑死灵疮,一個(gè)胖子當(dāng)著我的面吹牛织阅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播始藕,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蒲稳,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了伍派?” 一聲冷哼從身側(cè)響起江耀,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诉植,沒想到半個(gè)月后祥国,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年舌稀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了啊犬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壁查,死狀恐怖觉至,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情睡腿,我是刑警寧澤语御,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站席怪,受9級(jí)特大地震影響应闯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挂捻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一碉纺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刻撒,春花似錦骨田、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捧搞,卻和暖如春抵卫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胎撇。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來泰國打工介粘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晚树。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓姻采,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親慨亲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子宝鼓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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