這篇文章主要介紹JavaScript內存泄漏的主要原因,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
在閱讀這篇博客之前,你或許需要具備一些JavaScript內存管理的知識:
內存泄露(Memory Leaks):是指應用程序已經不再需要的內存,由于某種原因未返回給操作系統或者空閑內存池(Pool of Free Memory)。
內存泄露可能帶來的問題:變慢、卡頓、高延遲。
JavaScript內存泄漏的主要原因在于一些不再需要的引用(Unwanted References)。
所謂的Unwanted References指的是:有一些內存,其實開發人員已經不再需要了,但是由于某種原因,這些內存仍然被標記并保留在活動根目錄樹中。Unwanted References就是指對這些內存的引用。在JavaScript上下文中,Unwanted References是一些不再使用的變量,這些變量指向了原本可以釋放的一些內存。
首先,我們得知道,JavaScript中的全局變量是由根節點(root node)引用的,因此它們在應用程序的整個生命周期中都不會被垃圾回收。
場景一:在JavaScript中,如果引用未聲明的變量,將會導致,在全局環境中創建新的變量。
function foo(arg) { bar = "this is a hidden global variable"; }
上面這串代碼,實際上如下:
function foo(arg) { window.bar = "this is an explicit global variable"; }
假如,我們希望bar這個變量僅在foo函數作用域內部使用,但上面這種情況就會意外地在全局作用域內創建bar,這將造成內存泄漏。
場景二:
function foo() { this.variable = "potential accidental global"; }foo();
同樣的,如果我們希望bar這個變量僅在foo函數作用域內部使用,但如果不知道foo函數內部的this指向全局對象,將造成內存泄露。
建議:
避免意外地創建全局變量。比如,我們可以使用嚴格模式,則本節的第一段代碼將報錯,而不會創建全局變量。
減少創建全局變量。
如果必須使用全局變量來存儲大量數據,請確保在處理完數據后將其置null或重新分配。
場景舉例:
for (var i = 0; i < 100000; i++) { var buggyObject = { callAgain: function () { var ref = this; var val = setTimeout(function () { ref.callAgain(); }, 10); } } buggyObject.callAgain(); buggyObject = null;}
多處引用(Multiple references):當多個對象均引用同一對象時,但凡其中一個引用沒有清除,都將導致被引用對象無法GC。
場景一:
var elements = { button: document.getElementById('button'), image: document.getElementById('image'), text: document.getElementById('text')};function doStuff() { image.src = 'http://some.url/image'; button.click(); console.log(text.innerHTML); // Much more logic}function removeButton() { // The button is a direct child of body. document.body.removeChild(document.getElementById('button')); // At this point, we still have a reference to #button in the global // elements dictionary. In other words, the button element is still in // memory and cannot be collected by the GC.s}
在上面這種情況中,我們對#button的保持兩個引用:一個在DOM樹中,另一個在elements對象中。 如果將來決定回收#button,則需要使兩個引用均不可訪問。在上面的代碼中,由于我們只清除了來自DOM樹的引用,所以#button仍然存在內存中,而不會被GC。
場景二: 如果我們想要回收某個table,但我們保持著對這個table中某個單元格(cell)的引用,這個時候將導致整個table都保存在內存中,無法GC。
閉包(Closure):閉包是一個函數,它可以訪問那些定義在它的包圍作用域(Enclosing Scope)里的變量,即使這個包圍作用域已經結束。因此,閉包具有記憶周圍環境(Context)的功能。
場景舉例:
var newElem;function outer() { var someText = new Array(1000000); var elem = newElem; function inner() { if (elem) return someText; } return function () {}; }setInterval(function () { newElem = outer();}, 5);
在這個例子中,有兩個閉包:一個是inner,另一個是匿名函數function () {}
。其中,inner閉包引用了someText和elem,并且,inner永遠也不會被調用??墒?,我們需要注意:相同父作用域的閉包,他們能夠共享context。 也就是說,在這個例子中,inner的someText和elem將和匿名函數function () {}
共享。然而,這個匿名函數之后會被return返回,并且賦值給newElem。只要newElem還引用著這個匿名函數,那么,someText和elem就不會被GC。
同時,我們還要注意到,outer函數內部執行了var elem = newElem;
,而這個newElem引用了上一次調用的outer返回的匿名函數。試想,第n次調用outer將保持著第n-1次調用的outer中的匿名函數,而這個匿名函數由保持著對elem的引用,進而保持著對n-2次的...因此,這將造成內存泄漏。
解決方案:setInterval中的參數1的代碼改為newElem = outer()();
這一節內容的具體剖析,可以見資料1和資料2。
Chrome(最新的86版本)開發者工具中有兩個關于內存的分析工具:
Performance
Memory
以上是JavaScript內存泄漏的主要原因的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。