某些狀況下,調用堆棧中函數調用的數量超出了調用堆棧的實際大小,瀏覽器會拋出一個錯誤終止運行。這個就涉及到內存問題了。html
JS內存空間分爲棧(stack)、堆(heap)、池(通常也會歸類爲棧中)。 其中棧存放變量,堆存放複雜對象,池存放常量,因此也叫常量池。算法
一、基本類型 --> 保存在棧內存中,由於這些類型在內存中分別佔有固定大小的空間,經過按值來訪問。基本類型一共有6種:Undefined、Null、Boolean、Number 、String和Symbol數組
二、引用類型 --> 保存在堆內存中,由於這種值的大小不固定,所以不能把它們保存到棧內存中,但內存地址大小的固定的,所以保存在堆內存中,在棧內存中存放的只是該對象的訪問地址。當查詢引用類型的變量時, 先從棧中讀取內存地址, 而後再經過地址找到堆中的值。對於這種,咱們把它叫作按引用訪問。瀏覽器
在計算機的數據結構中,棧比堆的運算速度快,Object是一個複雜的結構且能夠擴展:數組可擴充,對象可添加屬性,均可以增刪改查。將他們放在堆中是爲了避免影響棧的效率。而是經過引用的方式查找到堆中的實際對象再進行操做。因此查找引用類型值的時候先去棧查找再去堆查找。服務器
例子:數據結構
<script> var a = {n:1}; var b = a; a.x = a = {n:2}; console.log(a.x);// --> undefined console.log(b.x);// --> {n:2} </script>
解析:閉包
var a = {n:1}; var b = a;
在這裏a指向了一個對象{n:1}(咱們姑且稱它爲對象A),b指向了a所指向的對象,也就是說,在這時候a和b都是指向對象A的。app
a.x = a = {n:2};
函數
另外, 閉包中的變量並不保存中棧內存中,而是保存在堆內存中,這也就解釋了函數以後以後爲何閉包還能引用到函數內的變量。學習
function A() { let a = 1 function B() { console.log(a) } return B }
函數 A 彈出調用棧後,函數 A 中的變量這時候是存儲在堆上的,因此函數B依舊能引用到函數A中的變量。如今的 JS 引擎能夠經過逃逸分析辨別出哪些變量須要存儲在堆上,哪些須要存儲在棧上。
JavaScript的內存生命週期是
一、分配你所須要的內存
二、使用分配到的內存(讀、寫)
三、不須要時將其釋放、歸還
JavaScript有自動垃圾收集機制,垃圾收集器會每隔一段時間就執行一次釋放操做,找出那些再也不繼續使用的值,而後釋放其佔用的內存。
引用計數算法簡單理解,就是看一個對象是否有指向它的引用。若是沒有其餘對象指向它了,說明該對象已經再也不須要了。
// 建立一個對象person,他有兩個指向屬性age和name的引用 var person = { age: 12, name: 'aaaa' }; person.name = null; // 雖然name設置爲null,但由於person對象還有指向name的引用,所以name不會回收 var p = person; person = 1; //原來的person對象被賦值爲1,但由於有新引用p指向原person對象,所以它不會被回收 p = null; //原person對象已經沒有引用,很快會被回收
引用計數有一個致命的問題,那就是循環引用
若是兩個對象相互引用,儘管他們已再也不使用,可是垃圾回收器不會進行回收,最終可能會致使內存泄露。
function cycle() { var o1 = {}; var o2 = {}; o1.a = o2; o2.a = o1; return "cycle reference!" } cycle();
cycle函數執行完成以後,對象o1和o2實際上已經再也不須要了,但根據引用計數的原則,他們之間的相互引用依然存在,所以這部份內存不會被回收。因此現代瀏覽器再也不使用這個算法。
可是IE依舊使用,以下,變量div有事件處理函數的引用,同時事件處理函數也有div的引用,由於div變量可在函數內被訪問,因此循環引用就出現了。
var div = document.createElement("div"); div.onclick = function() { console.log("click"); };
標記清除算法將「再也不使用的對象」定義爲「沒法到達的對象」。即從根部(在JS中就是全局對象)出發定時掃描內存中的對象,凡是能從根部到達的對象,保留。那些從根部出發沒法觸及到的對象被標記爲再也不使用,稍後進行回收。因此像上面的例子,雖然是循環引用,但從全局來講並無被使用到,因此就能夠正確被垃圾回收處理了。
算法由如下幾步組成:
對於主流瀏覽器來講,只須要切斷須要回收的對象與根部的聯繫。但可能還存在着與DOM元素綁定有關的內存問題:
email.message = document.createElement(「div」); displayList.appendChild(email.message); // 稍後從displayList中清除DOM元素 displayList.removeAllChildren();
上面代碼中,div元素已經從DOM樹中清除,可是該div元素還綁定在email對象中,因此若是email對象存在,那麼該div元素就會一直保存在內存中。若是再也不須要使用的話,須要手動設置email.message = null。
另外ES6 新出的兩種數據結構:WeakSet 和 WeakMap,表示這是弱引用,它們對於值的引用都是不計入垃圾回收機制的。
const wm = new WeakMap(); const element = document.getElementById('example'); wm.set(element, 'some information'); wm.get(element) // "some information"
先新建一個 Weakmap 實例,而後將一個 DOM 節點做爲鍵名存入該實例,並將一些附加信息做爲鍵值,一塊兒存放在 WeakMap 裏面。這時,WeakMap 裏面對element的引用就是弱引用,不會被計入垃圾回收機制。
續篇 js內存深刻學習(二)