通俗的讲,浅拷贝就是只拷贝一层,改变拷贝后的内容会影响到拷贝前的,而深拷贝是层层拷贝,拷贝前后的内容互不影响,在内存中有各自的地址
先看浅拷贝
浅拷贝
let obj = {
name: '小明',
address: {
x: 100,
y: 90
}
}
let newObj = {...obj}
obj.address.x = 200
console.log(obj) // {name: '小明', adress: {x: 200,y:90}}
console.log(newObj) // {name: '小明', adress: {x: 200,y:90}}
...扩展运算符只能拷贝一层
let a = [1,2,3,4,5]
let arr = [a]
let newArr = arr.slice()
newArr[0][0] = 100
console.log(arr) // [100, 2, 3, 4, 5]
console.log(newArr) // [100, 2, 3, 4, 5]
slice也是浅拷贝
我们来实现一个自己的浅拷贝
function shallowClone(obj) {
let newObj = {}
for (let key in obj) {
// 判断是否是实例上的属性
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}
深拷贝
let obj = {
name: '小明',
address: {
x: 100,
y: 90
}
}
let o = JSON.parse(JSON.stringify(obj))
obj.address.x = 200
console.log(obj) // {name: '小明', adress: {x: 200,y:90}}
console.log(o) // {name: '小明', adress: {x: 100,y:90}}
这种方式可以实现常见的深拷贝,但是一些特殊情况就不行了 比如:
let obj = {
name: '小明',
address: {
x: 100,
y: 90
},
fn:function(){},
un: undefined
}
let o = JSON.parse(JSON.stringify(obj))
obj.address.x = 200
console.log(obj) // {name: '小明', adress: {x: 200,y:90}, fn:function(){}, un: undefined}
console.log(o) // {name: '小明', adress: {x: 100,y:90}}
可以看到,函数和undefined在拷贝过程中都丢失了
接下来实现一个自己的深拷贝,根据上面实现的浅拷贝,很容易就想到深拷贝需要使用递归来实现,但是需要考虑一些特殊情况:
- 如果是null 或者 undefined ,就直接返回
if (obj == null)return obj
- 如果是普通类型,也直接返回
if (typeof obj !== object) return obj
- 如果是日期
if (obj instanceof Date) return new Date(obj)
- 如果是正则
if (obj instanceof RegExp) return new RegExp(obj)
剩下的则是引用类型的的 对象或者数组
接下来来实现第一版
function deepClone(obj) {
if (obj == null) return obj
if (typeof obj !== "object") return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
// 剩下的只可能是数组或对象
let cloneObj = Object.prototype.toString.call(obj) == "[object Array]" ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 因为属性也可能是引用类型,所以需要递归
cloneObj[key] = deepClone(obj[key])
}
}
return cloneObj
}
let obj = {
name: '小明',
address: {
x: 100,
y: 90
},
fn:function(){},
un: undefined
}
let newObj = deepClone(obj)
obj.address.x = 200
console.log(obj) // {name: '小明', adress: {x: 200,y:90}, fn:function(){}, un: undefined}
console.log(newObj) // {name: '小明', adress: {x: 100,y:90}, fn:function(){}, un: undefined}
到这里实现了我们想要的深拷贝功能,
还可以做一点小优化,上面我们判断类型是对象还是数组的时候用了 Object.prototype.toString,
下面我们换一种方式实现: 任何实例上都有一个constructor属性,也就是它的构造函数
console.log([1,2,3].constructor) // ƒ Array() { [native code] }
console.log({a:1}.constructor) // ƒ Object() { [native code]
可以直接通过:
let cloneObj = new obj.constructor // 如果obj为对象,则cloneObj = {}; 如果obj为数组,则cloneObj = []
第二版优化完整版
function deepClone(obj) {
if (obj == null) return obj
if (typeof obj !== "object") return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
// 剩下的只可能是数组或对象
let cloneObj = new obj.constructor
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 因为属性也可能是引用类型,所以需要递归
cloneObj[key] = deepClone(obj[key])
}
}
return cloneObj
}
let obj = {
name: '小明',
address: {
x: 100,
y: 90
},
fn:function(){},
un: undefined
}
let newObj = deepClone(obj)
obj.address.x = 200
console.log(obj) // {name: '小明', adress: {x: 200,y:90}, fn:function(){}, un: undefined}
console.log(newObj) // {name: '小明', adress: {x: 100,y:90}, fn:function(){}, un: undefined}
还有一种特殊情况需要考虑进去,就是对象可能存在循环引用的情况,比如:
let obj = {
name: '小明',
address: {
x: 100,
y: 90
},
fn:function(){},
un: undefined
}
obj.o = obj
循环引用的对象如果采用递归克隆的话会出现爆栈,因此需要过滤掉已经遍历过的对象 采用weakMap数据结构,weakMap 的键只能是对象
第三版优化完整版
function deepClone(obj, hash = new WeakMap()) {
if (obj == null) return obj
if (typeof obj !== "object") return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
// 剩下的只可能是数组或对象
// 如果已经存入了hash中,说明已经遍历过,直接return
if (hash.get(obj)) return hash.get(obj)
let cloneObj = new obj.constructor
// 已经遍历过的对象存入weakMap
hash.set(obj, cloneObj)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 因为属性也可能是引用类型,所以需要递归
cloneObj[key] = deepClone(obj[key], hash)
}
}
return cloneObj
}
let obj = {
name: '小明',
address: {
x: 100,
y: 90
},
fn:function(){},
un: undefined
}
let newObj = deepClone(obj)
obj.address.x = 200
console.log(obj) // {name: '小明', adress: {x: 200,y:90}, fn:function(){}, un: undefined}
console.log(newObj) // {name: '小明', adress: {x: 100,y:90}, fn:function(){}, un: undefined}
总结:
- 浅拷贝只遍历一层,多层引用类型的数据,改变拷贝前的值会影响到拷贝后的值
- 深拷贝是层层遍历,拷贝前后的数据互不影响
- ...扩展运算符和slice都属于浅拷贝,如果要实现自定义的浅拷贝,只需做一层遍历即可,但是要通过hasOwnProperty过滤掉原型上的属性
- 实现深拷贝需要在遍历的基础上加上递归,需要考虑日期、正则等特殊类型以及循环引用的特殊情况