淺談JavaScript中內(nèi)存泄漏的幾種情況
一、內(nèi)存泄漏是什么?
內(nèi)存泄漏(Memory leak)是再計(jì)算機(jī)科學(xué)中,由于疏忽或錯(cuò)誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存。
并非指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,由于設(shè)計(jì)錯(cuò)誤,導(dǎo)致在釋放該段內(nèi)存之前就失去了對(duì)該段內(nèi)存的控制,從而造成了內(nèi)存的浪費(fèi)。
程序的運(yùn)行需要內(nèi)存。只要程序提出要求,操作系統(tǒng)或者運(yùn)行時(shí)就必須供給內(nèi)存。
對(duì)于持續(xù)運(yùn)行的服務(wù)進(jìn)程,必須及時(shí)釋放不再用到的內(nèi)存。否則,內(nèi)存占用越來(lái)越高,輕則影響系統(tǒng)性能,重則導(dǎo)致進(jìn)程崩潰。
在C語(yǔ)言中,因?yàn)槭鞘謩?dòng)管理內(nèi)存,內(nèi)存泄露是經(jīng)常出現(xiàn)的事情。
char * buffer; buffer = (char*) malloc(42); // Do something with buffer free(buffer);
上面是 C 語(yǔ)言代碼,malloc方法用來(lái)申請(qǐng)內(nèi)存,使用完畢之后,必須自己用free方法釋放內(nèi)存。
這很麻煩,所以大多數(shù)語(yǔ)言提供自動(dòng)內(nèi)存管理,減輕程序員的負(fù)擔(dān),這被稱(chēng)為"垃圾回收機(jī)制"
二、垃圾回收機(jī)制
Javascript 具有自動(dòng)垃圾回收機(jī)制(GC:Garbage Collecation),也就是說(shuō),執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過(guò)程中使用的內(nèi)存
原理:垃圾收集器會(huì)定期(周期性)找出那些不在繼續(xù)使用的變量,然后釋放其內(nèi)存
通常情況下有兩種實(shí)現(xiàn)方式:
- 標(biāo)記清除
- 引用計(jì)數(shù)
1、標(biāo)記清除
JavaScript最常用的垃圾收回機(jī)制
當(dāng)變量進(jìn)入執(zhí)行環(huán)境是,就標(biāo)記這個(gè)變量為“進(jìn)入環(huán)境“。進(jìn)入環(huán)境的變量所占用的內(nèi)存就不能釋放,當(dāng)變量離開(kāi)環(huán)境時(shí),則將其標(biāo)記為“離開(kāi)環(huán)境“
垃圾回收程序運(yùn)行的時(shí)候,會(huì)標(biāo)記內(nèi)存中存儲(chǔ)的所有變量。然后,它會(huì)將所有在上下文中的變量,以及被在上下文中的變量引用的變量的標(biāo)記去掉
在此之后再被加上標(biāo)記的變量就是待刪除的了,原因是任何在上下文中的變量都訪(fǎng)問(wèn)不到它們了
隨后垃圾回收程序做一次內(nèi)存清理,銷(xiāo)毀帶標(biāo)記的所有值并收回它們的內(nèi)存
代碼如下(示例):
var m = 0,n = 19 // 把 m,n,add() 標(biāo)記為進(jìn)入環(huán)境。 add(m, n) // 把 a, b, c標(biāo)記為進(jìn)入環(huán)境。 console.log(n) // a,b,c標(biāo)記為離開(kāi)環(huán)境,等待垃圾回收。 function add(a, b) { a++ var c = a + b return c }
2、引用計(jì)數(shù)
語(yǔ)言引擎有一張"引用表",保存了內(nèi)存里面所有的資源(通常是各種值)的引用次數(shù)。如果一個(gè)值的引用次數(shù)是0,就表示這個(gè)值不再用到了,因此可以將這塊內(nèi)存釋放
如果一個(gè)值不再需要了,引用數(shù)卻不為0,垃圾回收機(jī)制無(wú)法釋放這塊內(nèi)存,從而導(dǎo)致內(nèi)存泄漏
代碼如下(示例):
const arr = [1, 2, 3, 4]; console.log('hello world'); // arr = null
上面代碼中,數(shù)組[1, 2, 3, 4]是一個(gè)值,會(huì)占用內(nèi)存。變量arr是僅有的對(duì)這個(gè)值的引用,因此引用次數(shù)為1。盡管后面的代碼沒(méi)有用到arr,它還是會(huì)持續(xù)占用內(nèi)存。
如果需要這塊內(nèi)存被垃圾回收機(jī)制釋放,只需要再次將arr設(shè)置為null,就解除了對(duì)數(shù)組[1,2,3,4]的引用,引用次數(shù)變?yōu)?0,就被垃圾回收了。
小結(jié)
有了垃圾回收機(jī)制,不代表不用關(guān)注內(nèi)存泄露。那些很占空間的值,一旦不再用到,需要檢查是否還存在對(duì)它們的引用。如果是的話(huà),就必須手動(dòng)解除引用。
三、常見(jiàn)的內(nèi)存泄漏的情況
1、意外的全局變量
// 不設(shè)置定義變量的方法默認(rèn)為var function foo(arg) { ? ? bar = "this is a hidden global variable"; } // 另一種意外的全局變量可能由 this 創(chuàng)建: function foo() { ? ? this.variable = "potential accidental global"; } // foo 調(diào)用自己,this 指向了全局對(duì)象(window) foo();
上述使用嚴(yán)格模式,可以避免意外的全局變量。
2、定時(shí)器造成的內(nèi)存泄露
var someResource = getData(); setInterval(function() { var node = document.getElementById('Node'); if(node) { // 處理 node 和 someResource node.innerHTML = JSON.stringify(someResource)); } }, 1000);
如果id為Node的元素從DOM中移除,該定時(shí)器仍會(huì)存在,同時(shí),因?yàn)榛卣{(diào)函數(shù)中包含對(duì)someResource的引用,定時(shí)器外面的someResource也不會(huì)被釋放
3、閉包
function bindEvent() { var obj = document.createElement('XXX'); var unused = function () { console.log(obj, '閉包內(nèi)引用obj obj不會(huì)被釋放'); }; obj = null; // 解決方法 }
維持函數(shù)內(nèi)局部變量,使其得不到釋放
4、沒(méi)有清理對(duì)DOM元素的引用
const refA = document.getElementById('refA'); document.body.removeChild(refA); // dom刪除了 console.log(refA, 'refA'); // 但是還存在引用能console出整個(gè)div 沒(méi)有被回收 refA = null; console.log(refA, 'refA'); // 解除引用
5、事件監(jiān)聽(tīng)(addEventListener)
包括使用事件監(jiān)聽(tīng)addEventListener監(jiān)聽(tīng)的時(shí)候,在不監(jiān)聽(tīng)的情況下使用removeEventListener取消對(duì)事件監(jiān)聽(tīng)
總結(jié)
到此這篇關(guān)于淺談JavaScript中內(nèi)存泄漏的幾種情況的文章就介紹到這了,更多相關(guān)JavaScript 內(nèi)存泄漏內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
BootStrap selectpicker后臺(tái)動(dòng)態(tài)綁定數(shù)據(jù)的方法
這篇文章主要為大家詳細(xì)介紹了BootStrap selectpicker后臺(tái)動(dòng)態(tài)綁定數(shù)據(jù)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07詳談js中window.location.search的用法和作用
下面小編就為大家?guī)?lái)一篇詳談js中window.location.search的用法和作用。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02關(guān)于JavaScript回調(diào)函數(shù)的深入理解
由于函數(shù)是一等對(duì)象,我們可以把一個(gè)函數(shù)作為參數(shù)傳遞給另一個(gè)函數(shù),然后在那個(gè)函數(shù)內(nèi)執(zhí)行,至也可以被那個(gè)函數(shù)返回,然后再執(zhí)行,這篇文章主要給大家介紹了關(guān)于JavaScript回調(diào)函數(shù)的深入理解,需要的朋友可以參考下2021-06-06微信小程序滑動(dòng)選擇器的實(shí)現(xiàn)代碼
這篇文章主要介紹了微信小程序滑動(dòng)選擇器的實(shí)現(xiàn)代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08