|
|
|
|
|
在 JavaScript 的上下文中,不需要的引用是保存在代碼中某個位置的變量,這些變量將不再使用,并且指向一塊本來可以被釋放的內(nèi)存。這就產(chǎn)生了所謂的JavaScript內(nèi)存泄露。
在本文中,我們將深入了解一下我們的應(yīng)用程序中可能發(fā)生哪些常見的內(nèi)存泄漏。主要從四種常見的 JavaScript 泄漏進(jìn)行分析。
要了解哪些是 JavaScript 中最常見的泄漏,我們需要知道引用通常以哪些方式被遺忘。
JavaScript 允許的方式之一是它處理未聲明變量的方式:對未聲明變量的引用會在全局對象內(nèi)創(chuàng)建一個新變量。
在瀏覽器的情況下,全局對象是一個“window”。例如:
function foo(arg){
bar = "這是一個隱藏的全局變量";
}
但是,其實是
function foo(arg){
window.bar = "這是一個顯式全局變量";
}
要始終避免使用全局變量或防止發(fā)生這些錯誤,請在 JavaScript 文件的開頭添加'use strict';
。這啟用了一種更嚴(yán)格的 JavaScript 解析模式,可防止意外的全局變量。
在回調(diào)中使用 setTimeout
或 setInterval
引用某個對象是防止對象被垃圾回收的最常用方法。如果我們在代碼中設(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)的生命周期未定義或不確定:
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); // 停止計時器,比如如果按下按鈕
活動事件偵聽器將防止在其范圍內(nèi)捕獲的所有變量被垃圾收集。添加后,事件偵聽器將一直有效,直到:
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)聽器運行一次后將被移除
函數(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 中不可避免的一個組成部分,所以重要的是:
內(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)文章