你知道的越多,你不知道的越多
點贊
再看,手留餘香,與有榮焉javascript
JS的內存空間分爲棧(stack)、堆(heap)、池(通常也會歸類爲棧中)。前端
其中棧存放變量,堆存放複雜對象,池存放常量,因此也叫常量池。java
棧是一種特殊的列表,棧內的元素只能經過列表的一端訪問,這一端稱爲棧頂。 棧被稱爲是一種後入先出(LIFO,last-in-first-out)的數據結構。 因爲棧具備後入先出的特色,因此任何不在棧頂的元素都沒法訪問。 爲了獲得棧底的元素,必須先拿掉上面的元素。git
在這裏,爲方便理解,經過類比乒乓球盒子來分析棧的存取方式。github
這種乒乓球的存放方式與棧中存取數據的方式一模一樣。 處於盒子中最頂層的乒乓球 5,它必定是最後被放進去,但能夠最早被使用。 而咱們想要使用底層的乒乓球 1,就必須將上面的 4 個乒乓球取出來,讓乒乓球1處於盒子頂層。 這就是棧空間先進後出,後進先出的特色。數組
堆是一種通過排序的樹形數據結構,每一個結點都有一個值。 一般咱們所說的堆的數據結構,是指二叉堆。 堆的特色是根結點的值最小(或最大),且根結點的兩個子樹也是一個堆。 因爲堆的這個特性,經常使用來實現優先隊列,堆的存取是隨意,這就如同咱們在圖書館的書架上取書, 雖然書的擺放是有順序的,可是咱們想取任意一本時沒必要像棧同樣,先取出前面全部的書, 咱們只須要關心書的名字。微信
基本數據類型共有6種:markdown
基本數據類型保存在棧內存中,由於基本數據類型佔用空間小、大小固定,經過按值來訪問,屬於被頻繁使用的數據。數據結構
爲了更好的搞懂基本數據類型變量與棧內存,咱們結合如下例子與圖解進行理解:閉包
let num1 = 1; let num2 = 1; 複製代碼
PS: 須要注意的是閉包中的基本數據類型變量不保存在棧內存中,而是保存在堆內存中。這個問題,咱們後文再說。
Array,Function,Object...能夠認爲除了上文提到的基本數據類型之外,全部類型都是引用數據類型。
引用數據類型存儲在堆內存中,由於引用數據類型佔據空間大、大小不固定。 若是存儲在棧中,將會影響程序運行的性能; 引用數據類型在棧中存儲了指針,該指針指向堆中該實體的起始地址。 當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中得到實體
爲了更好的搞懂變量對象與堆內存,咱們結合如下例子與圖解進行理解。
// 基本數據類型-棧內存 let a1 = 0; // 基本數據類型-棧內存 let a2 = 'this is string'; // 基本數據類型-棧內存 let a3 = null; // 對象的指針存放在棧內存中,指針指向的對象存放在堆內存中 let b = { m: 20 }; // 數組的指針存放在棧內存中,指針指向的數組存放在堆內存中 let c = [1, 2, 3]; 複製代碼
所以當咱們要訪問堆內存中的引用數據類型時,實際上咱們首先是從變量中獲取了該對象的地址指針, 而後再從堆內存中取得咱們須要的數據。
let a = 20; let b = a; b = 30; console.log(a); // 此時a的值是多少,是30?仍是20? 複製代碼
答案是:20
在這個例子中,a、b 都是基本類型,它們的值是存儲在棧內存中的,a、b 分別有各自獨立的棧空間, 因此修改了 b 的值之後,a 的值並不會發生變化。
從下圖能夠清晰的看到變量是如何複製並修改的。
let m = { a: 10, b: 20 }; let n = m; n.a = 15; console.log(m.a) //此時m.a的值是多少,是10?仍是15? 複製代碼
答案是:15
在這個例子中,m、n都是引用類型,棧內存中存放地址指向堆內存中的對象, 引用類型的複製會爲新的變量自動分配一個新的值保存在變量中, 但只是引用類型的一個地址指針而已,實際指向的是同一個對象, 因此修改 n.a 的值後,相應的 m.a 也就發生了改變。
從下圖能夠清晰的看到變量是如何複製並修改的。
在JS中,基本數據類型變量大小固定,而且操做簡單容易,因此把它們放入棧中存儲。 引用類型變量大小不固定,因此把它們分配給堆中,讓他們申請空間的時候本身肯定大小,這樣把它們分開存儲可以使得程序運行起來佔用的內存最小。
棧內存因爲它的特色,因此它的系統效率較高。 堆內存須要分配空間和地址,還要把地址存到棧中,因此效率低於棧。
棧內存中變量通常在它的當前執行環境結束就會被銷燬被垃圾回收制回收, 而堆內存中的變量則不會,由於不肯定其餘的地方是否是還有一些對它的引用。 堆內存中的變量只有在全部對它的引用都結束的時候纔會被回收。
閉包中的變量並不保存中棧內存中,而是保存在堆內存中。 這也就解釋了函數調用以後以後爲何閉包還能引用到函數內的變量。
咱們先來看什麼是閉包:
function A() { let a = 1; function B() { console.log(a); } return B; } let res = A(); 複製代碼
函數 A 返回了一個函數 B,而且函數 B 中使用了函數 A 的變量,函數 B 就被稱爲閉包。
函數 A 彈出調用棧後,函數 A 中的變量這時候是存儲在堆上的,因此函數B依舊能引用到函數A中的變量。 如今的 JS 引擎能夠經過逃逸分析辨別出哪些變量須要存儲在堆上,哪些須要存儲在棧上。
歡迎關注微信公衆號
【前端小黑屋】
,每週1-3篇精品優質文章推送,助你走上進階之旅