- 原文地址: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
声明变量的个中原因以及应用场景。
参考:
目前专一于前端领域和交互设计领域的学习,热爱分享和交流。感兴趣的朋友能够关注公众号,一块儿学习和进步。