技術(shù)頻道導(dǎo)航
HTML/CSS
.NET技術(shù)
IIS技術(shù)
PHP技術(shù)
Js/JQuery
Photoshop
Fireworks
服務(wù)器技術(shù)
操作系統(tǒng)
網(wǎng)站運營

贊助商

分類目錄

贊助商

最新文章

搜索

4種常見的JavaScript內(nèi)存泄漏以及如何避免它們

作者:admin    時間:2022-6-21 23:55:8    瀏覽:

在 JavaScript 的上下文中,不需要的引用是保存在代碼中某個位置的變量,這些變量將不再使用,并且指向一塊本來可以被釋放的內(nèi)存。這就產(chǎn)生了所謂的JavaScript內(nèi)存泄露。

在本文中,我們將深入了解一下我們的應(yīng)用程序中可能發(fā)生哪些常見的內(nèi)存泄漏。主要從四種常見的 JavaScript 泄漏進(jìn)行分析。

要了解哪些是 JavaScript 中最常見的泄漏,我們需要知道引用通常以哪些方式被遺忘。

1、未聲明/全局變量

JavaScript 允許的方式之一是它處理未聲明變量的方式:對未聲明變量的引用會在全局對象內(nèi)創(chuàng)建一個新變量。

在瀏覽器的情況下,全局對象是一個“window”。例如:

function foo(arg){ 
   bar = "這是一個隱藏的全局變量"; 
}

但是,其實是

function foo(arg){ 
   window.bar = "這是一個顯式全局變量"; 
}

要始終避免使用全局變量或防止發(fā)生這些錯誤,請在 JavaScript 文件的開頭添加'use strict';。這啟用了一種更嚴(yán)格的 JavaScript 解析模式,可防止意外的全局變量。

2、忘記定時器或回調(diào)

在回調(diào)中使用 setTimeoutsetInterval引用某個對象是防止對象被垃圾回收的最常用方法。如果我們在代碼中設(shè)置循環(huán)計時器,只要回調(diào)是可調(diào)用的,來自計時器回調(diào)的對象的引用就會保持活動狀態(tài)。

在下面的示例中,data只有在清除計時器后才能對對象進(jìn)行垃圾收集。由于我們沒有引用setInterval,因此它永遠(yuǎn)不會被清除并data.hugeString保存在內(nèi)存中,直到應(yīng)用程序停止,盡管從未使用過。

function setCallback() {    
 const data = {   
    counter: 0,    
    hugeString: new Array(100000).join('x')   
  };  
 return function cb() {  
  data.counter++; // data對象現(xiàn)在是回調(diào)范圍的一部分
 console.log(data.counter);   
   }

setInterval(setCallback(), 1000); // 我們怎樣停止它?

如何停止它

如何停止它?特別是如果回調(diào)的生命周期未定義或不確定:

  • 意識到從計時器的回調(diào)中引用的對象,
  • 必要時使用從計時器返回的句柄取消它。
  • 從計時器返回以在必要時取消它。
function setCallback() {
    // “解包”數(shù)據(jù)對象
    let counter = 0;
    const hugeString = new Array(100000).join('x'); // 當(dāng) setCallback 返回時被移除
    return function cb() {
        counter++; // 只有計數(shù)器是回調(diào)范圍的一部分
        console.log(counter);
    }
}
const timerId = setInterval(setCallback(), 1000); // 保存 interval ID
// doing something ...
clearInterval(timerId); // 停止計時器,比如如果按下按鈕

3、沒有 DOM 引用 (活動事件偵聽器)

活動事件偵聽器將防止在其范圍內(nèi)捕獲的所有變量被垃圾收集。添加后,事件偵聽器將一直有效,直到:

  • 明確刪除removeEventListener()
  • 關(guān)聯(lián)的 DOM 元素被移除。

DOM 元素屬于 DOM,但它也存在于 Object Graph Memory 中。因此,如果刪除前者,則應(yīng)刪除后者。

var trigger = document.getElementById("trigger"); 
var elem = document.getElementById("elementToDelete");
trigger.addEventListener("click", function(){ 
   elem.remove(); 
});

在此示例中,單擊trigger后,elementToDelete將從 DOM 中刪除。但是由于它仍然在偵聽器中被引用,所以仍然使用為對象分配的內(nèi)存。

如何防止它

一旦不再需要,我們應(yīng)該始終取消注冊事件偵聽器,通過創(chuàng)建指向它的引用并將其傳遞給removeEventListener() 或 addEventListener()可以接受第三個參數(shù),這是一個提供附加選項的對象。鑒于它{once: true}作為第三個參數(shù)傳遞給addEventListener(),偵聽器函數(shù)將在處理一次事件后自動刪除。

trigger.addEventListener("click", function(){ 
   elem.remove(); 
},{once: true})); // 監(jiān)聽器運行一次后將被移除

4、閉包

函數(shù)范圍的變量將在函數(shù)退出調(diào)用堆棧后清理,如果函數(shù)外部沒有任何指向它們的引用。盡管函數(shù)已經(jīng)完成執(zhí)行并且其執(zhí)行上下文和變量環(huán)境早已不復(fù)存在,但閉包將保持變量被引用并保持活動狀態(tài)。

function outer() { 
    const potentiallyHugeArray = []; 
    return function inner() {  
       potentiallyHugeArray.push('Hello'); 
       console.log('Hello');    
  }; 
};
 const sayHello = outer(); // 包含函數(shù)內(nèi)部的定義 
 function repeat(fn, num) { 
    for (let i = 0; i < num; i++){ 
        fn();     
   }
 } 
repeat(sayHello, 10); // 每個 sayHello 調(diào)用都會將另一個 'Hello' 推送到potentiallyHugeArray
 // 現(xiàn)在想象 repeat(sayHello, 100000)

在這個例子中,potentiallyHugeArray永遠(yuǎn)不會從任何函數(shù)返回,也無法到達(dá),但它的大小可以無限增長,具體取決于我們調(diào)用function inner()。

如何防止它

閉包是 JavaScript 中不可避免的一個組成部分,所以重要的是:

  • 了解閉包何時創(chuàng)建以及它保留了哪些對象,
  • 了解閉包的預(yù)期壽命和使用情況(尤其是用作回調(diào)時)。

結(jié)論

內(nèi)存管理過程的組成部分是了解典型的內(nèi)存泄漏源,以防止它們發(fā)生。內(nèi)存泄漏可能并且確實發(fā)生在垃圾收集語言(如 JavaScript)中。這些可能會在一段時間內(nèi)被忽視,最終它們會造成嚴(yán)重破壞。因此,內(nèi)存分析工具對于查找內(nèi)存泄漏至關(guān)重要。分析運行應(yīng)該是開發(fā)周期的一部分,尤其是對于中型或大型應(yīng)用程序。開始這樣做是為了給你的用戶最好的體驗。

相關(guān)文章

標(biāo)簽: 內(nèi)存泄漏  
x
  • 站長推薦
/* 左側(cè)顯示文章內(nèi)容目錄 */