记录如何深度拷贝一个属性存在循环引用的对象

深度拷贝的实现思路是遍历对象的 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 完全一样了。

转载于:https://juejin.im/post/5c7bc9c16fb9a049ec6bcb24

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值