实现深拷贝和浅拷贝
参考资料
前言
js中的数据类型就只有string、number、boolean、object、null、undefined这6种。而浅拷贝和深拷贝只是针对object而言的,其他5种不存在浅拷贝和深拷贝的问题。
一个object其实可以看做是一颗树,树的叶子节点的数据类型是string、number、boolean、null、undefined这5种,而中间节点的数据类型就是object。所谓拷贝,就是生成这样一棵树的副本。深拷贝就是在拷贝的时候遍历到所有的叶子节点,而浅拷贝则遍历到某一层的中间节点就结束了。另外,深拷贝在复制中间节点(即object对象)的时候,不是直接把对方的中间节点拿过来用,而是自己创建一个新对象,否则就是浅拷贝了。
对象的浅拷贝和深拷贝
测试代码
/**
* 测试copy是否是浅拷贝函数
*/
function testObjectShallowCopy(copy) {
const desObj = copy(resObj)
return !isObjectDeepCopy(resObj, desObj)
}
/**
* 测试copy是否是深拷贝函数
*/
function testObjectDeepCopy(copy) {
const desObj = copy(resObj)
return isObjectDeepCopy(resObj, desObj)
}
// 被拷贝的对象
const resObj = {
l1: 'l1',
l2: {
l21: 'l21',
l22: 'l22',
}
}
/**
* 判断是否是深拷贝
* @param {*} obj1
* @param {*} obj2
*/
function isObjectDeepCopy(obj1, obj2) {
// 参数检查
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
throw 'obj1和obj2都必须是对象'
}
// 如果两者都是null,则直接返回true
if (obj1 === null && obj2 === null) {
return true
}
// 如果其中一个是null,另一个不是null,代表拷贝出问题,返回false
if (!(obj1 === null) === (obj2 === null)) {
return false
}
// 如果两者相等,代表是浅拷贝,返回false
if (obj1 === obj2) {
return false
}
// 遍历比较两者的第一层属性
for (let key in obj1) {
// 如果两者都是对象,且isObjectDeepCopy()返回false,则返回false
if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object' && isObjectDeepCopy(obj1[key], obj2[key]) === false) {
return false
}
// 如果两者不是对象,且不相等,代表拷贝出问题,返回false
if (obj1[key] !== obj2[key]) {
return false
}
}
// 考虑到obj2的属性可能比obj1的属性多,因此最后还要比一下序列化值
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
return false
}
return true
}
实现浅拷贝
1)直接赋值
testObjectShallowCopy(function(resObj) {
const desObj = resObj
return desObj
},) // true
2)手动遍历
testObjectShallowCopy(function(resObj) {
const desObj = {}
for (let key in resObj) { // 只遍历第一层
desObj[key] = resObj[key]
}
return desObj
},) // true
3)Object.assign()
testObjectShallowCopy(function(resObj) {
const desObj = Object.assign(resObj)
return desObj
},) // true
实现深拷贝
1)递归遍历
function deepCopy(resObj) {
const desObj = {}
for (let key in resObj) {
if (typeof resObj[key] === 'object' && resObj[key] !== null) {
desObj[key] = deepCopy(resObj[key])
} else {
desObj[key] = resObj[key]
}
}
return desObj
}
testObjectDeepCopy(deepCopy) // true
2)用JSON
testObjectDeepCopy(function(resObj) {
const desObj = JSON.parse(JSON.stringify(resObj))
return desObj
}) // true
3)使用Object.create()
testObjectDeepCopy(function(resObj) {
const desObj = Object.create(resObj)
return desObj
}) // true
数组的浅拷贝和深拷贝
测试代码
/**
* 测试copy是否是浅拷贝函数
*/
function testArrayShallowCopy(copy) {
const desArr = copy(resArr)
return !isArrayDeepCopy(resArr, desArr)
}
/**
* 测试copy是否是深拷贝函数
*/
function testArrayDeepCopy(copy) {
const desArr = copy(resArr)
console.log(isArrayDeepCopy(resArr, desArr));
console.log(resArr);
console.log(desArr);
return isArrayDeepCopy(resArr, desArr)
}
// 被拷贝的数组
const resArr = [1, [21, [221, [2221]]]]
/**
* 判断是否是深拷贝
* @param {*} arr1
* @param {*} arr2
*/
function isArrayDeepCopy(arr1, arr2) {
// 参数检查
if (!(arr1 instanceof Array && arr2 instanceof Array)) {
throw 'arr1和arr2都必须是数组'
}
// 如果两个数组相等,代表是浅拷贝,返回false
if (arr1 === arr2) {
return false
}
// 遍历比较两者的第一层属性
for (let index in arr1) {
// 如果两者都是数组
if (arr1[index] instanceof Array && arr2[index] instanceof Array) {
// 如果isArrayDeepCopy返回false,则返回false
if (isArrayDeepCopy(arr1[index], arr2[index]) === false) {
return false
}
}
// 如果两者都是对象,且isObjectDeepCopy返回false,则返回false
else if (typeof arr1[index] === 'object' && typeof arr2[index] === 'object' && isObjectDeepCopy(arr1[index], arr2[index]) === false) {
return false
}
// 如果两者不相等,代表拷贝出问题,返回false
else if (arr1[index] !== arr2[index]) {
return false
}
// 如果两者相等,则放行
}
// 考虑到arr2的元素数量可能比arr1多,因此最后还要比一下序列化值
if (JSON.stringify(arr1) !== JSON.stringify(arr2)) {
return false
}
return true
}
实现浅拷贝
和对象的浅拷贝类似,不赘述。
另外要注意的是,concat()对那些元素中不存在对象或数组的数组来说,也可以实现深拷贝。
实现深拷贝
1)用slice()
testArrayDeepCopy(function(resArr) {
const desArr = resArr.slice(0)
return desArr
}) // true
2)用JSON
同对象使用JSON进行深拷贝。
最后
如参考资料1中作者表达的意思,一些浅拷贝方法对于没有嵌套结构(即只有一层,该层的所有属性或元素都是基本元素类型)的对象和数组来说,也可以是深拷贝。因此深拷贝的实现方式并没那么绝对。但如果要写出一个普适性的深拷贝函数,那么得考虑到对象和数组有嵌套结构。