var a = 20; var b = 'abc'; var c = true; var d = { m: 20 }
由於JavaScript具備自動垃圾回收機制,因此對於前端開發來講,內存空間並非一個常常被說起的概念,很容易被你們忽視。特別是不少不是計算機專業的朋友在進入到前端以後,會對內存空間的認知比較模糊,甚至有些人乾脆就是一無所知。前端
固然也包括我本身。在很長一段時間裏認爲內存空間的概念在JS的學習中並非那麼重要。但是後我當我回過頭來從新整理JS基礎時,發現因爲對它們的模糊認知,致使了不少東西我都理解得並不明白。好比最基本的引用數據類型和引用傳遞究竟是怎麼回事兒?好比淺複製與深複製有什麼不一樣?還有閉包,原型等等。程序員
所以後來我才漸漸明白,想要對JS的理解更加深入,就必須對內存空間有一個清晰的認知。面試
在JS中,每個數據都須要一個內存空間。內存空間又被分爲兩種,棧內存(stock)與堆內存(heap)。算法
JS中的基礎數據類型,這些值都有固定的大小,每每都保存在棧內存中,由系統自動分配存儲空間。咱們能夠直接操做保存在棧內存空間的值,所以基礎數據類型都是按值訪問。編程
數據在棧內存中的存儲與使用方式相似於數據結構中的堆棧數據結構,遵循後進先出的原則。數組
基礎數據類型:
Number
String
Null
Undefined
Boolean
複習一下,此問題經常在面試中問到,然而答不出來的人大有人在 ~ ~數據結構
要簡單理解棧內存空間的存儲方式,咱們能夠經過類比乒乓球盒子來分析。以下圖左側。閉包
這種乒乓球的存放方式與棧內存中存儲數據的方式一模一樣。處於盒子中最頂層的乒乓球5,它必定是最後被放進去,但能夠最早被使用。而咱們想要使用底層的乒乓球1,就必須將上面的4個乒乓球取出來,讓乒乓球1處於盒子頂層。這就是棧空間先進後出,後進先出的特色。圖中已經詳細的代表了棧空間的存儲原理。函數
拓展閱讀:原來你是這樣的 setTimeout性能
與其餘語言不通,JS的引用數據類型,好比數組Array,它們值的大小是不固定的。引用數據類型的值是保存在堆內存中的對象。JavaScript不容許直接訪問堆內存中的位置,所以咱們不能直接操做對象的堆內存空間。在操做對象時,其實是在操做對象的引用而不是實際的對象。所以,引用類型的值都是按引用訪問的。這裏的引用,咱們能夠粗淺地理解爲保存在棧內存中的一個地址,該地址與堆內存的實際值相關聯。
要簡單理解堆內存空間的存儲方式,咱們能夠經過書架與書進行類比。
書雖然也有序的存放在書架上,可是咱們只要知道書的名字,咱們就能夠很方便的取出咱們想要的書,而不用像從乒乓球盒子裏取乒乓同樣,非得將上面的全部乒乓球拿出來才能取到中間的某一個乒乓球。比如在JSON格式的數據中,咱們存儲的key-value
是能夠無序的,由於順序的不一樣並不影響咱們的使用,咱們只須要關心書的名字。
爲了更好的搞懂棧內存與堆內存,咱們能夠結合如下例子與圖解進行理解。
var a1 = 0; // 棧 var a2 = 'this is string'; // 棧 var a3 = null; // 棧 var b = { m: 20 }; // 變量b存在於棧中, 做爲對象存在於堆內存中 var c = [1, 2, 3]; // 變量c存在於棧中,[1, 2, 3] 做爲對象存在於堆內存中
所以當咱們要訪問堆內存中的引用數據類型時,實際上咱們首先是從棧中獲取了該對象的地址引用(或者地址指針),而後再從堆內存中取得咱們須要的數據。
理解了JS的內存空間,咱們就能夠藉助內存空間的特性來驗證一下引用類型的一些特色了。
在前端面試中咱們經常會遇到這樣一個相似的題目
// demo01.js var a = 20; var b = a; b = 30; // 這時a的值是多少?
// demo02.js var m = { a: 10, b: 20 } var n = m; n.a = 15; // 這時m.a的值是多少
在棧內存中的數據發生複製行爲時,系統會自動爲新的變量分配一個新值。var b = a
執行以後,a與b雖然值都等於20,可是他們其實已是相互獨立互不影響的值了。具體如圖。因此咱們修改了b的值之後,a的值並不會發生變化。
在demo02中,咱們經過var n = m
執行一次複製引用類型的操做。引用類型的複製一樣也會爲新的變量自動分配一個新的值保存在棧內存中,但不一樣的是,這個新的值,僅僅只是引用類型的一個地址指針。當地址指針相同時,儘管他們相互獨立,可是在堆內存中訪問到的具體對象其實是同一個。如圖所示。
所以當我改變n時,m也發生了變化。這就是引用類型的特性。
經過內存的角度來理解,是否是感受要輕鬆不少。除此以外,咱們還能夠以此爲基礎,一步一步的理解JavaScript的執行上下文,做用域鏈,閉包,原型鏈等重要概念。可是時間有限,就不作詳細的解讀了。
由於JavaScript具備自動垃圾收集機制,因此咱們在開發時好像不用關心內存的使用問題,內存的分配與回收都徹底實現了自動管理。可是根據我本身的開發經驗,瞭解內存機制有助於本身清晰的認識到本身寫的代碼在執行過程當中發生過什麼,從而寫出性能更加優秀的代碼。所以關心內存是一件很是重要的事情。
JavaScript的內存生命週期
1. 分配你所須要的內存 2. 使用分配到的內存(讀、寫) 3. 不須要時將其釋放、歸還
爲了便於理解,咱們使用一個簡單的例子來解釋這個週期。
var a = 20; // 在堆內存中給數值變量分配空間 alert(a + 100); // 使用內存 var a = null; // 使用完畢以後,釋放內存空間
第一步和第二步咱們都很好理解,JavaScript在定義變量時就完成了內存分配。第三步釋放內存空間則是咱們須要重點理解的一個點。
JavaScript有自動垃圾收集機制,那麼這個自動垃圾收集機制的原理是什麼呢?其實很簡單,就是找出那些再也不繼續使用的值,而後釋放其佔用的內存。垃圾收集器會每隔固定的時間段就執行一次釋放操做。
在JavaScript中,最經常使用的是經過標記清除的算法來找到哪些對象是再也不繼續使用的,所以a = null
其實僅僅只是作了一個釋放引用的操做,讓 a 本來對應的值失去引用,脫離執行環境,這個值會在下一次垃圾收集器執行操做時被找到並釋放。而在適當的時候解除引用,是爲頁面得到更好性能的一個重要方式。
- 在局部做用域中,當函數執行完畢,局部變量也就沒有存在的必要了,所以垃圾收集器很容易作出判斷並回收。可是全局變量何時須要自動釋放內存空間則很難判斷,所以在咱們的開發中,須要儘可能避免使用全局變量,以確保性能問題。
- 要詳細瞭解垃圾收集機制,建議閱讀《JavaScript高級編程》中的4.3節
最後總結一下:
棧內存 | 堆內存 |
---|---|
存儲基礎數據類型 | 存儲引用數據類型 |
按值訪問 | 按引用訪問 |
存儲的值大小固定 | 存儲的值大小不定,可動態調整 |
由系統自動分配內存空間 | 由程序員經過代碼進行分配 |
主要用來執行程序 | 主要用來存放對象 |
空間小,運行效率高 | 空間大,可是運行效率相對較低 |
先進後出,後進先出 | 無序存儲,可根據引用直接獲取 |