堆内存,栈内存
大家从一开始学习c/c++的时候对于声明变量就不陌生了,这同样也是行文的重要部分,JavaScript也同样,出于JS引擎的独特设计,在分配内存空间时会自动处理不同类型的变量的存储位置,给程序员减轻一些负担,但是同时也加大了一些学习成本,今天我们就来细说一下。先贴定义:
-
栈内存:引擎执行代码时工作的内存空间,除了引擎,也用来保存基本类型数据和引用类型的地址
-
堆内存:用来保存一组无序且唯一的引用类型值,可以使用栈中的键名来取得
显而易见的,堆内存和栈内存在存储的任务上并不相同,本文仅对于存储变量,常量的角度来进行探究,关于栈内存中提供JS执行的运行环境部分劳烦自行查找,或者查看我之前的博客。
栈内存中通常用来存储JS定义的基本类型变量,包括Number、String、Boolean、undefined、object、Null、Symbol 再加上对象变量的指针,这部分数据是直接以 变量名 => 变量数据 的方式存储在栈内存中的,每种类型的数据占用的内存空间的大小是确定的,并由系统自动分配和自动释放。这样带来的好处就是,内存可以及时得到回收,更加容易管理内存空间。而像function,Array,Object这样的庞大的数据内容显然不能放进栈内存中,因为这会涉及到经典的数据长度问题(思考一下,js在声明数组的时候并不要求你提前声明数组长度)栈内存中的变量一般都是已知大小或者有范围上限的,算作一种简单存储。实际上,这部分变量是放在堆内存中存储的,而每个变量所对应的这块空间有个唯一的指针也就是地址,被在栈内存中声明的变量所引用,指向了堆内存中的这块空间,所以function,Array,Object也被我们成为引用类型,如下图所示
Let 和 Const
从上面的存储模型中,我们引出了一个问题,也是日常开发时会经常遇到的:const声明的常量真的不可变吗?
虽说const(常量),let(变量)在声明的时候,已经决定了该数据的类型以及作用域,但是实际上,const和let仅仅是限制了栈内存中该数据的属性(例如let a = 1;那么在栈内存中存储的数据模型是:类型:变量,name:a,value:1,type:string「这里缺了张图,很早以前在js数据结构那里看到的,要补上」)由之前对存储模型的分析我们可以知道,实际上栈内存中存储的引用类型数据仅仅是指向堆内存中一块区域的地址,那么如果我直接去修改堆内存中的数据,并不与const所限制的常量中的所有属性所冲突,也就绕过了let和const对变量属性的限制,大家可以尝试下面的代码
const a = [
{
name: '123'
},
{
name: '456'
}
];
a.push({name: '789'});
console.log(a) // [ { name: '123' }, { name: '456' }, { name: '789' } ]
你会惊奇的发现,即便是定义了a为常量, { name:"789" } 这个对象还是被push进了a的尾部
深拷贝,浅拷贝
通过了解上面关于JS的存储方式,你可能会想起日常开发中的很多问题,例如为什么把一个对象赋值给另一个空对象,一个对象数据改变之后两个都会变,也就是浅拷贝。实际上我们通过JS的存储模式可以了解到,当JS运行赋值语句,在声明以及初始化一个新引用类型变量时,会把引用类型的地址传递给新的对象,这样的确做到了最小的内存开销以及最快的系统处理速度,但是问题也是显而易见的,多数时候我们重复赋值一个对象是想分别处理两者的值,而不是把一个变量单纯的复制一份,这就要求开发者手动进行深拷贝。
所谓深拷贝,实际上就是重新在堆内存中开辟一块新的空间,把原对象的数据拷贝到这个新地址空间里来,通常来说,我们有两种方法
第一种也就是最偷懒的方法,转一遍JSON再转回来 ,但是这个办法有一个问题,这只能转化一般常见数据,function,undefined等类型都无法通过这种变回来
JSON.parse(JSON.stringify(data))
第二种方法就是手动去写循环遍历,这里大家立刻就能想到经常用的 map(), forEach() 方法,实际上二者的语法上是一样的,但是实际执行的操作有差别,map(), 会返回遍历的每一项,而forEach()返回undefined,引用阮一峰的说法,forEach更像是立正报数,而map是工厂生产复制人。我们在实际做深拷贝的时候用map可读性更高,对原数组每一项或者按某些规则做操作的时候forEach就显得更方便(例如每一项+1),下面是深拷贝的对照,下面的操作就完成了一个对数组a的深拷贝
const a = [
{
name: '123'
},
{
name: '456'
},
{
name: '789'
}
];
let b = a.map(item => item)
let c = a;
console.log(b === a, c === a); // false true
本文参考:
https://www.imooc.com/article/284409
https://www.cnblogs.com/heioray/p/9487093.html
https://blog.csdn.net/tzllxya/article/details/93507899