對於不少沒經驗的前端開發來講,以爲JS反正有垃圾回收機制,很容易忽視內存空間的管理,這實際上是一個大錯誤。javascript
直到最近,看了阮一峯老師關於JS內存泄漏的文章,才發現本身之前寫的代碼,存在許多內存泄漏的問題,再者,由於忽略對內存空間的學習,致使後面不少進階概念很模糊,好比閉包、做用域鏈,好比深拷貝與淺拷貝的區別等等。html
這裏先介紹內存空間,後續還會經過別的文章來介紹深淺拷貝和內存泄漏。前端
JavaScript的內存生命週期:java
1. 分配你所須要的內存
2. 使用分配到的內存(讀、寫)
3. 不須要時將其釋放、歸還
複製代碼
爲了便於理解,咱們使用一個簡單的例子來解釋這個週期。git
var a = 10; // 在內存中給數值變量分配空間
alert(a + 90); // 使用分配到的內存
a = null; // 使用完畢以後,釋放內存空間
複製代碼
在JS中,每個數據都須要一個內存空間。內存空間又被分爲兩種,棧內存(stack)與堆內存(heap)。github
棧(stack)是有序的,主要存放一些基本類型的變量和對象的地址,每一個區塊按照必定次序存放(後進先出),它們都是直接按值存儲在棧中的,每種類型的數據佔用的內存空間的大小也是肯定的,並由系統自動分配和自動釋放。面試
所以,這樣帶來的好處就是,內存能夠及時獲得回收,相對於堆來講,更加容易管理內存空間,且尋址速度也更快。數組
堆(heap)是沒有特別的順序的,數據能夠任意存放,多用於複雜數據類型(引用類型)分配空間,例如數組對象、object對象。bash
其實這樣說也不太準確,由於,引用類型數據的地址是存儲於棧中的,當咱們想要訪問引用類型的值的時候,須要先從棧中得到想要訪問對象的地址,而後,再經過地址指向找出堆中的所需數據。就比如書架上的書,雖然已經按順序放好了,但咱們只要知道書的名字,就能夠對應的取下來。閉包
首先,咱們來看一下代碼:
//原始類型都放在棧(stack)裏
//引用類型都放在堆(heap)裏
var a = 10;
var b = 'lzm';
var c = true;
var d = { n: 22 }; //地址假設爲0x0012ff7f,不表明實際地址
var e = { n: 22 }; //從新開闢一段內存空間,地址假設爲0x0012ff8c
console.log(e==d); //false
var obj = new Object(); //地址假設爲0x0012ff9d
var arr = ['a','b','c']; //地址假設爲0x0012ff6e
複製代碼
爲何console.log(e == d)
的結果爲false
?能夠用下面的內存圖解釋:
變量a,b,c爲基本數據類型,它們的值,直接存放在棧中,d,e,obj,arr爲複合數據類型,他們的引用變量及地址存儲在棧中,指向於存儲在堆中的實際對象。咱們是沒法直接操縱堆中的數據的,也就是說咱們沒法直接操縱對象,咱們只能經過棧中對對象的引用來操做對象,就像咱們經過遙控機操做電視同樣,區別在於這臺電視自己並無控制按鈕。
變量d,e雖然指向存在堆內存中對象內容的值是相等的,可是它們來自棧內存中變量地址不相同,致使console.log(e == d)
的結果爲false
。
這裏就回到了最初的疑問,爲何原始類型值要放在棧中,而引用類型值要放在堆中,爲何要分開放置呢?單列一種內存豈不是更省事嗎?那接下來,援引這篇文章裏邊的解釋:
記住一句話:能量是守衡的,無非是時間換空間,空間換時間的問題。堆比棧大,棧比堆的運算速度快,對象是一個複雜的結構,而且能夠自由擴展,如:數組能夠無限擴充,對象能夠自由添加屬性。將他們放在堆中是爲了避免影響棧的效率。而是經過引用的方式查找到堆中的實際對象再進行操做。相對於簡單數據類型而言,簡單數據類型就比較穩定,而且它只佔據很小的內存。不將簡單數據類型放在堆是由於經過引用到堆中查找實際對象是要花費時間的,而這個綜合成本遠大於直接從棧中取得實際值的成本。因此簡單數據類型的值直接存放在棧中。
下面的幾道是關於內存空間的面試題,雖然不是特別的難,但比較扣細節你稍不注意就錯了,個人建議仍是老老實實畫個內存圖再自信的給出正確答案吧。
第一題:
var a = 1
var b = a
b = 2
請問 a 顯示是幾?
複製代碼
上圖中能夠看出,答案爲:1。在棧內存中的數據發生複製行爲時,系統會自動爲新的變量分配一個新值。var b = a
執行以後,a與b雖然值都等於1,可是他們其實已是相互獨立互不影響的值了。
第二題:
var a = {name: 'a'}
var b = a
b = {name: 'b'}
請問如今 a.name 是多少?
複製代碼
上圖中能夠看出,答案爲:"a"。由於b ={name:'b'}
後至關於從新在堆內存中分配內存給對象{name:'b'}
,同時棧內存中變量b的指向地址也隨之變化,變量a不受影響。
第三題:
var a = {name: 'a'}
var b = a
b.name = 'b'
請問如今 a.name 是多少?
複製代碼
上圖中能夠看出,答案爲:"b"。咱們經過var b = a執行一次複製引用類型的操做。引用類型的複製一樣也會爲新的變量自動分配一個新的值保存在棧內存中,但不一樣的是,這個新的值,僅僅只是引用類型的一個地址指針。當地址指針相同時,儘管他們相互獨立,可是在堆內存中訪問到的具體對象其實是同一個,所以b.name ='b'
使堆內存中對象的value
值變化,a.name
的值也隨之變化。
第四題:
var a = {name: 'a'}
var b = a
b = null
請問如今 a 是什麼?
複製代碼
上圖中能夠看出,答案爲:{name: "a"}。由於null
爲基本類型,存在棧內存當中。所以棧內存中的變量b由以前指向對象的一個地址轉變爲null
,變量a的地址仍是指向原先的對象。
最後來個圖總結一下:
以上都是經過內存圖來解釋關於內存空間的知識,若有不合理的地方,但願指正一下~後續還會增長內存泄漏以及深淺拷貝的文章,敬請期待!
本人Github連接以下,歡迎各位Star