对于深拷贝和浅拷贝,之前一直不怎么用到,所以一直也没太深入了解,22年的时候,被一个赋值问题教育了,这是那时候记录下的问题。
正文
面试回答
变量的每次声明都会开辟新的存储空间,js中为了找到对应的存储空间,需要指针来指向这个内容。
- 深拷贝,重新开辟一个存储空间,来存储你拷贝过来的内容
- 浅拷贝,简单的改一下引用指向,使新申明的内容指向原有的内容
- 深浅拷贝只针对对象和数组,无所谓好坏,看情况使用,一般是针对多层级的对象嵌套使用深拷贝,便于内容的修改
- 虽然有很多方法,但是个人一般使用自己写的js工具方法来处理这些小问题。
定义
在了解深浅拷贝之前,要先了解定义变量发生了什么。
我们每次申明变量,会在存储空间内分出来一块单位用来存储变量,而每个存储的变量,又会有个叫做指针的地址。
所以,我们在给其他变量赋值的时候,就会有个区分:是连着存储单位一块赋值的深拷贝,还是只给一个地址的浅拷贝。
深拷贝:开辟一块新的存储空间,存放原有的变量,并新开一个指针地址
浅拷贝:只拷贝指针地址,不另开辟存储空间
如果使用浅拷贝赋值,原有值会被修改
如果使用深拷贝赋值,原有值不会被修改
当然,有人可能会说我胡说,为什么string,boolean,null,undefined这些类型的值怎么没有改变原有值?
因为深复制和浅复制只针对象 Object对象、Array数组、RegExp对象(正则表达式)、Date时间对象、Function函数 这类复杂对象的。
简单来说,浅复制只复制一层对象的属性,而深复制则递归复制了所有层级。
所以,一般深浅拷贝,都是针对数组或对象这类数据进行操作的。
理解
如果已经理解上边的朋友,可以跳过这段,这是给一些新手朋友理解的。
举个不恰当的例子,声明变量,就像我开一家公司,我必然会有个地址,也必然会在现实有个场地办公司。
我如果要开子公司,子公司就相当于我声明的新变量,而我要把母公司挂名给子公司,就像是赋值这个过程。
但是开的子公司,我可以只挂名,不开新办公室,直接把新公司的地址指向母公司————这就类似于浅拷贝;
那么,与之相对的,如果子公司不仅挂名,而且新开了一间办公室,那么我的新公司的地址就指向了新的办公室————这就深拷贝。
深拷贝会有个新空间来存储赋值后的变量,而浅拷贝没有。
所以,我们继续用这个例子来讲解:
如果是深拷贝,我要修改新公司的布局,因为新公司指向的是新办公室,所以只会修改新办公室的布局,而母公司的办公室不会被修改;
但若是浅拷贝,我要修改新公司的布局,因为新公司指向的是老办公室,所以,原有办公室的布局会被修改;
深拷贝应用
浅拷贝没什么好说的,而深拷贝则需要一些特殊的方法赋值,以下记录一些常用的深拷贝使用手法。
1. 手动复制
1 | obj1 = {a:1,b:2} |
2. 对象只有一层的话。可以用object.assign({},obj1)
Es6的object.assign()是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身
所以只能实现一层基本类型属性的拷贝 当obj1中属性是引用类型时,就会发现,修改其中一个属性值,另一个值也发生变化。 如下所示:
1 | let obj1 = {a: 1, b: { c: 2}} |
3. 转成json再转换回来
用JSON.stringify转为字符串 再用JSON.parse把字符串再转为新的对象
obj2 = JSON.parse(JSON.stringify(obj1))
坏处: 摒弃了对象的constructor,不管原来的constructor是什么,拷贝后都是object. 只能处理可以被json直接表示的数据结构,number,string,array,扁平对象;
boolean RegExp对象,无法通过此方式深拷贝
4. 递归拷贝
递归深拷贝的实现
1 | deepCopy(obj) { |
缺点: 相互引用会出现死循环,深拷贝的做法是遇到对象就进行递归 复制,那么结果只能无限循环下去
5. object.create()方法
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。
也就是说,现有对象是新的对象的构造函数的prototype.其实现过程如下:
1 | function create(obj) { |
6. 直接使用一些库函数方法, 如lodash
1 | var _ = require('lodash') |
结语
深浅拷贝也算是经典面试题了,无论什么时候都会有人考。