前言
JavaScript 中的数据是如何存储在内存中的。虽然 JavaScript 并不需要直接去管理内存,但是在实际项目中为了能避开一些不必要的坑,你还是需要了解数据在内存中的存储方式。
示例
首先,我们先看下面这两段代码:
function foo() {
var a = 1
var b = a
a = 2
console.log(a)
console.log(b)
}
foo()
执行上面这段代码,打印出来 a
的值是 2
,b
的值是 1
,这没什么难理解的。
function foo() {
var a = {name:"极客时间"}
var b = a
a.name = "极客邦"
console.log(a)
console.log(b)
}
foo()
执行第二段代码,你会发现,仅仅改变了 a
中 name
的属性值,但是最终 a
和 b
打印出来的值都是 {name:"极客邦"}
。这就和我们预期的不一致了,因为我们想改变的仅仅是 a
的内容,但 b
的内容也同时被改变了。
要彻底弄清楚这个问题,我们就得先从“JavaScript 是什么类型的语言”讲起。
JavaScript 是什么类型的语言?
支持隐式类型转换的语言称为 弱类型语言,不支持隐式类型转换的语言称为 强类型语言。在这点上,C 和 JavaScript 都是弱类型语言。
- 弱类型,意味着你不需要告诉 JavaScript 引擎这个或那个变量是什么数据类型,JavaScript 引擎在运行代码的时候自己会计算出来。
- 动态,意味着你可以使用同一个变量保存不同类型的数据。
JavaScript 数据类型包括原始类型和引用类型,之所以把它们区分为两种不同的类型,是因为它们在内存中存放的位置不一样。到底怎么个不一样法呢?接下来,我们就来讲解一下 JavaScript 的原始类型和引用类型到底是怎么储存的。
内存空间
在 JavaScript 的执行过程中, 主要有三种类型内存空间,分别是代码空间、栈空间和堆空间。其中的代码空间主要是存储可执行代码的,本文主要来说说栈空间和堆空间。
栈空间和堆空间
栈空间是用来存储执行上下文的。为了搞清楚栈空间是如何存储数据的,我们还是先看下面这段代码:
function foo() {
var a = "极客时间"
var b = a
var c = {name:"极客时间"}
var d = c
}
foo()
当 JavaScript 执行一段代码时,需要先编译,并创建执行上下文,然后再按照顺序执行代码。那么下面我们来看看,当执行到第 3 行代码时,其调用栈的状态,你可以参考下面这张调用栈状态图:
从图中可以看出来,当执行到第 3 行时,变量 a 和变量 b 的值都被保存在执行上下文中,而执行上下文又被压入到栈中,所以你也可以认为变量 a 和变量 b 的值都是存放在栈中的。
接下来执行第 4 行代码,由于 JavaScript 引擎判断右边的值是一个引用类型,这时候处理的情况就不一样了,JavaScript 引擎并不是直接将该对象存放到变量环境中,而是将它分配到堆空间里面,分配后该对象会有一个在“堆”中的地址,然后再将该数据的地址写进 c 的变量值,最终分配好内存的示意图如下所示:
从上图你可以清晰地观察到,对象类型是存放在堆空间的,在栈空间中只是保留了对象的引用地址,当 JavaScript 需要访问该数据的时候,是通过栈中的引用地址来访问的。
通常情况下,栈空间都不会设置太大,主要用来存放一些原始类型的小数据。而引用类型的数据占用的空间都比较大,所以这一类数据会被存放到堆中,堆空间很大,能存放很多大的数据,不过缺点是分配内存和回收内存都会占用一定的时间。
我们还是回到示例代码那里,看看它最后一步将变量 c
赋值给变量 d
是怎么执行的?
在 JavaScript 中,原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址。所以 d = c
的操作就是把 c
的引用地址赋值给 d
,你可以参考下图:
从图中你可以看到,变量 c
和变量 d
都指向了同一个堆中的对象,所以这就很好地解释了文章开头的那个问题,通过 a
修改 name
的值,变量 b
的值也跟着改变,归根结底它们是同一个对象。
最后
以上就是 JavaScript 中数据的存储方式,如果觉得对你有帮助的话,不要忘了点赞哟~