变量类型与堆栈内存的关系
基本数据类型
我们知道JS的基本数据类型有7种:
string
number
boolean
null
undefined
symbol
bigInt
基本数据类型变量保存在栈内存中,因为基本数据类型占用空间小、大小固定,通过值来访问,属于被频繁使用的数据。
接下来,我们通过一个例子来讲解下,基本数据类型在栈内存中的存储:
let name = "大白"; let age = 20;
上述代码中,我们定义了2个变量:
- name为
string
类型 - age为
number
类型
我们画个图来描述下它在栈内存的存储:
image-20210323152445985
注意⚠️:闭包中的基本数据类型变量是保存在堆内存里的,当函数执行完弹出调用栈后,返回一个内部函数的一个引用,这时候函数的变量就会转移到堆上,因此内部函数依然能访问到上一层函数的变量。
引用数据类型
除了上个章节提到的基本数据类型外,其他的都属于引用数据类型,例如:Array
、Function
、Object
等。
引用数据类型存储在堆内存中,引用数据类型占据空间大、大小不固定,如果存储在栈中,将影响程序的运行性能。
引用数据类型会在栈中存储一个指针,这个指针指向堆内存空间中该实体的起始地址。
当解释器寻找引用值时,会先检索其在栈中的地址,取得地址后,从堆中获得实体。
我们举个例子来描述下上述话语:
// 基本数据类型-栈内存 let name = "大白"; // 基本数据类型-栈内存 let age = 20; // 基本数据类型-栈内存 let info = null; // 对象指针存放在栈内存中,指针指向的对象放在堆内存中 let msgObj = {msg: "测试", id: 5}; // 数组的指针存放在栈内存中,指针指向的数组存放在堆内存中 let ages = [19, 22, 57]
上述代码中:
- 我们创建了两个变量
msgObj
、ages
,他们的值都是引用类型(object、array) - 堆内存空间采用
二叉堆
作为数据结构,msgObj
与ages
的具体值会存在堆内存空间中 - 存储完成后,堆内存空间会返回这两个值的引用地址(指针)
- 拿到引用地址后,这个引用地址会和它的变量名对应起来,存放在栈内存空间中
- 在查找变量
msgObj
与ages
的具体值时,会先从栈内存空间中获取它的引用地址 - 获取到引用地址后,通过引用地址在堆内存空间的二叉堆中查找到对应的值。
我们画个图来描述下上述话语,如下所示:
堆内存空间中的Object
,表示的是存储在空间中的其他对象的引用值。
image-20210323170843691
我们来理解下堆内存空间与堆内存的区别:
堆内存空间:相当于一个采用二叉堆作为数据结构的容器。
堆内存:指的是一个引用类型的具体值。
堆内存存在于堆内存空间中。
变量复制
接下来,我们从内存角度来看下变量复制。
基本数据类型的复制
我们通过一个例子来看下基本类型的复制,代码如下所示:
let name = "神奇的程序员"; let alias = name; alias = "大白";
上述代码中:
name
、alias
都是基本类型,它们的值存储在栈内存。- 它们分别有各自独立的栈空间
- 因此,修改
alias
的值,name
不受影响
我们画个图来描述下:
image-20210323203531067
引用数据类型的复制
接下来,我们通过一个例子来看下引用类型的复制,代码如下所示:
let book = {title:"书", id: 12} let info = book; info.title = "故事书"; console.log(book.title); // 故事书
上述代码中:
info
、book
都是引用类型,它们的引用存在栈内存,值存在堆内存- 它们的值指向同一块堆内存,栈内存中会复制一份相同的引用
我们画个图来描述下:
image-20210323213720148
深拷贝与浅拷贝
通过上述章节的学习,我们了解到引用数据类型在复制时,改了其中一个数据的值,另一个数据的值也会跟着改变,这种拷贝方式我们称为浅拷贝。
在实际开发中,我们希望引用类型复制到新的变量后,二者是独立的,不会因为一个的改变而影响到另一个。这种拷贝方式就称为深拷贝。
深拷贝,实际上就是重新在堆内存中开辟一块新的空间,把原对象的数据拷贝到这个新地址空间里来,通常来说,我们有两种方法:
- 转一遍JSON再转回来 ,但是这个办法有一个问题,这只能转化一般常见数据,function,undefined等类型都无法通过这种变回来
- 手动去写循环遍历
我们来看下第一种方法,代码如下所示:
const data = { name: "大白" }; const obj = JSON.parse(JSON.stringify(data)); obj.age = 20; console.log("data = ", data); console.log("obj = ", obj);
运行结果如下:
image-20210323233500632
最后,我们来看下第二种写法,代码如下所示:
const data = [{ name: "大白" }]; let obj = data.map(item => item); obj.push({ name: "神奇的程序员" }); console.log("data = ", data); console.log("obj = ", obj);
运行结果如下:
image-20210323234043404
代码地址
本文为《JS原理学习》系列的第4篇文章,本系列的完整路线请移步:JS原理学习 (1) 》学习路线规划
本系列文章的所有示例代码,请移步:js-learning
写在最后
至此,文章就分享完毕了。
我是神奇的程序员,一位前端开发工程师。
如果你对我感兴趣,请移步我的个人网站,进一步了解。
- 公众号无法外链,如果文中有链接,可点击下方阅读原文查看😊