一、深拷贝 VS 浅拷贝
- 浅拷贝:如果拷贝的属性是引用类型的话,原始对象和副本对象共享内存。
- 深拷贝:如果拷贝的属性是引用类型的话,原始对象和副本对象不共享内存,副本会创建一个新的内存地址。
简单理解:浅和深的相对于拷贝层级来划分的,如果只拷贝外层基本数据类型和引用类型的内存地址,不会递归复制引用类型属性所引用的对象,就叫做浅拷贝;如果递归复制引用类型属性所引用的对象,包括嵌套的对象和属性,并创建独立的对象副本,则叫做深拷贝。
二、实现深拷贝的方法及注意事项
2.1 JSON.parse 会生成一个新对象(无法处理函数、Symbol;如果有循环引用也会报错。)
const obj4 = JSON.parse(JSON.stringify(info))
2.2 自己手写实现
function deepCopy(originValue) {
// 0.如果值是Symbol的类型
if (typeof originValue === "symbol") {
return Symbol(originValue.description) //由于 Symbol 的唯一性,即使描述符相同,创建的 Symbol 值也是不相等的。
}
// 1.如果是原始类型, 直接返回
if (!isObject(originValue)) {
return originValue
}
// 2.如果是set类型,for of 遍历item,且对item进行深拷贝
if (originValue instanceof Set) {
const newSet = new Set()
for(const setItem of originValue) {
newSet.add(deepCopy(setItem))
}
}
// 3.如果是函数function类型, 不需要进行深拷贝
if (typeof originValue === "function") {
return originValue
}
// 2.如果是数组类型,创建数组;对象类型, 创建对象
const newObj = Array.isArray(originValue) ? []: {}
// 遍历普通的key
for (const key in originValue) {
newObj[key] = deepCopy(originValue[key]);
}
// 单独遍历symbol
const symbolKeys = Object.getOwnPropertySymbols(originValue)
for (const symbolKey of symbolKeys) {
newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey])
}
return newObj
}
三、追问问题:
1问:
为什么引用类型的属性值是函数,则不需要深拷贝,直接返回原来的引用地址就行了?
1答:
- 函数比较特殊,函数是用来执行的,不是用来保存数据的。也就是说,对于保存数据的类型,如果不深拷贝,只要修改一个对象,另一个也会被修改,无法保持独立;但是对于函数而言是不会有修改的操作的,自然也没有深拷贝的必要了。
- 函数的定义在内存中只有一个实例,无论我们创建多少个对该函数的引用,它们都指向同一个函数定义所在的内存地址,每调用一次函数,都会有独立的作用域和上下文。因此,如果每深拷贝一次,创建更多对函数的引用,只会浪费内存。
2问:
如何解决循环引用问题?
2答:
用weakMap来跟踪已经拷贝过的对象,传参map = new WeakMap()。
function deepCopy(originValue, map = new WeakMap()) {
// const map = new WeakMap()
// 4.如果是对象类型, 才需要创建对象
if (map.get(originValue)) {
return map.get(originValue)
}
const newObj = Array.isArray(originValue) ? []: {}
map.set(originValue, newObj)
// 遍历普通的key
for (const key in originValue) {
newObj[key] = deepCopy(originValue[key], map);
}
return newObj
}
-------------------------------------------以下是测试代码--------------------
const info = {
name: "abs",
age: 12,
classmates: {
name: "xiaoliu",
hobby: {
name: "篮球",
detail: "打篮球"
}
},
// self: info
}
info.self = info //出现了循环引用的问题
let newObj = deepCopy(info)
console.log(newObj)
console.log(newObj.self === newObj)