js 数组深度拷贝详解
1.我们已经知道的深拷贝和浅拷贝的区别,在于,深拷贝是拷贝值的同时拥有一个新的存储地址,而浅拷贝只是拷贝了值,而存储地址不变;这样会导致的问题是修改拷贝的值,会同时修改原数组;
但是你所知道深拷贝真的就是深拷贝吗?
浅拷贝示例:
var arr = [1,2,3,4]
let newArr = arr //浅拷贝
newArr.splice(0,1)
console.log(arr,'原数组'); //[2, 3, 4] "原数组"
console.log(newArr,'浅拷贝数组') //[2, 3, 4] "拷贝数组"
深拷贝示例:
第一种 利用数组api slice 和 map 返回一新数组的特性
var arr = [1,2,3,4]
let newArr = arr.slice(0) //深拷贝
newArr.splice(0,1)
console.log(arr,'原数组'); //[1, 2, 3, 4] "原数组"
console.log(newArr,'深拷贝数组') //[2, 3, 4] "拷贝数组"
//map
let newArr2 = arr.map(item=>{
return item
})
newArr2.splice(0,1)
console.log(arr,'原数组'); //[1, 2, 3, 4] "原数组"
console.log(newArr2,'深拷贝数组') //[2, 3, 4] "拷贝数组"
第二种 创建新数组存储
var arr = [1,2,3,4]
let newArr3 = []
for(var i=0 ;i<arr.length;i++){
newArr3.push(arr[i])
}
newArr3.splice(0,1)
console.log(arr,'原数组'); //[1, 2, 3, 4] "原数组"
console.log(newArr3,'拷贝数组') //[ 2, 3, 4] "拷贝数组"
第三种 利用 JSON.stringify() ,JSON.parse() 之间的转换
var arr = [1,2,3,4]
let newArr4 = JSON.parse(JSON.stringify(arr))
newArr4.splice(0,1)
console.log(arr,'原数组'); //[1, 2, 3, 4] "原数组"
console.log(newArr4,'拷贝数组') //[ 2, 3, 4] "拷贝数组"
但是这种方法有一定缺陷,不能转换function,还有undefined
var arr2 = [1,2,3,function(){console.log(11)},undefined,null]
let newArr4 = JSON.parse(JSON.stringify(arr2))
newArr4.splice(0,1)
console.log(arr2,'原数组'); //[1, 2, 3, ƒ] "原数组"
console.log(newArr4,'拷贝数组') // [2, 3, null,null,null] "拷贝数组"
上述方法是能实现深拷贝,但我们需要了解当数组中有层级,是否能深拷贝呢?
要知道,比方一个数组 arr =[{a:1,b:2}],这个数组arr存储在一个地址中,而其中的arr[0].a存储的地址却是里另一个地址,相当于引用传递,这时简单的深拷贝将不能拷贝这个深层级的拷贝
多层级拷贝错误示例:
var arr1 = [{a:1,b:1}]
let newArr1 = arr1.slice(0)
newArr1.forEach(item=>{
delete item.a
})
console.log(arr1,'原数组'); //[{b:1}] "原数组"
console.log(newArr1,'拷贝数组') //[{b:1}] "拷贝数组"
//第二种
var arr2 = [[1,2,3],[2,3,4]]
let newArr2 = []
for(var i=0 ;i<arr2.length;i++){
newArr2.push(arr2[i])
}
newArr2.forEach(item=>{
item.splice(0,1)
})
console.log(arr2,'原数组'); //[[2,3],[3,4]] "原数组"
console.log(newArr2,'拷贝数组') //[[2,3],[3,4]] "拷贝数组"
以上是无法实现多层级拷贝,但是JSON.stringify() ,JSON.parse() 却可以
var arr1 = [{a:1,b:1}]
let newArr4 = JSON.parse(JSON.stringify(arr1))
newArr4.forEach(item=>{
delete item.a
})
console.log(arr1,'原数组'); //[{a:1,b:1}] "原数组"
console.log(newArr4,'拷贝数组') //[{b:1}] "拷贝数组"
换一个思路,既然每一层的地址不同,我们何不遍历每一层的拷贝呢
var arr1 = [{a:1,b:1}]
function clone(obj){
if (obj instanceof Array) {
var copy = [];
for (var i = 0, len = obj.length; i < len; ++i) {
copy[i] = clone(obj[i]);
}
return copy;
}
}
let newArr5 = clone(arr1)
newArr5.forEach(item=>{
delete item.a
})
console.log(arr1,'原数组'); //[{a:1,b:1}] "原数组"
console.log(newArr5,'拷贝数组') //[{b:1}] "拷贝数组"
当然不同的对象有不同的clone,以下是笔者封装的方法适用于多种对象
function clone(obj) {
// 判断是否为空未定义
if (null == obj || "object" != typeof obj) return obj;
// Date
if (obj instanceof Date) {
var copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Array
if (obj instanceof Array) {
var copy = [];
for (var i = 0, len = obj.length; i < len; ++i) {
copy[i] = clone(obj[i]);
}
return copy;
}
// Object
if (obj instanceof Object) {
var copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}
throw new Error("无法复制对象!不支持其类型。");
}