底層語言,如C,有底層內存管理函數malloc()和free();JavaScript建立的對象再也不使用的時候將會自動釋放的過程稱爲垃圾回收,這種回收機制帶來了一些問題:開發者們能夠決定不關心內存管理。javascript
1.值的初始化html
JavaScript 在定義變量時就完成了內存分配。java
var n = 123; // 給數值變量分配內存
var s = "azerty"; // 給字符串分配內存
var o = {
a: 1,
b: null
}; // 給對象及其包含的值分配內存
// 給數組及其包含的值分配內存(就像對象同樣)
var a = [1, null, "abra"];
function f(a){
return a + 2;
} // 給函數(可調用的對象)分配內存
// 函數表達式也能分配一個對象
someElement.addEventListener('click', function(){
someElement.style.backgroundColor = 'blue';
}, false);
複製代碼
2.經過函數調用分配內存node
有些函數調用結果是分配對象內存git
var d = new Date(); // 分配一個 Date 對象
var e = document.createElement('div'); // 分配一個 DOM 元素
複製代碼
有些方法分配新變量或者新對象github
var s = "azerty";
var s2 = s.substr(0, 3); // s2 是一個新的字符串
// 由於字符串是不變量,
// JavaScript 可能決定不分配內存,
// 只是存儲了 [0-3] 的範圍。
var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2);
// 新數組有四個元素,是 a 鏈接 a2 的結果
複製代碼
3.使用值 使用值的過程其實是對分配內存進行讀取與寫入的操做。讀取與寫入多是寫入一個變量或者一個對象的屬性值,甚至傳遞函數的參數。web
4.當內存再也不須要使用時釋放 詳情請見垃圾回收機制算法
1.引用 在內存管理的環境中,一個對象若是有訪問另外一個對象的權限(隱式或者顯式),叫作一個對象引用另外一個對象。例如,一個Javascript對象具備對它原型的引用(隱式引用)和對它屬性的引用(顯式引用)。chrome
在這裏,「對象」的概念不只特指 JavaScript 對象,還包括函數做用域(或者全局詞法做用域)。數組
2.引用數垃圾回收 這是最簡單的垃圾收集算法。此算法把「對象是否再也不須要」簡化定義爲「對象有沒有其餘對象引用到它」。若是沒有引用指向該對象(零引用),對象將被垃圾回收機制回收。(相關閱讀:阮一峯:JavaScript 內存泄漏教程)
var o = {
a: {
b:2
}
};
// 兩個對象被建立,一個做爲另外一個的屬性被引用,另外一個被分配給變量o
// 很顯然,沒有一個能夠被垃圾收集
var o2 = o; // o2變量是第二個對「這個對象」的引用
o = 1; // 如今,「這個對象」的原始引用o被o2替換了
var oa = o2.a; // 引用「這個對象」的a屬性
// 如今,「這個對象」有兩個引用了,一個是o2,一個是oa
o2 = "yo"; // 最初的對象如今已是零引用了
// 他能夠被垃圾回收了
// 然而它的屬性a的對象還在被oa引用,因此還不能回收
oa = null; // a屬性的那個對象如今也是零引用了
// 它能夠被垃圾回收了
複製代碼
3.標記清除法 這個算法把「對象是否再也不須要」簡化定義爲「對象是否能夠得到」。
這個算法假定設置一個叫作根(root)的對象(在Javascript裏,根是全局對象)。按期的,垃圾回收器將從根開始,找全部從根開始引用的對象,而後找這些對象引用的對象……從根開始,垃圾回收器將找到全部能夠得到的對象和全部不能得到的對象。
這個算法比前一個要好,由於「有零引用的對象」老是不可得到的,可是相反卻不必定,參考「循環引用」。
從2012年起,全部現代瀏覽器都使用了標記-清除垃圾回收算法。全部對JavaScript垃圾回收算法的改進都是基於標記-清除算法的改進,並無改進標記-清除算法自己和它對「對象是否再也不須要」的簡化定義。
1.全局變量 JavaScript 處理未定義變量的方式比較寬鬆:未定義的變量會在全局對象建立一個新變量。在瀏覽器中,全局對象是 window 。
function foo(arg) {
bar = "這是一個局部變量";
}
//實際狀況是
function foo(arg) {
window.bar = "這實際是一個全局變量";
}
複製代碼
或者是
function foo() {
this.variable = "potential accidental global";
}
// Foo 調用本身,this 指向了全局對象(window)
// 而不是 undefined
foo();
複製代碼
注意事項: 儘管咱們討論了一些意外的全局變量,可是仍有一些明確的全局變量產生的垃圾。它們被定義爲不可回收(除非定義爲空或從新分配)。尤爲當全局變量用於臨時存儲和處理大量信息時,須要多加當心。若是必須使用全局變量存儲大量數據時,確保用完之後把它設置爲 null 或者從新定義。與全局變量相關的增長內存消耗的一個主因是緩存,緩存內容沒法被回收。
相關閱讀: 命名空間-極客學院 當即執行函數IIFE-MDN 當即執行函數IIFE-伯樂在線
2:計時器或回調函數
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
// 處理 node 和 someResource
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);
複製代碼
此例說明了什麼:與節點或數據關聯的計時器再也不須要,node 對象能夠刪除,整個回調函數也不須要了。但是,計時器回調函數仍然沒被回收(計時器中止纔會被回收)。同時,someResource 若是存儲了大量的數據,也是沒法被回收的。
觀察者代碼示例:
var element = document.getElementById('button');
function onClick(event) {
element.innerHTML = 'text';
}
element.addEventListener('click', onClick);
複製代碼
老版本的 IE 是沒法檢測 DOM 節點與 JavaScript 代碼之間的循環引用,會致使內存泄漏。現在,現代的瀏覽器(包括 IE 和 Microsoft Edge)使用了更先進的垃圾回收算法,已經能夠正確檢測和處理循環引用了。換言之,回收節點內存時,沒必要非要調用 removeEventListener 了。
3:脫離 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);
// 更多邏輯
}
function removeButton() {
// 按鈕是 body 的後代元素
document.body.removeChild(document.getElementById('button'));
// 此時,仍舊存在一個全局的 #button 的引用
// elements 字典。button 元素仍舊在內存中,不能被 GC 回收。
}
複製代碼
有時,保存 DOM 節點內部數據結構頗有用。假如你想快速更新表格的幾行內容,把每一行 DOM 存成字典(JSON 鍵值對)或者數組頗有意義。此時,一樣的 DOM 元素存在兩個引用:一個在 DOM 樹中,另外一個在字典中。未來你決定刪除這些行時,須要把兩個引用都清除。
4.閉包
相關閱讀:閉包-MDN
chrome瀏覽器在57版本後Timeline工具改名爲Performance。相關閱讀:Performance-chorme官方文檔
Performance 能夠檢測代碼中不須要的內存。在此截圖中,咱們能夠看到潛在的泄漏對象穩定的增加,數據採集期間內存的變化爲[102-216M],出現明顯的內存泄漏,從圖中的增量來看,js的內存泄漏最爲明顯也是最主要的。
Profiles工具主要是用來監控和查找瀏覽器內存問題的工具。相關閱讀:Profiles-chorme官方文檔
Profiles工具分爲三種類型: 1.Take heap snapshot 保存當前內存快照 2.Record allocation profile 記錄一段時間的內存分配和使用狀況 3.Record allocation timeline 以時間線記錄內存的分配和使用狀況
以chrome文檔中的代碼爲例:
var x = [];
function createSomeNodes() {
var div,
i = 100,
frag = document.createDocumentFragment();
for (;i > 0; i--) {
div = document.createElement("div");
div.appendChild(document.createTextNode(i + " - "+ new Date().toTimeString()));
frag.appendChild(div);
}
document.getElementById("nodes").appendChild(frag);
}
function grow() {
x.push(new Array(1000000).join('x'));
createSomeNodes();
setTimeout(grow,1000);
}
複製代碼
當 grow 執行的時候,開始建立 div 節點並插入到 DOM 中,而且給全局變量分配一個巨大的數組。經過以上提到的工具能夠檢測到內存穩定上升。
找出週期性增加的內存 Performance 工具擅長作這些。在 Chrome 中打開例子,打開 Dev Tools ,切換到 Performance,勾選 memory 並點擊記錄按鈕,而後點擊頁面上的 The Button
按鈕。過一陣中止記錄看結果:
兩種跡象顯示出現了內存泄漏,圖中的 Nodes(綠線)和 JS heap(藍線)。Nodes 穩定增加,並未降低,這是個顯著的信號。
JS heap 的內存佔用也是穩定增加。因爲垃圾收集器的影響,並不那麼容易發現。圖中顯示內存佔用忽漲忽跌,實際上每一次下跌以後,JS heap 的大小都比原先大了。換言之,儘管垃圾收集器不斷的收集內存,內存仍是週期性的泄漏了。
肯定存在內存泄漏以後,咱們找找根源所在。待補充。。。