前言
深浅拷贝是在面试中经常出现的考题,今天让让我们深入理解深浅拷贝以及它们的区别。
深拷贝 VS 浅拷贝
浅拷贝是什么?
创建一个新对象,这个对象有着原始对象属性值的一份精准拷贝。如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,那么拷贝的就是内存地址。所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝是什么?
创建一个新对象,把原对象从内存里完整的拷贝一份出来,从堆内存里开辟一个新的区域来存放新对象,并且修改新对象的属性值不会影响原对象。
浅拷贝
手写浅拷贝
function shallowCopy(obj) {
if (typeof obj !== 'object') return obj
let newObj = Array.isArray(obj) ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}
深拷贝
入门方法:JSON.stringify() 利用JSON.stringfy()把JS对象或值转换为JSON字符串,最后利用JSON.parse()方法把JSON字符串生成一个新对象。该方法虽然简单,但是还是存在些问题。
- 执行会报错:存在BigInt类型、循环引用
- 拷贝Date引用类型会变成字符串
- 键值会消失:对象的值中为function、undefined、Symbol这几种类型
- 键值会变成空对象:对象的值中为Map、Set、RegExp这几种类型
- 无法拷贝:不可枚举属性,对象的原型链 由于以上问题,我们如果要拷贝很复杂的数据类型只能换种方法了。接下来让我们手动实现深拷贝吧。
手写深拷贝基础版
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) return obj
let newObj = Array.isArray(obj) ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object') {
newObj[key] = deepCopy(obj[key])
} else {
newObj[key] = obj[key]
}
}
}
return newObj
}
手写深拷贝进阶版
function deepCopyBest(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用:如果对象已经拷贝过,则直接返回其拷贝副本
if (hash.has(obj)) {
return hash.get(obj);
}
// 处理 Date 对象
if (obj instanceof Date) {
return new Date(obj);
}
// 处理 RegExp 对象
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// 根据原始对象类型(数组或对象)创建新的容器
const newObj = Array.isArray(obj) ? [] : {};
// 将新创建的拷贝对象存入 hash 表,以便后续处理循环引用
hash.set(obj, newObj);
// 拷贝普通属性 (own properties)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepCopy(obj[key], hash);
}
}
// 拷贝 Symbol 属性 (own properties)
const symbols = Object.getOwnPropertySymbols(obj);
for (let symbol of symbols) {
// Symbol 属性的描述符也需要考虑,这里简单处理直接赋值
// 如果需要精确拷贝属性描述符,可以使用 Object.defineProperty
newObj[symbol] = deepCopy(obj[symbol], hash);
}
return newObj;
}