在JavaScript中,它的内存分为三种类型:代码空间、栈空间、堆空间,其中代码空间用于存放可执行代码。
本文带大家来深入理解下栈空间与堆空间(堆内存与栈内存)
栈的认识
概念
栈也是一种数据呈线性排列的数据结构,
入栈
往栈中添加数据的操作就叫“入栈”,往栈中添加数据时,新数据被放在最上面。
出栈
从栈中取出数据的操作就叫“出栈”,从栈中取出数据时,会从最新的数据开始取。
优点
由于栈中存放数据的结构是后放进去的数据先取出来(后进先出),针对一些操作需要取最新数据时,选择栈作为数据结构是最合适的。
缺点
访问栈中的任意数据时,就需要从最新的数据开始取,效率较低。
栈内存空间
栈内存空间 就是用栈作为数据结构在内存中所申请的空间。
堆的认识
堆的特点
如图所示,每个节点由两个子节点,用线条连接即为堆。
- 结点内的数字就是存储的数据
- 堆中的每个结点最多有两个子节点
- 树的形状取决于数据的个数
- 节点的排列顺序为从上到下,同一行里则为从左到右
- 堆的父节点必须小于子结点
堆的数据存储
在堆中存储数据时必须遵守这样一条规则:子结点必定大于父节点
- 顶端的结点为根节点存储的数据为堆中的最小值
- 新数据增加时会被放在堆的最底部靠左的位置
- 堆的底部没有多余空间时,会另起一行把数据加在这一行的最左端
堆的数据获取
从堆中获取数据时,需要从最上面的数据开始取,取完数据后,堆需要进行重新排序,将最后的数据移到取出的结点位置。
堆内存空间
堆内存空间就是用堆作为数据结构在内存中所申请的空间。
通常情况下,堆数据结构指的是 二叉堆
二叉堆的特点:
- 它是一颗完全二叉树
- 二叉堆不是最小堆就是最大堆
我们画个图来描述下 最大堆 与 最小堆 ,如下所示:
变量类型与堆栈内存的关系
基本数据类型
我们知道JS的基本数据类型有7种:
-
string
-
number
-
boolean
-
null
-
undefined
-
symbol
-
bigInt
基本数据类型变量保存在栈内存中,因为基本数据类型占用空间小、大小固定,通过值来访问,属于被频繁使用的数据。
例如:
let name = "大白";
let age = 20;```
定义了2个变量:
- name为
string
类型 - age为
number
类型
引用数据类型
除了之前提到的基本数据类型外,其他的都属于引用数据类型,例如:Array
、Function
、Object
等。
引用数据类型存储在堆内存中,引用数据类型占据空间大、大小不固定,如果存储在栈中,将影响程序的运行性能。
引用数据类型会在栈中存储一个指针,这个指针指向堆内存空间中该实体的起始地址。
当解释器寻找引用值时,会先检索其在栈中的地址,取得地址后,从堆中获得实体。
例如:
let msgObj = {msg: "测试", id: 5};
let ages = [19, 22, 57]
对象和数组的指针存放在栈内存中,指针指向的数组存放在堆内存中
经典题目:
let a = {}, b = '0', c = 0;
a[b] = '珠峰';
a[c] = '培训';
console.log(a[b]);
注意:堆内存中,属性名是不可以重复的,且数字属性名0与字符串属性名’0’是一样的
let a={},b=Symbol('1'),c=Symbol('1');
a[b]='珠峰';
a[c]='培训';
console.log(a[b]);
对象的属性名不一定是字符串,又可能还是一个Symbol的值
变量复制
接下来,我们从内存角度来看下变量复制。
基本数据类型的复制
let name = "神奇的程序员";
let alias = name;
alias = "大白";
-
name
、alias
都是基本类型,它们的值存储在栈内存。 - 它们分别有各自独立的栈空间
- 因此,修改
alias
的值,name
不受影响
引用数据类型的复制
let book = {title:"书", id: 12}
let info = book;
info.title = "故事书";
console.log(book.title); // 故事书
-
info
、book
都是引用类型,它们的引用存在栈内存,值存在堆内存 - 它们的值指向同一块堆内存,栈内存中会复制一份相同的引用
深拷贝与浅拷贝
浅拷贝
引用数据类型在复制时,改了其中一个数据的值,另一个数据的值也会跟着改变,这种拷贝方式我们称为浅拷贝
深拷贝
实际上就是重新在堆内存中开辟一块新的空间,把原对象的数据拷贝到这个新地址空间里来,通常来说,我们有两种方法:
- 转一遍JSON再转回来 ,但是这个办法有一个问题,这只能转化一般常见数据,function,undefined等类型都无法通过这种变回来
const abc = { name: "大白" };
const cba = JSON.parse(JSON.stringify(a));
cba.age = 20;
console.log("abc = ", abc);
console.log("cba = ", cba);
//abc = {name: "大白"}
//obj = {name: "大白", age: 20}
- 手动去写循环遍历
const abc = [{ name: "大白" }];
let obj = abc.map(item => item);
obj.push({ name: "神奇的程序员" });
console.log("abc = ", abc);
console.log("obj = ", obj);