数据类型和存储方式
JavaScript的数据类型分为基本数据类型和引用数据类型。
基本数据类型:number,string,boolean,null,undefined,symbol
引用数据类型:统称为Object类型,细分的话,有:Object,Array,Date,Function等。
基本数据类型保存在栈内存,栈内存中分别存储着变量的标识符以及变量的值。
1 | let a = 1; |
引用数据类型保存在栈内存,变量名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值。
1 | let obj = { |
所以这两种类型,赋值的时候就能体现出不同之处了:
1 | let b = a; // 重新开辟了一个空间存储b,重新开辟了一个空间存储b的值,也就是1 |
所以我们现在得出,obj存的xxx指向的值发生变化的时候,xxx是不变的,此时通过obj2去访问属性,也是会变化的。
浅拷贝和深拷贝
浅拷贝:创建一个新的数据,这个数据有着原始数据属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个数据改变了这个地址,就会影响到另一个数据。
深拷贝:深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。在堆中重新分配内存,拥有不同的地址,且值是一样的,复制后的对象与原来的对象是完全隔离,互不影响。
深拷贝的实现方式
- 先 stringify 再 parse
缺点:忽略了undefined、symbol和函数;对象循环引用时,会报错;
- Object#assign 扩展运算符
缺点:只能实现一层深拷贝
- jQuery的 $.extends 方法
缺点:需要引用第三方库
- 使用递归的方式,遍历对象中的属性
step1: 遍历对象中的属性,先创建一个新的对象。遍历属性的时候,如果这个属性值是一个对象,那么我们需要把这个属性值再次做拷贝操作。如果属性值是基本类型,就直接赋值给前面创建的新对象。
1 | function deepClone(obj){ |
Map数据结构,允许字符串以外的类型的数据作为Map的key,并且这个key是唯一的。
1 | var myMap = new Map(); |
step3: 对于一些不可继续拷贝的对象,例如函数、Date()等,我们需要判断具体的类型,使用Object#toString
方法。
小结一下
实现深拷贝的方式,上面说了很多种,有些可能是项目中使用的,有些是比较完整,但是项目中为了图省事没有这么写的。这里注意的是,如果我们是在面试的过程中被问到了这个问题,我们需要回答比较完整的那种方案,可以循循善诱,先把其他几种简单的说一下,然后说出这些方式的缺点,最后为了解决这些缺点,我们使用了XXX方案,这样才是面试官希望听到的答案。
回答要点:
step1: 其他几种方式极其缺点
step2: 描述递归:先判断目标的类型是否是可拷贝的值,然后创建一个新的空对象,再进行遍历属性,如果属性值也是一个可拷贝的值,就继续调用这个拷贝函数,如果不是,就直接返回这个值。然后可以解释一下什么叫做可拷贝的值,以及如何判断该值是可拷贝的值。
step3: 解决循环引用的问题,我们需要一个新的空间来存储已经被拷贝过的对象,每次在遍历对象之前,先去这个空间找一下该对象是否曾经被拷贝过。