JS中的棧內存、堆內存

淺談JS中的堆棧

引言:咱們都熟知而且經常使用JS變量的聲明以及初始化(賦值),好比一行極其簡單的代碼var str = '我是字符串',那麼這行代碼執行的時候發生了什麼呢?再好比var obj = {name: 'reslicma'}又發生了什麼?他們同樣嗎?請看正文。javascript

JS中變量的類型

咱們先行討論JS中變量的類型,由於JS中變量的具體存儲方式是取決於這個變量的類型的。JS中的變量共有兩大類:基本數據類型引用數據類型,咱們下文說基本型就是基本數據類型,引用型就是引用數據類型。java

基本數據類型(簡單數據類型)

JS中的基本型共有五種:string,number,Boolean,undefined,null。分別對應:字符串類型,數字類型,布爾類型,undefined(變量聲明未初始化),null(空對象或理解爲空指針)。ui

引用數據類型

JS中的引用型:Array,Function,Object。可是實際上就是一種:Object型,沒錯,就是對象,畢竟Array,Function也是對象。JS一切皆對象這句話並不爲過······spa

棧內存和堆內存

介紹完了基本型和引用型就能夠真正的進入正題了。咱們知道聲明一個變量而且給它賦值這樣的操做對於這兩種類型而言沒什麼區別,可是對這兩種類型的具體的操做卻大不相同。 在JS中,棧內存用於存儲基本型的變量值,堆內存用於存儲引用型的值。這是爲何呢?由於JS這門語言和其餘語言有一個不一樣之處:不容許直接訪問內存的位置,也就是說不能直接操做對象的內存空間,操做的是對象的引用而已,那麼引用能夠理解爲是一個指針,是一個具體的堆內存的地址。我寫下這幾行代碼,而且附上一張圖,讓咱們來詳細的看一下:3d

var str = `我是字符串`,
    num = 1,
    bl = true,
    nu = null,
    un = undefined,
    obj = {
        name: 'reslicma'
    }
複製代碼

在內存中就發生了以下圖這樣的事情: 指針

堆棧內存圖
咱們接下來詳細的分析它: 首先,我定義的前五個變量都是基本型,那麼他們都是存儲在 棧內存當中,而且他們存儲的就是 值自己,因此說訪問基本型變量就能訪問獲得 。而obj這個變量是個引用型,因此它在棧內存中只保存了一個指針(或者理解爲一個地址),好比上圖中的這個地址,那麼這個地址指向了堆內存中的一塊內存空間,這個空間纔是真正存儲了這個obj對象的內存空間。

理解棧內存和堆內存

咱們來講一下棧內存和堆內存具體的區別和聯繫。code

棧內存就像一個線性的、規則的、大小基本固定的、有序的排列起來的一塊塊內存空間,就像我上圖畫的那樣,每一個單元大小固定,規則有序的排列下來,就是棧。因此,在定義一個基本型變量的時候,發生的事情以下:向棧內存申請(注意是申請)一塊空間,而後把你聲明的變量名和這個變量的具體的值自己壓入這個申請好的小空間內。cdn

堆內存就像一個不規則的、大小不固定的、無序的一塊塊內存空間,像上圖中我畫的堆內存圖中,大小不固定,而且每一塊堆內存都有一個本身的地址(指針),用來操做它們。這個地址頗有意思,在你定義一個引用型變量的時候,向堆內存申請一塊空間,用於存儲這個具體的引用型的值(對象),同時JS會隨機分配給這塊堆內存的小空間一個地址,而後,把這個變量名和這個地址壓入申請好的棧空間內。這裏就是我詳細說明的地方,其實上圖就很形象的表達了這個流程。對象

棧內存和堆內存的優缺點

那麼爲何JS要這樣區分棧內存和堆內存呢?在JS中,這些基本型變量大小固定,而且操做容易簡單,因此把它們放入棧中存儲。引用型變量大小不固定,因此把它們分配給堆中,讓他們申請空間的時候本身肯定大小,這樣把它們分開存儲可以使得程序運行起來佔用的內存最小。blog

棧內存因爲它的特色,因此它的系統效率較高,堆內存須要分配空間和地址,還要把地址存到棧中,因此效率低於棧。

棧內存和堆內存的垃圾回收

咱們知道JS是有垃圾回收制的(不詳細說),棧內存中基本型通常在它的當前執行環境結束就會被銷燬被垃圾回收制回收,而引用類型不會,由於不肯定其餘的地方是否是還有一些對它的引用,因此引用型只有在全部對它的引用都結束的時候纔會被回收掉。

加深理解

注意:在JS中訪問變量時,是把變量名做爲索引來尋找值的,不管是基本型仍是引用型。也就是說,訪問變量的過程就是:經過變量名找到棧內存中存儲的具體的值,若是是基本型,直接就返回值,若是是引用型,返回一個指向堆內存的地址

案例1:基本型的複製:

var num1 = 1
var num2 = num1
// 修改num1的值
num1 = 2
console.log(num2) // 仍是1,不會改變
複製代碼

解析具體過程:首先在棧內存中壓入一個變量名爲num1、值爲1的一個變量。而後,第二行代碼:賦值操做,先執行賦值運算符右邊的式子,因此經過變量名找到了num1的值1,而後把這個值1返回而且賦值給了num2這個變量,因此棧內存中就又壓入了一個變量名爲num2、值爲1的變量,這兩個變量的值1相等但不是同一個。因此,改變num1的值就只改變了num1棧內存中的值,對num2沒有任何影響。圖解:

基本型複製圖
案例2:引用型的複製:

var obj1 = {name: 'reslicma'}
var obj2 = obj1
obj1.name = '我被修改了'
console.log(obj2.name) // 我被修改了
複製代碼

咱們前面已經說過了,不管是基本型仍是引用型都是按變量名訪問棧內存中的值,因此,第二句代碼執行的時候,就至關於把經過obj1找到的那個內存地址賦值給了obj2這個變量,因此這兩個變量在棧內存中都是存儲的同一個地址、指針,指向的是同一塊堆內存中的空間,我修改了obj1的內容,那麼因爲obj2也是指向這個內容,因此obj2的內容也會隨之改變。

內存圖解:

基本型複製圖
基本型複製圖
相關文章
相關標籤/搜索