浅拷贝与深拷贝的区别及方法
概念及区别
浅拷贝
浅拷贝,按照字面意思来理解,就是浅层拷贝(复制),浅拷贝主要是将拷贝对象的值进行拷贝,当拷贝对象是引用类型是,会直接拷贝对象的引用地址。
深拷贝
深拷贝,就是深层拷贝,当拷贝对象属于引用类型时,深拷贝会层层进入对象,复制其中的值,而不是直接复制引用地址。
区别
浅拷贝与深拷贝的主要区别就在于,当拷贝对象属于引用类型时,
如果使用使用浅拷贝,那么当原对象发生改变时,复制对象也会同时发生改变:
let originData = { a: 1, b: 2}
let copyData = originData // copyData={a: 1,b: 2}
originData.a = 3 // originData = {a: 3, b: 2} copyData= {a: 3, b: 2}
当使用深拷贝时,复制对象和原对象已经属于两个独立的对象,互相各不影响,所以不会发生改变
let originData = { a: 1, b: 2}
let copyData = originData // copyData={a: 1,b: 2}
originData.a = 3 // originData = {a: 3, b: 2} copyData= {a: 1,b: 2}
可以参考c语言中的指针,使用浅拷贝时,复制对象只是创建了一个指针,并且将其指向了复制对象的地址,即两个对象同时指向了一个地址,所以当对象数据发生改变时,两个对象都会同时发生改变
使用深拷贝时,也是创建了一个指针,但不同的是,深拷贝重新分配了一个存储空间,并且将该指针指向新的存储空间,然后将复制对象中的值,一一复制到新的空间中,完成后,两个对象即是两个完全独立的存储空间,所以当复制对象发生改变时,新的对象并不会随之改变。
深拷贝的方法
JSON.stringify(parse)
该方法只能用于数组以及对象的深拷贝,对function不适用。该方法通过将对象转化为字符串,然后再将字符串转化为对象,从而达到建立新的一个完全独立的对象的目的。
但是该方法存在较大的弊端,只有在源数据格式相对简单的情况下才可以使用,但源数据中包括Map,Set,Date以及其他内置类型时,会存在数据类型丢失的情况
let target = {a: 1, b:2}
// JSON.stringify(target): '{a: 1,b:2}'字符串
// JSON.parse(JSON.stringify(target)): {a:1,b:2}
let copy = JSON.parse(JSON.stringify(target)) // copy = {a:1, b:2}
let a = {}
let b = new Set()
b.add(11)
// 此时b为Set类型数据
a.b = b
console.log(a) // {b: Set(10)}
console.log(JSON.parse(JSON.stringify(a))) // {b: [10]} 数据类型丢失
$.extend
利用$.extend也可以实现深拷贝,但是需要引入jquery库,如果项目中并没有引入JQ库,那么明显是不合适的。
let a = [1,2,3]
let b = a
a.push(4)
console.log(a) // [1,2,3,4]
console.log(b) // [1,2,3,4]
let a = [1,2,3]
let b = $.extend(true, [],a)
a.push(4)
console.log(a) // [1,2,3,4]
console.log(b) // [1,2,3]
递归实现深度克隆
递归实现深度克隆,需要判断节点是否为简单数据类型,以及节点为引用数据类型时,该如何处理。
当节点为引用数据类型时,遍历该节点,如果子节点为简单数据类型,直接进行简单复制,如果为引用数据类型,就递归进入该子节点,重复操作。并且在末尾返回该子节点的克隆节点。
function deepClone(obj){
let objClone;
// 判断源数据是否为引用数据类型
if(obj && typeof obj==="object"){
objClone = Array.isArray(obj)?[]:{}
for(key in obj){
// 判断子节点是否为对象
if(obj.hasOwnProperty(key)){
if(obj[key]&&typeof obj[key] ==="object"){
objClone[key] = deepClone(obj[key]);
}else{
// 如果不是,简单复制
objClone[key] = obj[key];
}
}
}
}else{
objClone = obj
}
return objClone;
}
误区
误区1(Array.slice)
Array.slice并不能实现完全的深度拷贝,其只进行了一层的拷贝,当源数据拥有多层结构时,第二层及之后的节点,依旧会受源数据影响。
let a = [
[1,2,3],
[2,3,4]
]
let b = a.slice()
a[1][2] = 5
console.log(a) // [[1,2,3],[2,3,5]]
console.log(b) // [[1,2,3],[2,3,5]]
误区2(Array.concat)
因为Array.concat方法返回了一个新的数组,所以很容易导致开发者认为这是一个深拷贝,但非常的遗憾,这同样只在第一层进行了拷贝。
let a = [
[1,2,3],
[2,3,4]
]
let b = a.concat([])
a[1][2] = 5
console.log(a) // [[1,2,3],[2,3,5]]
console.log(b) // [[1,2,3],[2,3,5]]