前言
一年前刚毕业面试的时候,有次聊到ES6新特性,我首先提到的是新的变量声明:有自带块级作用域的let,还有一个是定义常量的const。
当时关于常量我是这样解释的:所谓常量即只能读取不能编辑(删除,修改)的变量。
面试官随即问到如果定义的常量是一个obj,那么也无法修改吗?我脱口而出的是可以修改,但是解释起来却卡了壳,浅浅地说了说JS中的基本类型和引用类型,现在想来确实有些惭愧。
基本类型&引用类型
JS中的数据类型大致分为两种,
基本类型包括:
· String
· Number
· Boolean
· undefined
· null
· Symbol
他们的值是一些简单的数据段
引用类型包括:
· Object
· Array
· Date
· Function
· RegExp
他们的值是对象
既然基本类型和引用类型的值有区别,那么他们的储存方式有区别吗?答案当然是肯定的
JS中的栈内存与堆内存
在JS中,栈内存用来存储所有基本类型的变量以及引用类型变量的指针,而堆内存用来存储引用类型变量的值
由于两种数据类型的存储方式不同,对变量的复制引用就出现了深拷贝和浅拷贝两种不同的方式
浅拷贝&深拷贝
对于基本类型来说并不区分深浅拷贝,例如:
let a=1;
let b=a;
b=b+1;
console.log(a,b);//1,2
因为对基本类型进行拷贝时,栈内存会开辟一个空间保存新的变量和变量值,所以改变新变量的值对旧变量没有影响,如下图所示:
改变b的值对a的值无影响~
对于引用类型来说,直接用=号赋值属于浅拷贝,只会拷贝对象的指针,实际指向的是堆内存中的同一object,所以改变新值时旧值会同步改变:
改变obj2的值,obj1也被改变了,原因就是浅拷贝只拷贝了引用类型的指针:
那么我们不想复制出来的变量与原变量还有那么深的“羁绊”,应该怎样做呢?
利用遍历进行深拷贝
通过上面的代码,我们似乎觉得问题已经解决了,很简单嘛,通过遍历每一项分别进行赋值就好了呀,可是仔细想来,上面的数组color1,它的每一项还是String类型,如果有的项是引用类型呢?我们很自然地想到了递归,于是有了下面的深拷贝方法(仅提供思路,不足之处还望指正):
我们通过深拷贝获得的变量在内存中的关系如下图所示:
如此一来,修改obj2就不会对obj1的值造成影响
通过JSON序列化进行深拷贝
通过JSON.parse和JSON.stringify可以很轻松的实现引用类型的深拷贝(不能拷贝function、RegExp等类型)
总结
浅拷贝:只拷贝对象的引用,而不深层次的拷贝对象的值,多个指针指向堆内存中的同一对象,任何一个修改都会使得所有对象的值修改,因为它们共用一条数据
深拷贝:深拷贝只作用在引用类型上,例如:Object,Array
深拷贝不会拷贝引用类型的指针,而是将引用类型的值全部拷贝一份,形成一个新的引用类型保存在堆内存中并形成新的指针保存在栈内存中,这样就不会发生修改新数据原数据一同修改,从而错乱的问题