ie圖片壓縮後如何釋放記憶體_JavaScript系列之記憶體洩漏
技術標籤:ie圖片壓縮後如何釋放記憶體
在程式執行過程中不再用到的記憶體,沒有及時釋放,會出現記憶體洩漏(memory leak),會造成系統記憶體的浪費,導致程式執行速度減慢甚至系統崩潰等嚴重後果。
而記憶體洩漏是每個開發人員最終必須面對的問題。 即使使用記憶體管理語言,比如C語言有著malloc() 和 free() 這種低階記憶體管理語言也有可能出現洩露記憶體的情況。
這很麻煩,所以為了減輕程式設計中的負擔,大多數語言提供了自動記憶體管理,這被稱為"垃圾回收機制"(garbage collector)。
垃圾回收機制
現在各大瀏覽器通常採用的垃圾回收有兩種方法:標記清除(mark and sweep)
1、標記清除
這是javascript中最常用的垃圾回收方式。
工作原理:當變數進入執行環境時,將這個變數標記為“進入環境”。當變數離開環境時,則將其標記為“離開環境”。標記“離開環境”的就回收記憶體。
工作流程:
- 垃圾回收器,在執行的時候會給儲存在記憶體中的所有變數都加上標記。
- 去掉環境中的變數以及被環境中的變數引用的變數的標記。
- 之後再被加上標記的變數將被視為準備刪除的變數。
- 垃圾回收器完成記憶體清除工作,銷燬那些帶標記的值並回收他們所佔用的記憶體空間。
2、引用計數
工作原理:跟蹤記錄每個值被引用的次數。
工作流程:
- 將一個引用型別的值賦值給這個聲明瞭的變數,這個引用型別值的引用次數就是1。
- 同一個值又被賦值給另一個變數,這個引用型別值的引用次數加1。
- 當包含這個引用型別值的變數又被賦值成另一個值了,那麼這個引用型別值的引用次數減1
- 當引用次數變成0時,就表示這個值不再用到了。
- 當垃圾收集器下一次執行時,它就會釋放引用次數是0的值所佔的記憶體。
但如果一個值不再需要了,引用數卻不為0
,垃圾回收機制無法釋放這塊記憶體,會導致記憶體洩漏。
var arr = [1, 2, 3];
console.log('hello miqilin');
上面程式碼中,陣列[1, 2, 3]
會佔用記憶體,賦值給了變數arr
,因此引用次數為1
。儘管後面的一段程式碼沒有用到arr
,它還是會持續佔用記憶體。
如果增加一行程式碼,解除arr對[1, 2, 3]引用,這塊記憶體就可以被垃圾回收機制釋放了。
var arr = [1, 2, 3];
console.log('hello miqilin');
arr = null;
上面程式碼中,arr
重置為null
,就解除了對[1, 2, 3]
的引用,引用次數變成了0
,記憶體就可以釋放出來了。
因此,並不是說有了垃圾回收機制,程式設計師就無事一身輕了。你還是需要關注記憶體佔用:那些很佔空間的值,一旦不再用到,你必須檢查是否還存在對它們的引用。如果是的話,就必須手動解除引用。
接下來,我將介紹四種常見的JavaScript 記憶體洩漏及如何避免。目前水平有限,借鑑了國外大牛的文章瞭解這幾種記憶體洩漏,原文連結:https://blog.sessionstack.com/how-javascript-works-memory-management-how-to-handle-4-common-memory-leaks-3f28b94cfbec
四種常見的 JavaScript 記憶體洩漏
1.意外的全域性變數
未定義的變數會在全域性物件建立一個新變數,對於在瀏覽器的情況下,全域性物件是window。 看以下程式碼:
function foo(arg) {
bar = "this is a hidden global variable";
}
函式foo
內部使用var
宣告,實際上JS會把bar掛載在全域性物件上,意外建立一個全域性變數。等同於:
function foo(arg) {
window.bar = "this is an explicit global variable";
}
在上述情況下, 洩漏一個簡單的字串不會造成太大的傷害,但它肯定會更糟。
另一種可以建立偶然全域性變數的情況是this:
function foo() {
this.variable = "potential accidental global";
}
// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
解決方法:
在 JavaScript 檔案頭部加上 'use strict'
,使用嚴格模式避免意外的全域性變數,此時上例中的this指向undefined
。如果必須使用全域性變數儲存大量資料時,確保用完以後把它設定為 null 或者重新定義。
2.被遺忘的計時器或回撥函式
在JavaScript中使用setInterval非常常見。
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
// Do stuff with node and someResource.
node.innerHTML = JSON.stringify(someResource));
} }, 1000);
上面的程式碼表明,在節點node或者資料不再需要時,定時器依舊指向這些資料。所以哪怕當node節點被移除後,interval 仍舊存活並且垃圾回收器沒辦法回收,它的依賴也沒辦法被回收,除非終止定時器。
var element = document.getElementById('button');
function onClick(event) {
element.innerHtml = 'text';
}
element.addEventListener('click', onClick); // Do stuff
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
// Now when element goes out of scope,
// both element and onClick will be collected even in old browsers that don't
// handle cycles well.
對於上面觀察者的例子,一旦它們不再需要(或者關聯的物件變成不可達),明確地移除它們非常重要。其中IE 6 是無法處理迴圈引用的。因為老版本的 IE 是無法檢測 DOM 節點與 JavaScript 程式碼之間的迴圈引用,會導致記憶體洩漏。
但是,現代的瀏覽器(包括 IE 和 Microsoft Edge)使用了更先進的垃圾回收演算法(標記清除),已經可以正確檢測和處理迴圈引用了。即回收節點記憶體時,不必非要呼叫removeEventListener
了。
諸如jQuery之類的框架和庫在處理節點之前會刪除偵聽器(當使用它們的特定API時)。 這由庫內部處理,並確保不會產生任何洩漏,即使在有問題的瀏覽器(如舊版Internet Explorer)下執行也是如此。
3.閉包
JavaScript 開發的一個關鍵是閉包:這是一個內部函式,它可以訪問外部(封閉)函式的變數。由於 JavaScript 執行時的實現細節,用下邊這種方式可能會造成記憶體洩漏:
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing)
console.log("hi");
};
theThing = {
longStr: newArray(1000000).join('*'),
someMethod: function () {
console.log(someMessage);
}
};
};
setInterval(replaceThing, 1000);
每次呼叫replaceThing
,theThing
得到一個包含一個大陣列和一個新閉包(someMethod
)的新物件。同時,變數unused
是一個引用originalThing
的閉包(先前的replaceThing
又呼叫了theThing
)。someMethod
可以通過theThing
使用,someMethod
與unused
分享閉包作用域,儘管unused
從未使用,它引用的originalThing
迫使它保留在記憶體中(防止被回收)。需要記住的是一旦一個閉包作用域被同一個父作用域的閉包所建立,那麼這個作用域是共享的。
所有這些都可能導致嚴重的記憶體洩漏。當上面的程式碼片段一次又一次地執行時,你可以看到記憶體使用量的急劇增加。當垃圾收集器執行時,也不會減少。一個連結列表閉包被建立(在這種情況下 theThing 變數是根源),每一個閉包作用域對打陣列進行間接引用。
解決方法:
在 replaceThing
的最後新增 originalThing = null
。將所有聯絡都切斷。
4.脫離 DOM 的引用
如果把DOM 存成字典(JSON 鍵值對)或者陣列,此時,同樣的 DOM 元素存在兩個引用:一個在 DOM 樹中,另一個在字典中。如果在將來某個時候您決定刪除這些行,則需要使兩個引用都無法訪問,都清除掉。
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.
}
如果程式碼中儲存了表格某一個<td>
的引用。將來決定刪除整個表格的時候,直覺認為 GC 會回收除了已儲存的<td>
以外的其它節點。實際情況並非如此:此<td>
是表格的子節點,子元素與父元素是引用關係。由於程式碼保留了<td>
的引用,導致整個表格仍待在記憶體中。所以儲存 DOM 元素引用的時候,要小心謹慎。
避免記憶體洩漏
在區域性作用域中,等函式執行完畢,變數就沒有存在的必要了,js垃圾回收機制很快做出判斷並且回收,但是全域性變數什麼時候需要自動釋放記憶體空間則很難判斷,因此在我們的開發中,需要儘量避免使用全域性變數。
我們在使用閉包的時候,就會造成嚴重的記憶體洩漏,因為閉包的原因,區域性變數會一直儲存在記憶體中,所以在使用閉包的時候,要多加小心。
Resources
- http://www-bcf.usc.edu/~dkempe/CS104/08-29.pdf
- https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156
- http://www.nodesimplified.com/2017/08/javascript-memory-management-and.html
如果有別的關於記憶體洩漏好的資源,可以分享給我嘛謝謝了~
本人Github連結如下,歡迎各位Star
https://github.com/miqilin21/miqilin21.github.io