深度拷贝的实现思路是遍历对象的 property 取值然后进行递归调用。(代码部分假设该对象的属性均为 object)
function fn(object) {
for (var key in object) {
fn(object[key]); // 递归调用
}
}
复制代码
在每一次函数执行开始前创建一个新的 object,在遍历 property 的同时赋予新对象相同的属性。
function fn(object) {
var cloneObj = {};
for (var key in object) {
// 赋予新对象相应的 property
// 通过递归调用来拷贝 property 的值
cloneObj[key] = fn(object[key]);
}
// 返回新对象
return cloneObj;
}
复制代码
假如有这样一个对象,就会让这个函数陷入死循环。
var obj = {};
obj.a = obj; // obj 的 a 属性 引用 obj,形成循环引用
// obj 的结构是这个样子
obj: {
a: {
a: {
a:{
...
}
}
}
}
复制代码
看了 Lodash 的 cloneDeep 函数提供了解决思路,创建一个在整个函数执行期间一直存在的 Map 类型变量(Map 可以设置除 string 以外的类型作为 key,也是键值对的结构,这一点比 object 要灵活),每一次函数执行把这一轮传入的对象作为 key 新的创建对象作为值,在函数开头执行判断传入的对象是否已经存在 map 的 keys 中,如果存在那代表着传入的对象在之前已经遍历过了
完整代码
function fn(object) {
// 首先判断 object 是否存在于 map.keys 中
if (Array.from(map.keys()).includes(object)) {
// 如果存在则取出值并返回
return map.get(object);
}
var cloneObj = {};
// 设置 object 为 key,cloneObj 为值
map.set(object, cloneObj);
for (var key in object) {
// 赋予新对象相应的 property
// 通过递归调用来拷贝 property 的值
cloneObj[key] = fn(object[key]);
}
// 返回新对象
return cloneObj;
}
var obj = {};
obj.a = obj;
var map = new Map();
fn(obj);
复制代码
阐述一下具体执行过程
第一次循环,把 obj 作为 key,cloneObj 作为值存入map
map.set(object, cloneObj);
// result: map { obj: cloneObj }
复制代码
遍历 obj 的属性(这里只有一个属性 a)
for (var key in obj) {
}
复制代码
给 cloneObj 添加上相同的属性名为 a,但是 obj.a 值我们并不知道,所以使用递归依次向下取值
for (var key in obj) {
cloneObj[a] = fn(obj[a]);
}
复制代码
注意在这之前初始化 obj 的时候,obj 的属性 a 是等于 obj的
var obj = {};
obj.a = obj
复制代码
所以
fn(obj[a]) 相当于 fn(obj)
复制代码
现在进入第二次循环
// 此时 obj 已经存在于 map.keys 中
// map { obj: cloneObj }
if (Array.from(map.keys()).includes(obj)) {
return map.get(obj); // 返回第一次循环中的 cloneObj
}
复制代码
从这里第二次循环就结束了,返回了第一次循环中的 cloneObj
// 函数执行结束,回到第一次循环
for (var key in obj) {
cloneObj[a] = cloneObj; // fn(obj[a])的返回值
}
// 赋值语句执行过后 cloneObj 的结构是这样的
cloneObj: {
a: {
a: {
...
}
}
}
复制代码
这里 cloneObj 的结构就跟 obj 完全一样了。