- 原文地址:JavaScript’s Memory Model
- 原文做者:Ethan Nam
- 譯者:Chor
// 聲明一些變量並進行初始化 var a = 5 let b = 'xy' const c = true // 從新賦值 a = 6 b = b + 'z' c = false // TypeError: Assignment to constant variable
對咱們程序員來講,聲明變量、進行初始化和賦值幾乎是天天都在作的一件事情。不過,這些操做本質上作了什麼事情呢?JavaScript 是如何在內部對這些進行處理的?更重要的是,瞭解 JavaScript 的底層細節對咱們程序員有什麼好處?javascript
本文的大綱以下:html
咱們先從一個簡單的例子講起:聲明一個名爲 muNumber
的變量,並初始化賦值爲 23。前端
let myNumber = 23
當執行這一行代碼的時候,JS 將會 ......java
myNumber
)咱們習慣的說法是「myNumber
等於23」,但更嚴謹的說法應該是,myNumber
等於保存着值 23 的那個內存空間的地址。這二者的區別很關鍵,須要搞清楚。git
若是咱們建立一個新變量 newVar
並將 myNumber
賦值給它 ......程序員
let newVar = myNumber
...... 因爲 myNumber
實際上等於內存地址 「0012CCGWH80」,所以這一操做會使得 newVar
也等於 「0012CCGWH80」,也就是等於保存着值 23 的那個內存地址。最終,咱們可能會習慣說「newVar
如今等於 23 了」。github
那麼,若是我這樣作會發生什麼呢?數組
myNumber = myNumber + 1
myNumber
天然會「等於」 24,不過 newVar
和 myNumber
指向的但是同一塊內存空間啊,newVar
是否也會「等於」 24 呢?session
並不會。在 JS 中,基本數據類型是不可改變的,在 「myNumber + 1」 被解析爲 「24」 的時候,JS 實際上將會在內存中從新分配一塊新的空間用於存放 24 這個值,而 myNumber
將會轉而指向這個新的內存空間的地址。ide
再看一個類型的例子:
let myString = 'abc' myString = myString + 'd'
JS 初學者可能會認爲,不管字符串 abc
存放在內存的哪一個地方,這個操做都會將字符 d
拼接在字符串後面。這種想法是錯誤的。別忘了,在 JS 中字符串也是基本類型。當 abc
與 d
拼接的時候,在內存中會從新分配一塊新的空間用於存放 abcd
這個字符串,而 myString
將會轉而指向這個新的內存空間的地址(同時,abc
依然位於原先的內存空間中)。
接下來咱們看一下基本類型的內存分配發生在哪裏。
簡單理解,能夠認爲 JS 的內存模型包含兩個不一樣的區域,一個是調用棧,一個是堆。
除了函數調用以外,調用棧同時也用於存放基本類型的數據。以上一小節的代碼爲例,在聲明變量後,調用棧能夠粗略表示以下圖:
在上面這張圖中,我對內存地址進行了抽象,以顯示每一個變量的值,但請記住,(正如以前所說的)變量始終指向某一塊保存着某個值的內存空間。這是理解 let vs const 這一小節的關鍵。
再來看一下堆。
堆是引用類型變量存放的地方。堆相對於棧的一個關鍵區別就在於,堆能夠存放動態增加的無序數據 —— 尤爲是數組和對象。
在變量聲明與賦值這方面,引用類型變量與基本類型變量的行爲表現有很大的差別。
咱們一樣從一個簡單的例子講起。下面聲明一個名爲 myArray
的變量並初始化爲一個空數組:
let myArray = []
當你聲明一個變量 myArray
並經過引用類型數據(好比 []
)爲它賦值的時候,在內存中的操做是這樣的:
myArray
)[]
)咱們能夠對 myArray
進行各類數組操做:
myArray.push("first") myArray.push("second") myArray.push("third") myArray.push("fourth") myArray.pop()
一般來說,咱們應該儘量多地使用 const
,而且只在肯定變量會改變以後才使用 let
。
重點來了,注意這裏的改變究竟指的是什麼意思。
不少人會錯誤地認爲,這裏的「改變」指的是值的改變,而且可能試圖用相似下面的代碼進行解釋:
let sum = 0 sum = 1 + 2 + 3 + 4 + 5 let numbers = [] numbers.push(1) numbers.push(2) numbers.push(3) numbers.push(4) numbers.push(5)
是的,用 let
聲明 sum
變量是正確的,畢竟 sum
變量的值確實會改變;不過,用 let
聲明 numbers
是錯誤的。而錯誤的根源在於,這些人認爲往數組中添加元素是在改變它的值。
所謂的「改變」,實際上指的是內存地址的改變。let
聲明的變量容許咱們修改內存地址,而 const
則不容許。
const importantID = 489 importantID = 100 // TypeError: Assignment to constant variable
咱們研究一下這裏爲何會報錯。
當聲明 importantID
變量以後,某一塊內存空間被分配出去,用於存放 489 這個值。牢記咱們以前所說的,變量 importantID
歷來只等於某一個內存地址。
當把 100 賦值給 importantID
的時候,因爲 100 是基本類型的值,內存中會分配一塊新的空間用於存放 100。以後,JS 試圖將這塊新空間的地址賦值給 importantID
,此時就會報錯。這其實正是咱們指望的結果,由於咱們根本就不想對這個很是重要的 ID 進行改動 .......
這樣就說得通了,用 let
聲明數組是錯誤的(不合適的),應該用 const
才行。這對初學者來講確實比較困惑,畢竟這徹底不符合直覺啊!初學者會認爲,既然是數組確定須要有所改動,而 const
聲明的常量明明是不可改動的啊,那爲什麼還要用 const
?不過,你必須得記住:所謂的「改變」指的是內存地址的改變。咱們再來深刻理解一下,爲何在這裏使用 const
徹底沒問題,而且絕對是更好的選擇。
const myArray = []
在聲明 myArray
以後,調用棧會分配一塊內存空間,它所存放的值是指向堆中某個被分配內存空間的地址。而堆中的這個空間纔是實際上存放空數組的地方。看下面的圖理解一下:
若是咱們進行這些操做:
myArray.push(1) myArray.push(2) myArray.push(3) myArray.push(4) myArray.push(5)
這將會往堆中的數組添加元素。不過,myArray
的內存地址但是至始至終都沒改變的。這也就解釋了爲何 myArray
是用 const
聲明的,可是對它(數組)的修改卻不會報錯。由於,myArray
始終等於內存地址 「0458AFCZX91」,該地址指向的空間存放着另外一個內存地址 「22VVCX011」,而這第二個地址指向的空間則真正存放着堆中的數組。
若是咱們這麼作,則會報錯:
myArray = 3
由於 3 是基本類型的值,這麼作會在內存中分配一塊新的空間用於存放 3,同時會修改 myArray
的值,使其等於這塊新空間的地址。而因爲 myArray
是用 const
聲明的,這樣修改就必然會報錯。
下面這樣作一樣會報錯:
myArray = ['a']
因爲 [‘a’]
是一個新的引用類型的數組,所以在棧中會分配一塊新的空間來存放堆中的某個空間地址,堆中這塊空間則用於存放[‘a’]
。以後咱們試圖把新的內存地址賦值給 myArray
,這樣顯然也是會報錯的。
對於用 const
聲明的對象,它和數組的表現也是同樣的。由於對象也是引用類型的數據,能夠添加鍵,更新值,諸如此類。
const myObj = {} myObj['newKey'] = 'someValue' // this will not throw an error
GitHub 和 Stack Overflow 年度開發者調查報告) 的相關數據顯示,JavaScript 是排名第一的語言。精通這門語言併成爲一名「JS 大師」多是咱們求之不得的。在任何一門像樣的 JS 課程或者一本書中,都會倡導咱們多使用 const
和 let
,少使用 var
,但他們基本上都沒有解釋這其中的原因。不少初學者會疑惑爲何有些用 const
聲明的變量在「修改」的時候確實會報錯,而有些變量卻不會。我可以理解,正是這種反直覺的體驗讓他們更喜歡隨處都使用 let
,畢竟誰也不想踩坑嘛。
不過,這並非咱們推薦的方式。Google 做爲一家擁有頂尖程序員的公司,它的 JavaScript 風格指南中就有這麼一段話:用 const
或者 let
聲明全部的局部變量。除非一個變量有從新賦值的須要,不然默認使用 const
進行聲明。毫不容許使用 var
關鍵字 (來源)。
雖然他們沒有指出箇中原因,不過我認爲有下面這些理由:
const
聲明的變量在聲明的時候就必須進行初始化,這會引導開發者關注這些變量在做用域中的表現,最終有助於促進更好的內存管理與性能表現。但願本文可以幫助你理解使用 const
或者 let
聲明變量的箇中原因以及應用場景。
參考:
目前專一於前端領域和交互設計領域的學習,熱愛分享和交流。感興趣的朋友能夠關注公衆號,一塊兒學習和進步。