|
|
|
|
|
|
在 JavaScript 的上下文中,不需要的引用是保存在代碼中某個位置的變量,這些變量將不再使用,并且指向一塊本來可以被釋放的內存。這就產生了所謂的JavaScript內存泄露。
在本文中,我們將深入了解一下我們的應用程序中可能發生哪些常見的內存泄漏。主要從四種常見的 JavaScript 泄漏進行分析。
要了解哪些是 JavaScript 中最常見的泄漏,我們需要知道引用通常以哪些方式被遺忘。
JavaScript 允許的方式之一是它處理未聲明變量的方式:對未聲明變量的引用會在全局對象內創建一個新變量。
在瀏覽器的情況下,全局對象是一個“window”。例如:
function foo(arg){
bar = "這是一個隱藏的全局變量";
}但是,其實是
function foo(arg){
window.bar = "這是一個顯式全局變量";
}要始終避免使用全局變量或防止發生這些錯誤,請在 JavaScript 文件的開頭添加'use strict';。這啟用了一種更嚴格的 JavaScript 解析模式,可防止意外的全局變量。
在回調中使用 setTimeout或 setInterval引用某個對象是防止對象被垃圾回收的最常用方法。如果我們在代碼中設置循環計時器,只要回調是可調用的,來自計時器回調的對象的引用就會保持活動狀態。
在下面的示例中,data只有在清除計時器后才能對對象進行垃圾收集。由于我們沒有引用setInterval,因此它永遠不會被清除并data.hugeString保存在內存中,直到應用程序停止,盡管從未使用過。
function setCallback() {
const data = {
counter: 0,
hugeString: new Array(100000).join('x')
};
return function cb() {
data.counter++; // data對象現在是回調范圍的一部分
console.log(data.counter);
}
}
setInterval(setCallback(), 1000); // 我們怎樣停止它?如何停止它?特別是如果回調的生命周期未定義或不確定:
function setCallback() {
// “解包”數據對象
let counter = 0;
const hugeString = new Array(100000).join('x'); // 當 setCallback 返回時被移除
return function cb() {
counter++; // 只有計數器是回調范圍的一部分
console.log(counter);
}
}
const timerId = setInterval(setCallback(), 1000); // 保存 interval ID
// doing something ...
clearInterval(timerId); // 停止計時器,比如如果按下按鈕活動事件偵聽器將防止在其范圍內捕獲的所有變量被垃圾收集。添加后,事件偵聽器將一直有效,直到:
DOM 元素屬于 DOM,但它也存在于 Object Graph Memory 中。因此,如果刪除前者,則應刪除后者。
var trigger = document.getElementById("trigger");
var elem = document.getElementById("elementToDelete");
trigger.addEventListener("click", function(){
elem.remove();
});在此示例中,單擊trigger后,elementToDelete將從 DOM 中刪除。但是由于它仍然在偵聽器中被引用,所以仍然使用為對象分配的內存。
一旦不再需要,我們應該始終取消注冊事件偵聽器,通過創建指向它的引用并將其傳遞給removeEventListener() 或 addEventListener()可以接受第三個參數,這是一個提供附加選項的對象。鑒于它{once: true}作為第三個參數傳遞給addEventListener(),偵聽器函數將在處理一次事件后自動刪除。
trigger.addEventListener("click", function(){
elem.remove();
},{once: true})); // 監聽器運行一次后將被移除函數范圍的變量將在函數退出調用堆棧后清理,如果函數外部沒有任何指向它們的引用。盡管函數已經完成執行并且其執行上下文和變量環境早已不復存在,但閉包將保持變量被引用并保持活動狀態。
function outer() {
const potentiallyHugeArray = [];
return function inner() {
potentiallyHugeArray.push('Hello');
console.log('Hello');
};
};
const sayHello = outer(); // 包含函數內部的定義
function repeat(fn, num) {
for (let i = 0; i < num; i++){
fn();
}
}
repeat(sayHello, 10); // 每個 sayHello 調用都會將另一個 'Hello' 推送到potentiallyHugeArray
// 現在想象 repeat(sayHello, 100000)在這個例子中,potentiallyHugeArray永遠不會從任何函數返回,也無法到達,但它的大小可以無限增長,具體取決于我們調用function inner()。
閉包是 JavaScript 中不可避免的一個組成部分,所以重要的是:
內存管理過程的組成部分是了解典型的內存泄漏源,以防止它們發生。內存泄漏可能并且確實發生在垃圾收集語言(如 JavaScript)中。這些可能會在一段時間內被忽視,最終它們會造成嚴重破壞。因此,內存分析工具對于查找內存泄漏至關重要。分析運行應該是開發周期的一部分,尤其是對于中型或大型應用程序。開始這樣做是為了給你的用戶最好的體驗。
相關文章
