前言
前拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。
首先,需要知道,JavaScript中的数据类型分为基本数据类型和引用数据类型,对于基本数据类型而言,没有深浅拷贝的区别,我们所说的深拷贝都是对于引用数据类型而言的。
浅拷贝
const obj = { a: 'a', b: 'b', c: [1, 2, 3], d: { dd: 'dd' } }
const array = [1, 2, 3, 4, 5]
const CloneObj = obj
const CloneArray = array
CloneObj.a = 'c'
CloneArray.push(6)
console.log(CloneArray, CloneObj)
// [1, 2, 3, 4, 5, 6]
console.log(CloneObj)
// a: "c"
// b: "b"
// c: (3) [1, 2, 3]
// d: {dd: "dd"}
可以清楚地看到当CloneObj和CloneArray发生变化时,obj和array也发生了变化。
深拷贝
深拷贝就是对目标完全的拷贝,只要进行了深拷贝,他们就不会再互相影响。
目前实现深度拷贝的方法主要有两种:
1. 利用JSON对象中的parse和stringify
2.利用递归来实现每一层重新创建对象并赋值
首先看第一种方式
// 使用JSON来实现深度克隆
const obj1 = { a: 'a', b: 'b', c: [1, 2, 3], d: { dd: 'dd' } }
const CloneObj1 = JSON.parse(JSON.stringify(obj1))
console.log(obj1 === CloneObj1) // false
CloneObj1.a = { aa: 'aa' }
console.log(obj1) //a: "a", b: "b",c: [1, 2, 3],d: {dd: "dd"}
const array1 = [1, 2, 3, 4, 5]
const CloneArray1 = JSON.parse(JSON.stringify(array1))
console.log(array1 === CloneArray1) // false
CloneArray1.push(6)
console.log(array1) // [1, 2, 3, 4, 5]
以上代码确实实现了深度拷贝,但是这种方法只能使用一些简单的情况,比如如果对象中的一个属性值是一个函数,那么使用这个方法会导致属性丢失。
因为undefined、function、symbol会在转换过程中被忽略,所以当一个对象中有函数时,就不能用这个方法进行深拷贝。
第二种方式:递归
function DeepClone(source) {
// 判断目标是数组还是对象
const targetObj = source.constructor === Array ? [] : {}
for (let key in source) {
if (source.hasOwnProperty(key)) {
// 如果是对象就递归
if (source[key] && typeof source[key] === 'object') {
targetObj[key] = source[key].constructor === Array ? [] : {}
targetObj[key] = DeepClone(source[key])
} else {
targetObj[key] = source[key]
}
}
}
return targetObj
}
首先判断目标的类型,但是要怎么判断呢??? 我们可以根据constructor属性来判断,因为实例对象的constructor属性指向它的构造函数,之前我们提到过,只有引用类型的数据才有浅拷贝深拷贝之分,所以接下来就是要判断是不是引用类型,如果是就继续向下进行递归,如果不是则直接赋值,这样就实现了深度拷贝。
那么我们来测试一下:
var a = {
msg: '213123',
data: '2018141142',
getName: function() {
return 'Bob'
}
}
var b = DeepClone(a)
b.data = '0'
console.log(a) // {msg: "213123", data: "2018141142", getName: ƒ}
可以,没有问题!
补充:JavaScript中拷贝的方法
我们知道在JavaScript
中,数组有两个方法 concat
和 slice
是可以实现对原数组的拷贝的,这两个方法都不会修改原数组,而是返回一个修改后的新数组。
同时,ES6 中 引入了 Object.assgn
方法和 ...
展开运算符也能实现对对象的拷贝。
经过我自己的测试,发现他们都是首层浅拷贝。
总结:
1. 赋值运算符=实现的是浅拷贝,只拷贝对象的引用值
2. JavaScript中数组和对象自带的拷贝方法都是首层浅拷贝
3. JSON实现的是深拷贝,但是对目标对象有要求
4. 如果想实现真正意义上的深拷贝,采用上面的递归方式