深拷贝只会JSON?
想必很多人对深拷贝的概念只停留在JSON.parse(JSON.stringify())
,但他有个致命的缺陷,就是无法拷贝undefined
、function
等类型的值。
下面让我们浅浅的了解下深拷贝:
深拷贝是一种拷贝对象的方式,它会创建一个完全独立于原始对象的新对象。深拷贝不仅复制了对象的引用,还复制了对象的所有内部属性和子对象。这意味着源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。
在实现深拷贝时,需要在堆内存中重新开辟一块空间,存放原对象的值,并让栈中的引用指向这块新的内存地址。这样,新旧对象就不会再相互影响。
我们先来看看深拷贝的逻辑图:
- 在进行深拷贝时,不仅复制对象的引用(即地址),还会复制对象本身及其所有子对象。这样,新的对象将完全独立于原始对象。
- 在这张图中,如果我们对obj1进行深拷贝,这些对象与原始对象在内容上相同但完全独立。
让我们来对深拷贝递归实现的梳理吧,用最简单的代码实现最全面的深拷贝
1.函数的定义
function deepCopy(val, hash = new WeakMap()) {
}
这里定义了一个名为deepCopy
的函数,它接受两个参数:val
(要拷贝的值)和hash
(一个WeakMap
对象,用于存储已经拷贝过的对象和它们的拷贝,默认为一个新的WeakMap
)。
2.类型判断
if (typeof val !== 'object' || val === null) {
return val;
}
首先,函数检查val
的类型。如果val
不是对象或者为null
,则直接返回val
本身,因为基本数据类型如(字符串、数字、布尔值等
)是按值传递的,不需要深度拷贝。
3.循环引用检查
if (hash.has(val)) {
return hash.get(val);
}
接下来函数检查val
是否已经在hash
中存在。如果存在,说明之前已经拷贝过这个对象,直接返回拷贝的引用,避免无限递归。
4.创建拷贝
let copy;
if (Array.isArray(val)) {
copy = [];
} else if (val instanceof Date) {
copy = new Date(val.getTime());
} else if (val instanceof RegExp) {
let pattern = val.valueOf()
let flags = ''
flags += pattern.global ? 'g' : '';
flags += pattern.ignoreCase ? 'i' : '';
flags += pattern.multiline ? 'm' : '';
copy = new RegExp(pattern.source, flags);
} else if (typeof val === 'function') {
// 对于函数,我们直接返回引用(浅拷贝)
copy = val;
} else {
copy = {};
}
根据val
的类型,函数创建不同的拷贝:
- 如果
val
是数组,创建一个新的空数组copy
。 - 如果
val
是Date
对象,通过new Date(val.getTime())
创建一个新的Date
对象,并设置相同的时间。 - 如果
val
是RegExp
对象,通过val.valueOf()
获取正则表达式的描述(但这实际上是错误的,因为valueOf
在RegExp
对象上并不返回源字符串和标志。应直接使用val.source
获取源字符串),并手动构建标志字符串,最后通过new RegExp(pattern.source, flags)
创建一个新的RegExp
对象。 - 如果
val
是函数,直接返回val
的引用(浅拷贝),因为函数在JavaScript中是不可变的。 - 如果
val
是普通对象,创建一个新的空对象copy
。
5. 存入哈希表
hash.set(val, copy);
在创建拷贝后,将原始对象val
和它的拷贝copy
存入hash
中,以便后续处理可能出现的循环引用。
6.递归复制对象的属性
for (let key in val) {
if (val.hasOwnProperty(key)) {
copy[key] = deepCopy(val[key], hash);
}
}
使用for...in
循环遍历val
的所有可枚举属性,并对每个属性递归调用deepCopy
函数,将结果赋值给copy
的相应属性。
最后,函数返回创建的拷贝对象copy
。
例子校验
// 使用示例
let val = {
a: 1,
b: {
c: 2,
d: [3, 4]
}
};
let copiedval = deepCopy(val);
console.log(copiedval); // { a: 1, b: { c: 2, d: [3, 4] } }
console.log(copiedval.b.d === val.b.d); // false,说明是深拷贝
没毛病!
完整代码
function deepCopy(val, hash = new WeakMap()) {
// 如果传入的不是对象或数组,直接返回
if (typeof val !== 'valect' || val === null) {
return val;
}
// 如果对象已经存在于哈希表中,直接返回引用
if (hash.has(val)) {
return hash.get(val);
}
// 创建一个新的拷贝
let copy;
if (Array.isArray(val)) {
copy = [];
} else if (val instanceof Date) {
copy = new Date(val.getTime());
} else if (val instanceof RegExp) {
let pattern = val.valueOf()
let flags = ''
flags += pattern.global ? 'g' : '';
flags += pattern.ignoreCase ? 'i' : '';
flags += pattern.multiline ? 'm' : '';
copy = new RegExp(pattern.source, flags);
} else if (typeof val === 'function') {
// 对于函数,我们直接返回引用(浅拷贝)
copy = val;
} else {
copy = {};
}
// 将新对象存入哈希表
hash.set(val, copy);
// 递归复制对象的属性
for (let key in val) {
if (val.hasOwnProperty(key)) {
copy[key] = deepCopy(val[key], hash);
}
}
return copy;
}
手撕深拷贝是面试经常遇到的问题,了解并掌握深拷贝,对自身开发也是有很大帮助!
转载自:https://juejin.cn/post/7373162529818574867