一、区别
- 浅拷贝是指只复制对象的第一层,深处属性修改,会影响原来的对象
- 深拷贝是指对对象所有层级属性都进行复制
二、浅拷贝的实现
- Object.assign()
- lodash库里的clone()方法
- 展开运算符...
- 还有一些数组的方法,比如 Array.prototype.concat()、Array.prototype.slice()等
三、深拷贝的实现
1、JSON方法实现
先通过JSON.string()转为字符串,然后再用JSON.parse()转成对象
var obj = {
a: {
b: '1'
}
}
// 可以得到一个全新的拷贝的对象
JSON.parse(JSON.stringify(obj))
这种方法实现简单,但是存在一个问题:对象的属性只支持string、number、boolean、null、object、array,不支持其他类型,如果存在其他类型的属性,在序列化后会丢失属性或者被转为原始值
上面的对象,转换后,丢失了undefined和函数,日期对象被转换成了字符串格式的日期值。除此之外,正则对象、Symbol等也会被丢失。
其他的通过js原生方法实现的效果都和这个类似,只要涉及到序列化反序列化的过程,都存在同样的问题。
2、lodash库的 cloneDeep()
这个方法会返回一个全新的对象,完全拷贝原始对象的属性
const _ = require('lodash')
var obj = {
date: new Date(),
undef: undefined
}
var res = _.cloneDeep(obj)
// [object Date]
Object.prototype.toString.call(res.date)
// true
res.undef === undefined
3、structuredClone()
structuredClone() 是一个全局方法,可以复制对象
var res = structuredClone({date: new Date(), undef: undefined})
这个方法实现了对undefined、Date、RegExp等对象的拷贝,但是对Function、自定义类型、DOM节点、Error等还是无法拷贝,会抛出异常
4、手动实现深拷贝
思路:
- 如果是基础数据类型,则直接返回
- 如果是正则对象、日期对象,则调用相应的构造函数再构建一个
- 剩余对象,可以先获取该对象的构造函数,然后重新创建一个实例,并将原对象的自身的属性拷贝一份赋给新创建的实例
存在的问题与解决方案:
- 对象属性可能存在循环引用的问题,所以可以缓存一下所有创建过的对象,如果拷贝的时候发现对象已经被缓存过,可以直接返回缓存的值
function deepClone(obj) {
if(typeof obj !== 'object' || obj === null) {
return obj
}
if(obj instanceof RegExp) {
return new RegExp(obj)
}
if(obj instanceof Date) {
return new Date(obj)
}
// 剩下的对象,获取构造函数重新构建一个实例
if(map.has(obj)) {
return map.get(obj)
}
const target = new obj.constructor()
// 复制对象自身属性,深拷贝
Object.keys(obj).forEach((key) => {
target[key] = deepClone(obj[key])
})
map.set(obj, target)
return target
}