前言
这也是大厂经常会问到的高频考点了。
之前自己也写过深浅拷贝的文章,因为主要是对其概念的叙述,所以对其基本概念就不进行介绍了,想了解的朋友们可以参考一下这篇文章
👉👉 再来聊聊深浅拷贝~🐟 🐟 - 掘金 (juejin.cn)
面试官:如何实现深拷贝呢?
JSON.parse(JSON.stringify())
当被问到这个问题的时候,我们脑海中可能想的最多的就是
JSON.parse(JSON.stringify());
如果你仅仅这样告诉面试官的话,肯定戳不到面试官的心巴。 因为这个方法还存在着一些较大的缺陷。
1. 当拷贝对象里有函数的时候,结果会丢失
const test = {
name: 'a',
date: function hehe() {
console.log('fff')
},
};
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.log('copyed', copyed); // copyed { name: 'a' }
// 直接造成了date的丢失
2. 当拷贝的对象里有RegExp对象时,会得到空对象
const test = {
name: 'a',
date: new RegExp('\\w+'),
};
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.log('copyed', copyed); // copyed { name: 'a', date: {} }
3. 如果对象中存在循环引用的情况也无法正确实现深拷贝
从手写浅拷贝开始
我们的深拷贝,其实也就是在浅拷贝的基础上进行递归,所以实现一个深拷贝,可以从浅拷贝开始了解。
1. 要对对象进行拷贝,肯定也要创建一个新的对象来承接原始的对象
function clone(target) {
let cloneTarget = {};
return cloneTarget;
};
2. 进行一对一拷贝
function clone(target) {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = target[key];
}
return cloneTarget;
};
没错,一个浅拷贝的实现就是这样简单!!
基础版
要想把一个浅拷贝变成深拷贝,只需要进行逐层的递归。
递归的时候要记住
1. 如果是原始类型,无需继续拷贝,直接返回
2. 如果是引用类型,再进行递归,创建一个新的对象,遍历需要克隆的对象。
所以在这里还需要判断一下类型是否为引用类型
function clone(target) {
if (typeof target === 'object') {
} else {
return ;
}
};
再遍历递归对象
function clone(target) {
if (typeof target === 'object') {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
这里又出现了一个问题了,这要是拷贝的对象是个数组呢?
如果还是用这个代码,连中括号([])
都不存在的代码,怎么能得到我们想到的数组呢?
😋😋😋
(数组也是对象呀,不可能不考虑它)
所以接下来,还要对是否为数组进行考虑了
判断数组
function clone(target) {
if (typeof target === 'object') {
// 增加下列一行的代码就能兼容数组了
let cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
一般来说,面试官面到这一步,就不会为难你了。但事事有例外~😅
有些面试官就是想再搞一下我们心态,“你能手写个循环引用的情况吗”
啥是循环引用呀
循环引用
循环引用:即对象的属性间接或直接的引用了自身
不理解没关系,看下面这段测试代码
const obj = {
a: 1,
b: 2,
c: {
child: 'child'
},
};
obj.obj = obj;
console.log('obj', obj);
在node环境下我们可以得到这样的结果
这里的Circular
就是循环引用的意思
想要更清晰的理解,可以把这测试代码在浏览器上运行一下
可以看到,这里会一直obj => obj
的循环嵌套下去。
让我想起来原来的小故事:从前有座山,山里有座庙,庙里有个小和尚在讲故事,讲的是什么呢:从前有座山,山里有座庙...
🤣🤣🤣
那么如何改进之前的代码呢?
解决循环引用问题,我们可以额外开辟一个存储空间,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。
这个存储空间,需要可以存储key-value
形式的数据,且key
可以是一个引用类型,我们可以选择Map
这种数据结构:
- 检查
map
中有无克隆过的对象 - 有 - 直接返回
- 没有 - 将当前对象作为
key
,克隆对象作为value
进行存储 - 继续克隆
function clone(target, map = new Map()) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
for (const key in target) {
cloneTarget[key] = clone(target[key], map);
}
return cloneTarget;
} else {
return target;
}
};
小结
希望能对屏幕前的你有所帮助吧~
❤️❤️❤️