likes
comments
collection
share

[译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型

作者站长头像
站长
· 阅读数 6

本文翻译自:JavaScript’s Memory Model

感谢原作者创作了这么好的文章

作为开发者,我们每天都在声明变量,初始化变量,并在以后分配新值。

// declare some variables and initialize them
var a = 5let b = 'xy'
const c = true// assign new values
a = 6
b = b + 'z'
c = false // TypeError: Assignment to constant variable

但是,
实际上
这样做的时候会发生什么?
特别是 JavaScript 如何在内部处理此类基本功能?
更重要的是,作为开发者,了解 JavaScript 的基本细节有什么好处?

下面,我打算介绍以下内容

  1. JS 原始数据类型的变量声明和赋值
  2. JavaScript 的内存模型:调用堆栈和堆
  3. JS 引用数据类型的变量声明和赋值
  4. Let 和 const

JS原始数据类型的变量声明和赋值

让我们从一个简单的例子开始。
下面,我们声明一个名为 myNumber 的变量,并将其初始化为 23 。

let myNumber = 23

执行此代码后,JS 将…

  1. 为变量创建一个唯一标识符(“myNumber”)。
  2. 在内存中分配一个地址(将在运行时分配)。
  3. 在分配的地址中存储一个值(23)。
 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型

尽管我们通俗地说 “ myNumber 等于 23”,但从程序上讲,myNumber 等于 保存值 23 的内存地址。这是要理解的关键区别。

如果我们要创建一个名为 “ newVar” 的新变量并为其分配 “ myNumber”…

let newVar = myNumber

… 由于 myNumber 在程序上讲等于“0012CCGWH80”,因此 newVar 也等于“0012CCGWH80”,这是保存值 23 的内存地址。最终,这等同于说:“ newVar现在等于23”

 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型
由于 myNumber等于内存地址 “0012CCGWH80”,因此将其分配给 newVar 会将“0012CCGWH80”分配给 newVar

现在,如果我这样做,会发生什么:

myNumber = myNumber + 1

“myNumber” 的值肯定为24。但是newVar指向相同的内存地址时,其值也会为24吗?

答案是:
由于JS 中的原始数据类型是不可变的,因此当 “myNumber + 1” 解析为 “24” 时,JS将在内存中分配一个新地址,并将其值存储为 24,而 “myNumber” 将指向该新地址。

 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型

这是另一个例子:

let myString = 'abc'myString = myString + 'd'

JS新手可能会说,只要在内存中的任何位置,字母d都简单地附加在字符串abc后面,但这在程序角度上是错误的。

当 “abc” 与 “d” 串联时,由于字符串也是 JS 中的原始数据类型,因此会分配一个新的内存地址,在其中存储 “abcd”,而 “myString” 指向此新的内存地址。

 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型

下一步将会了解对于原始数据类型内存分配在哪发生。

JavaScript的内存模型:调用堆栈和堆

就本博客而言,可以将JS内存模型理解为具有两个不同的区域:调用堆栈和堆。
 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型

调用堆栈是存储原始数据类型的地方
在上一节中声明变量之后,下面是调用堆栈的粗略表示。

 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型

在下面的插图中,我剔除了内存地址以显示每个变量的值。
但是,请不要忘记实际上该变量指向一个内存地址,然后该内存地址保存一个值。
这是理解 letconst 的关键。

那么堆呢。

堆是存储引用数据类型的位置。
关键区别在于堆可以存储可以动态增长的无序数据,这对于数组和对象来说是完美的。

JS引用数据类型的变量声明和赋值

与原始JS数据类型相比,非原始JS数据类型的行为有所不同。

让我们从一个简单的例子开始。
在下面,我们声明一个名为 “myArray” 的变量,并使用一个空数组对其进行初始化。

let myArray = []

当您声明变量 “myArray” 并为其分配非原始数据类型(如“ []”)时,这将在内存中发生:

  1. 为变量创建一个唯一标识符(“ myArray”)。
  2. 在内存中分配一个地址(将在运行时分配)。
  3. 存储在堆上分配的内存地址的值(将在运行时分配)。
  4. 堆上的内存地址存储分配的值(空数组[])。
 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型
 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型

从这里,我们可以调用 push

pop 或做任何我们想做的事情。

myArray.push("first")myArray.push("second")myArray.push("third")myArray.push("fourth")myArray.pop()
 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型

let  vs. const

通常,我们应该尽可能多地使用 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” ,因为他们知道值会改变。使用 let 
声明了 numbers,调用 push 更改其值。

现在我们来解释一下 “更改” 的正确含义

let

您可以更改内存地址。

const

不允许您更改内存地址。

const importantID = 489importantID = 100 // TypeError: Assignment to constant variable

让我们想象一下这里发生的事情。

声明 “mportantID” 时,将分配一个内存地址,并存储值 489。
请记住,将变量 “importantID” 视为等于内存地址。

 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型

当将 100 分配给 “importantID” 时,由于 100 是基本数据类型,因此会分配一个新的内存地址,并将 100 的值存储在那里。
然后,JS尝试将新的内存地址分配给 “importantID”,这就是引发错误的地方。

 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型
当将 100 分配给 ImportantID 时,实际上是在尝试分配存储 100 的新内存地址。
这是不允许的,因为 ImportantID 是用 const 声明的。

如上所述,假设的 JS 新手使用 let 错误地声明了它们的数组
相反,他们应该使用 const 声明它
一开始这似乎令人困惑。
我承认,这一点都不直观。
初学者会认为该数组仅对我们有用,如果我们可以对其进行更改,而 const 使该数组不可更改
,那么为什么要使用它呢?
但是,请记住:“更改”是由内存地址定义的。
让我们更深入地探讨为什么它完全可以并且首选使用const声明数组的原因。

const myArray = []

声明myArray时,将在调用堆栈上分配一个内存地址,该值是在堆上分配的内存地址。
存储在堆中的值是实际的空数组。
可视化,看起来像这样:

 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型
 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型

如果我们要这样做...

myArray.push(1)
myArray.push(2)
myArray.push(3)
myArray.push(4)
myArray.push(5)
 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型

这会将数字推入堆中存在的数组。但是,“myArray”的内存地址未更改。这就是为什么尽管使用常量声明了 “myArray”,却未引发任何错误。“myArray” 仍等于 “0458AFCZX91”,其值为另一个内存地址 “22VVCX011”,其值为堆上数组的值。

如果执行以下操作,将引发错误:

myArray = 3

由于 3 是基本数据类型,因此将在调用堆栈上分配一个内存地址,并存储值 3,然后我们将尝试将新的内存地址分配给 myArray。
但是由于 myArray 是用 const 声明的,因此不允许这样做。

 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型

另一个将引发错误的示例:

myArray = ['a']

由于 ['a'] 是一个新的非原始数组,因此将在调用堆栈上分配一个新的内存地址,并存储堆上一个内存地址的值,存储在堆内存地址上的值将为['一种']。
然后,我们将尝试将调用堆栈内存地址分配给 myArray,这将引发错误。

 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型

对于用 const 声明的对象
(如数组),由于对象不是基本数据类型,因此您可以添加键,更新值等等。

const myObj = {} 
myObj['newKey'] ='someValue'//这不会引发错误

为什么这对我们有用

JavaScript 是全球排名第一的编程语言(根据 GitHub 和 
Stack Overflow 的年度开发人员调查
)。
发展掌握能力并成为 “JS Ninja” 是我们所有人都渴望成为的。任何体面的 JS 课程或书籍倡导const 让 va r放弃,但他们并不一定要说为什么。对于某些初学者而言,为什么某些 const
变量在 “更改” 其值时抛出错误而其他变量则没有,这是不直观的
对于我来说,为什么这些程序员随后默认使用在各处使用 let 以避免麻烦是很有意义的。

但是,不建议这样做。
拥有世界上最好的编码器的 Google 在其 JavaScript 编程风格指南中说:“使用constlet声明所有局部变量默认情况下使用const,除非需要重新分配变量。VAR
关键字不推荐使用”()。

尽管据我所知,他们没有明确说明原因,但有几个原因:

  1. 太过自由带来 bug。

  2. 用 const 声明的变量必须在声明时进行初始化,这迫使编码人员经常在范围方面考虑周全。这最终导致更好的内存管理和性能。
  3. 别人通过您的代码就像和你交流一样,哪些变量是不可变的,哪些变量可以重新分配。

我希望以上解释能帮助您开始理解:

const 或 Let

参考文献

  1. Google JS样式指南
  2. 学习JavaScript:通过共享调用,参数传递

最后

最近我录了几个大厂的笔试真题讲解,准备面试的可以关注一下:

哔哩哔哩2020校园招聘前端笔试卷(一):www.bilibili.com/video/av834…

哔哩哔哩2020校园招聘技术类笔试卷(二):www.bilibili.com/video/av834…

小米2019秋招前端开发笔试题(B):www.bilibili.com/video/av834…

阿里巴巴2017秋招前端笔试题(上篇):www.bilibili.com/video/av834…

如果喜欢我的文章,可以关注我的的微信公众号。

 [译]助力年后跳槽涨薪,深入理解 JavaScript 内存模型