版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第一文搞懂如何避免JavaScript內(nèi)存泄漏目錄一、什么是內(nèi)存泄漏二、常見的內(nèi)存泄漏1、意外的全局變量2、計(jì)時(shí)器3、閉包4、事件監(jiān)聽器5、緩存6、分離的DOM元素三、識(shí)別內(nèi)存泄漏1、使用性能分析器可視化內(nèi)存消耗2、識(shí)別分離的DOM節(jié)點(diǎn)大家好,我是CUGGZ。SPA(單頁應(yīng)用程序)的興起,促使我們更加關(guān)注與內(nèi)存相關(guān)的JavaScript編碼實(shí)踐。如果應(yīng)用使用的內(nèi)存越來越多,就會(huì)嚴(yán)重影響性能,甚至導(dǎo)致瀏覽器的崩潰。下面就來看看JavaScript中常見的內(nèi)存泄漏以及如何避免內(nèi)存泄漏。
一、什么是內(nèi)存泄漏
JavaScript就是所謂的垃圾回收語言之一,垃圾回收語言通過定期檢查哪些先前分配的內(nèi)存仍然可以從應(yīng)用程序的其他部分訪問來幫助開發(fā)人員管理內(nèi)存。垃圾回收語言中泄漏的主要原因是不需要的引用。如果你的JavaScript應(yīng)用程序經(jīng)常發(fā)生崩潰、高延遲和性能差,那么一個(gè)潛在的原因可能是內(nèi)存泄漏。
在JavaScript中,內(nèi)存是有生命周期的:
分配內(nèi)存:內(nèi)存由操作系統(tǒng)分配,允許程序使用它。在JavaScript中,分配內(nèi)存是自動(dòng)完成的。使用內(nèi)存:這是程序?qū)嶋H使用先前分配的內(nèi)存的空間。當(dāng)在代碼中使用分配的變量時(shí),會(huì)發(fā)生讀取和寫入操作。釋放內(nèi)存:釋放不需要的內(nèi)存,這樣內(nèi)存就會(huì)空閑并可以再次利用。在JavaScript中,釋放內(nèi)存是自動(dòng)完成的。
在JavaScript中,對(duì)象會(huì)保存在堆內(nèi)存中,可以根據(jù)引用鏈從根訪問它們。垃圾收集器是JavaScript引擎中的一個(gè)后臺(tái)進(jìn)程,用于識(shí)別無法訪問的對(duì)象、刪除它們并回收內(nèi)存。
下面是垃圾收集器根到對(duì)象的引用鏈?zhǔn)纠?/p>
當(dāng)內(nèi)存中應(yīng)該在垃圾回收周期中清理的對(duì)象,通過另一個(gè)對(duì)象的無意引用從根保持可訪問時(shí),就會(huì)發(fā)生內(nèi)存泄漏。將冗余對(duì)象保留在內(nèi)存中會(huì)導(dǎo)致應(yīng)用程序內(nèi)部使用過多的內(nèi)存,并可能導(dǎo)致性能下降。
那該如何判斷代碼正在泄漏內(nèi)存呢通常,內(nèi)存泄漏是很難被發(fā)現(xiàn)的,并且瀏覽器在運(yùn)行它時(shí)不會(huì)拋出任何錯(cuò)誤。如果注意到頁面的性能越來越差,瀏覽器的內(nèi)置工具可以幫助我們確定是否存在內(nèi)存泄漏以及導(dǎo)致內(nèi)存泄漏的對(duì)象。
內(nèi)存使用檢查最快的方法就是查看瀏覽器的任務(wù)管理器。它們提供了當(dāng)前在瀏覽器中運(yùn)行的所有選項(xiàng)卡和進(jìn)程的概覽。在任務(wù)管理器中查看每個(gè)選項(xiàng)卡的JavaScript內(nèi)存占用情況。如果網(wǎng)站什么都不做,但是JavaScript內(nèi)存使用量卻在逐漸增加,那么很有可能發(fā)生了內(nèi)存泄漏。
二、常見的內(nèi)存泄漏
我們可以通過了解在JavaScript中如何創(chuàng)建不需要的引用來防止內(nèi)存泄漏。以下情況就會(huì)導(dǎo)致不需要的引用。
1、意外的全局變量
全局變量始終可以從全局對(duì)象(在瀏覽器中,全局對(duì)象是window)中獲得,并且永遠(yuǎn)不會(huì)被垃圾回收。在非嚴(yán)格模式下,以下行為會(huì)導(dǎo)致變量從局部范圍泄露到全局范圍:
(1)為未聲明的變量賦值
這里我們給函數(shù)中一個(gè)未聲明的變量bar賦值,這時(shí)就會(huì)使bar成為一個(gè)全局變量:
functionfoo(arg){
bar="helloworld";
}
這就等價(jià)于:
functionfoo(arg){
window.bar="helloworld";
}
這樣就會(huì)創(chuàng)建一個(gè)多余的全局變量,當(dāng)執(zhí)行完foo函數(shù)之后,變量bar仍然會(huì)存在于全局對(duì)象中:
foo()
window.bar//helloworld
(2)使用指向全局對(duì)象的this
使用以下方式也會(huì)創(chuàng)建一個(gè)以外的全局變量:
functionfoo(){
this.bar="helloworld";
foo();
這里foo是在全局對(duì)象中調(diào)用的,所以其this是指向全局對(duì)象的(這里是window):
window.bar//helloworld
我們可以通過使用嚴(yán)格模式usestrict來避免這一切。在JavaScript文件的開頭,它將開啟更嚴(yán)格的JavaScript解析模式,從而防止意外的創(chuàng)建全局變量。
需要特別注意那些用于臨時(shí)存儲(chǔ)和處理大量信息的全局變量。如果必須使用全局變量存儲(chǔ)數(shù)據(jù),就使用全局變量存儲(chǔ)數(shù)據(jù),但在不再使用時(shí),就手動(dòng)將其設(shè)置為null,或者在處理完后重新分配。否則的話,請(qǐng)盡可能的使用局部變量。
2、計(jì)時(shí)器
使用setTimeout或setInterval引用回調(diào)中的某個(gè)對(duì)象是防止對(duì)象被垃圾收集的最常見方法。如果我們?cè)诖a中設(shè)置了循環(huán)計(jì)時(shí)器,只要回調(diào)是可調(diào)用的,計(jì)時(shí)器回調(diào)中對(duì)對(duì)象的引用就會(huì)保持活動(dòng)狀態(tài)。
在下面的示例中,只有在清除計(jì)時(shí)器后,才能對(duì)數(shù)據(jù)對(duì)象進(jìn)行垃圾收集。由于我們沒有對(duì)setInterval的引用,所以它永遠(yuǎn)無法被清除和刪除數(shù)據(jù)。hugeString會(huì)一直保存在內(nèi)存中,直到應(yīng)用程序停止,盡管從未使用過。
functionsetCallback(){
constdata={
counter:0,
hugeString:newArray(100000).join('x')
returnfunctioncb(){
data.counter++;//data對(duì)象是回調(diào)范圍的一部分
console.log(data.counter);
setInterval(setCallback(),1000);
當(dāng)執(zhí)行這段代碼時(shí),就會(huì)每秒輸出一個(gè)數(shù)字:
那我們?nèi)绾稳プ柚顾赜绕涫窃诨卣{(diào)的壽命未定義或不確定的情況下:
修改計(jì)時(shí)器回調(diào)中引用的對(duì)象;必要時(shí)使用從計(jì)時(shí)器返回的句柄(定時(shí)器的標(biāo)識(shí)符)取消它。
functionsetCallback(){
//將數(shù)據(jù)對(duì)象解包
letcounter=0;
consthugeString=newArray(100000).join('x');//在setCallback返回時(shí)被刪除
returnfunctioncb(){
counter++;//只有計(jì)數(shù)器counter是回調(diào)范圍的一部分
console.log(counter);
consttimerId=setInterval(setCallback(),1000);//保存定時(shí)器的ID
//合適的時(shí)機(jī)清除定時(shí)器
clearInterval(timerId);
3、閉包
我們知道,函數(shù)范圍內(nèi)的變量在函數(shù)退出調(diào)用堆棧后,如果函數(shù)外部沒有任何指向它們的引用,則會(huì)被清除。盡管函數(shù)已經(jīng)完成執(zhí)行,其執(zhí)行上下文和變量環(huán)境早已消失,但閉包將保持變量的引用和活動(dòng)狀態(tài)。
functionouter(){
constpotentiallyHugeArray=[];
returnfunctioninner(){
potentiallyHugeArray.push('Hello');
console.log('Hello');
constsayHello=outer();
functionrepeat(fn,num){
for(leti=0;inum;i++){
fn();
repeat(sayHello,10);
顯而易見,這里就形成了一個(gè)閉包。其輸出結(jié)果如下:
這里,potentiallyHugeArray永遠(yuǎn)不會(huì)從任何函數(shù)返回,也無法訪問,但它的大小可能會(huì)無限增長(zhǎng),這取決于調(diào)用函數(shù)inner()的次數(shù)。
那該如何防止這個(gè)問題呢閉包是不可避免的,也是JavaScript不可或缺的一部分,因此重要的是:
了解何時(shí)創(chuàng)建閉包以及閉包保留了哪些對(duì)象。了解閉包的預(yù)期壽命和用法(尤其是用作回調(diào)時(shí))。
4、事件監(jiān)聽器
活動(dòng)事件偵聽器將防止在其范圍內(nèi)捕獲的所有變量被垃圾收集。添加后,事件偵聽器將一直有效,直到:
使用removeEventListener()顯式刪除。關(guān)聯(lián)的DOM元素被移除。
對(duì)于某些類型的事件,它會(huì)一直保留到用戶離開頁面,就像應(yīng)該多次單擊的按鈕一樣。但是,有時(shí)我們希望事件偵聽器執(zhí)行一定次數(shù)。
consthugeString=newArray(100000).join('x');
document.addEventListener('keyup',function(){//匿名內(nèi)聯(lián)函數(shù),無法刪除它
doSomething(hugeString);//hugeString將永遠(yuǎn)保留在回調(diào)的范圍內(nèi)
});
在上面的示例中,匿名內(nèi)聯(lián)函數(shù)用作事件偵聽器,這意味著不能使用removeEventListener()刪除它。同樣,document不能被刪除,因此只能使用listener函數(shù)以及它在其范圍內(nèi)保留的內(nèi)容,即使只需要啟動(dòng)一次。
那該如何防止這個(gè)問題呢一旦不再需要,我們應(yīng)該通過創(chuàng)建指向事件偵聽器的引用并將其傳遞給removeEventListener()來注銷事件偵聽器。
functionlistener(){
doSomething(hugeString);
document.addEventListener('keyup',listener);
document.removeEventListener('keyup',listener);
如果事件偵聽器只能執(zhí)行一次,addEventListener()可以接受第三個(gè)參數(shù),這是一個(gè)提供附加選項(xiàng)的對(duì)象。假定將{once:true}作為第三個(gè)參數(shù)傳遞給addEventListener(),則偵聽器函數(shù)將在處理一次事件后自動(dòng)刪除。
document.addEventListener('keyup',functionlistener(){
doSomething(hugeString);
},{once:true});
5、緩存
如果我們不斷地將內(nèi)存添加到緩存中,而不刪除未使用的對(duì)象,并且沒有一些限制大小的邏輯,那么緩存可以無限增長(zhǎng)。
letuser_1={name:"Peter",id:12345};
letuser_2={name:"Mark",id:54321};
constmapCache=newMap();
functioncache(obj){
if(!mapCache.has(obj)){
constvalue=`${}hasanidof${obj.id}`;
mapCache.set(obj,value);
return[value,'computed'];
return[mapCache.get(obj),'cached'];
cache(user_1);//['Peterhasanidof12345','computed']
cache(user_1);//['Peterhasanidof12345','cached']
cache(user_2);//['Markhasanidof54321','computed']
console.log(mapCache);//{{…}='Peterhasanidof12345',{…}='Markhasanidof54321'}
user_1=null;
console.log(mapCache);//{{…}='Peterhasanidof12345',{…}='Markhasanidof54321'}
在上面的示例中,緩存仍然保留user_1對(duì)象。因此,我們需要將那些永遠(yuǎn)不會(huì)被重用的變量從緩存中清除。
可以使用WeakMap來解決此問題。它是一種具有弱鍵引用的數(shù)據(jù)結(jié)構(gòu),僅接受對(duì)象作為鍵。如果我們使用一個(gè)對(duì)象作為鍵,并且它是對(duì)該對(duì)象的唯一引用相關(guān)變量將從緩存中刪除并被垃圾收集。在以下示例中,將user_1對(duì)象清空后,相關(guān)變量會(huì)在下一次垃圾回收后自動(dòng)從WeakMap中刪除。
letuser_1={name:"Peter",id:12345};
letuser_2={name:"Mark",id:54321};
constweakMapCache=newWeakMap();
functioncache(obj){
//...
return[weakMapCache.get(obj),'cached'];
cache(user_1);//['Peterhasanidof12345','computed']
cache(user_2);//['Markhasanidof54321','computed']
console.log(weakMapCache);//{(…)="Peterhasanidof12345",(…)="Markhasanidof54321"}
user_1=null;
console.log(weakMapCache);//{(…)="Markhasanidof54321"}
6、分離的DOM元素
如果DOM節(jié)點(diǎn)具有來自JavaScript的直接引用,它將防止對(duì)其進(jìn)行垃圾收集,即使在從DOM樹中刪除該節(jié)點(diǎn)之后也是如此。
在下面的示例中,創(chuàng)建了一個(gè)div元素并將其附加到document.body中。removeChild()就無法按預(yù)期工作,堆快照將顯示分離的HTMLDivElement,因?yàn)槿杂幸粋€(gè)變量指向div。
functioncreateElement(){
constdiv=document.createElement('div');
div.id='detached';
returndiv;
//即使在調(diào)用deleteElement()之后,它仍將繼續(xù)引用DOM元素
constdetachedDiv=createElement();
document.body.appendChild(detachedDiv);
functiondeleteElement(){
document.body.removeChild(document.getElementById('detached'));
deleteElement();
要解決此問題,可以將DOM引用移動(dòng)到本地范圍。在下面的示例中,在函數(shù)appendElement()完成后,將刪除指向DOM元素的變量。
functioncreateElement(){...}
//DOM引用在函數(shù)范圍內(nèi)
functionappendElement(){
constdetachedDiv=createElement();
document.body.appendChild(detachedDiv);
appendElement();
functiondeleteElement(){
document.body.removeChild(document.getElementById('detached'));
deleteElement();
三、識(shí)別內(nèi)存泄漏
調(diào)試內(nèi)存問題是一項(xiàng)復(fù)雜的工作,我們可以使用ChromeDevTools來識(shí)別內(nèi)存圖和一些內(nèi)存泄漏,我們需要關(guān)注以下兩個(gè)方面:
使用性能分析器可視化內(nèi)存消耗。識(shí)別分離的DOM節(jié)點(diǎn)。
1、使用性能分析器可視化內(nèi)存消耗
以下面的代碼為例,有兩個(gè)按鈕:打印和清除。點(diǎn)擊打印按鈕,通過創(chuàng)建paragraph節(jié)點(diǎn)并將大字符串設(shè)置到全局,將1到10000的數(shù)字追加到DOM中。
清除按鈕會(huì)清除全局變量并覆蓋body的正文,但不會(huì)刪除單擊打印時(shí)創(chuàng)建的節(jié)點(diǎn):
!DOCTYPEhtml
htmllang="en"
head
titleMemoryleaks/title
/head
body
buttonid="print"打印/button
buttonid="clear"清除/button
/body
/html
script
varlongArray=[];
functionprint(){
for(vari=0;i10000;i++){
letparagraph=document.createElement("p");
paragraph.innerHTML=i;
document.body.appendChild(paragraph);
longArray.push(newArray(1000000).join("y"));
document.getElementById("print").addEventListener("click",print);
document.getElementById("clear").addEventListener("click",()={
window.longArray=null;
document.body.innerHTML="Cleared";
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 景區(qū)運(yùn)營(yíng)管理師安全培訓(xùn)效果評(píng)優(yōu)考核試卷含答案
- 企業(yè)員工職業(yè)道德培訓(xùn)講義及心得體會(huì)
- 幼兒園端午節(jié)主題活動(dòng)教案設(shè)計(jì)
- 客戶服務(wù)培訓(xùn)課程效果評(píng)估試題
- 血液采供崗位培訓(xùn)考試題庫
- 小學(xué)科學(xué)實(shí)驗(yàn)教案與教學(xué)設(shè)計(jì)
- 企業(yè)內(nèi)訓(xùn)體系搭建及培訓(xùn)內(nèi)容工具
- 企業(yè)培訓(xùn)計(jì)劃設(shè)計(jì)參考指南
- 中班社會(huì)活動(dòng)《垃圾分類》教案
- 公共衛(wèi)生制度管理制度
- 安全生產(chǎn)目標(biāo)及考核制度
- (2026版)患者十大安全目標(biāo)(2篇)
- 2026年北大拉丁語標(biāo)準(zhǔn)考試試題
- 臨床護(hù)理操作流程禮儀規(guī)范
- 2025年酒店總經(jīng)理年度工作總結(jié)暨戰(zhàn)略規(guī)劃
- 空氣栓塞課件教學(xué)
- 2025年國家市場(chǎng)監(jiān)管總局公開遴選公務(wù)員面試題及答案
- 肌骨康復(fù)腰椎課件
- 患者身份識(shí)別管理標(biāo)準(zhǔn)
- 2025年10月自考04184線性代數(shù)經(jīng)管類試題及答案含評(píng)分參考
- 2025年勞動(dòng)保障協(xié)理員三級(jí)技能試題及答案
評(píng)論
0/150
提交評(píng)論