当前位置: 首页 > 知识库问答 >
问题:

javascript - 求教js的深拷贝解决循环引用的问题?

薛宜
2023-04-27
function cloneDeep(obj: any, map = new WeakMap()): any{
    if(typeof obj !== 'object'|| obj == null) return obj
    // 避免循环引用
    const objFromMap = map.get(obj)
    if(objFromMap) return objFromMap

    let res:any = {} 
    map.set(obj, res)
    
    // Map
    if(obj instanceof Map){
        res = new Map()
        obj.forEach((value,key)=> {
            const key1 = cloneDeep(key, map)
            const value1 = cloneDeep(value, map)
            res.set(key1, value1)
        })
    }
    
    // Set
    if(obj instanceof Set){
        res = new Set()
        obj.forEach(value=> {
            res.add(cloneDeep(value, map))
        })
    }
    
    // Array
    if(obj instanceof Array){
        res = obj.map(value => cloneDeep(value, map))
    }
    
    // Object
    for(const key in obj){
        res[key] = cloneDeep(obj[key], map)
    }
        
    return res
}

这个深拷贝的代码,解决了循环引用的问题,但是我没看明白是怎么解决的循环引用?
当递归遇到引用了自己的属性的时候,WeakMap返回的不是空对象吗?不应该是指向自己啊。求指教

共有2个答案

秦学林
2023-04-27

先把 js 的引用类型和基础类型的区别理解透彻之后,这个问题你自己就能解释了。

简单说就是 res[key] = xxx 这种语句,会对 res 进行修改,这个修改也会动态反馈到 WeakMap 中的那个对象中去,因为它们所储存的都是该对象的引用。

明白这点之后其实逻辑就很清晰了,深克隆会递归遍历所有子属性,如果存在循环引用,比如:

const a = {}
const b = {}
a.b = b
b.a = a

cloneDeep(a)

这时候就会发生无限循环 b => a => b => a => ...,所以要用一个中间变量来记录是否已经对当前属性进行过深拷贝了,在这种情况下,因为已经深拷贝过该属性,直接返回拷贝过后的该属性(也是引用类型)即可。

宰父疏珂
2023-04-27

循环引用解决方案:
使用一个map记录已经读取的值,避免下次再次读取;
例如对象:a = {b:{c:{d:{a}}}}
拷贝读取以上循环引用的数据,
如果不使用map记录已经拷贝的;以上的拷贝顺序:b,c,d,a,b,c,d,a,b,c····一致循环下去;
打断上面的循环就是使用map记录以及拷贝的,从而终止程序;可以看到上面重复b的时候就可以终止了;

你程序中的空对象相当于一个flag,也就是true,起到了一个判断作用,就是判断是否已经拷贝过了;
你程序中的空对象,只需要递归的将值拷贝进去 res 这个对象中,最终返回这个对象就是你的拷贝结果;

你的程序和上面的逻辑类似:


function cloneDeep(obj: any, map = new WeakMap()): any{
    if(typeof obj !== 'object'|| obj == null) return obj
    // 避免循环引用
// 这里就是记录的map,从而打断循环
    const objFromMap = map.get(obj)
    if(objFromMap) return objFromMap

    let res:any = {} 
    map.set(obj, res)
    
    // Map
    if(obj instanceof Map){
        res = new Map()
        obj.forEach((value,key)=> {
            const key1 = cloneDeep(key, map)
            const value1 = cloneDeep(value, map)
            res.set(key1, value1)
        })
    }
    
    // Set
    if(obj instanceof Set){
        res = new Set()
        obj.forEach(value=> {
            res.add(cloneDeep(value, map))
        })
    }
    
    // Array
    if(obj instanceof Array){
        res = obj.map(value => cloneDeep(value, map))
    }
    
    // Object
    for(const key in obj){
//
        res[key] = cloneDeep(obj[key], map)
    }
        
    return res
}
 类似资料:
  • 本文向大家介绍深拷贝里的循环引用如何解决?相关面试题,主要包含被问及深拷贝里的循环引用如何解决?时的应答技巧和注意事项,需要的朋友参考一下 考察的是如何实现深拷贝问题。深拷贝需要为每一个对象属性创建新的对象,但是如果单纯这样做碰到含有循环引用的对象,就会进入死循环。 这么操作当然是错误的,为了正确进行深拷贝,不出现这种错误,就需要: 遍历原对象每个节点的时候,记录该节点是否被访问过,这样当在遍历过

  • 本文向大家介绍javascript深拷贝和浅拷贝详解,包括了javascript深拷贝和浅拷贝详解的使用技巧和注意事项,需要的朋友参考一下 一、数组的深浅拷贝 在使用JavaScript对数组进行操作的时候,我们经常需要将数组进行备份,事实证明如果只是简单的将它赋予其他变量,那么我们只要更改其中的任何一个,然后其他的也会跟着改变,这就导致了问题的发生。 这是为什么呢? 因为如果只是简单的赋值,它只

  • 本文向大家介绍解决pytorch 的state_dict()拷贝问题,包括了解决pytorch 的state_dict()拷贝问题的使用技巧和注意事项,需要的朋友参考一下 先说结论 model.state_dict()是浅拷贝,返回的参数仍然会随着网络的训练而变化。 应该使用deepcopy(model.state_dict()),或将参数及时序列化到硬盘。 再讲故事,前几天在做一个模型的交叉验证

  • 本文向大家介绍深入理解python中的浅拷贝和深拷贝,包括了深入理解python中的浅拷贝和深拷贝的使用技巧和注意事项,需要的朋友参考一下 在讲什么是深浅拷贝之前,我们先来看这样一个现象: 为什么我只对b进行修改,却影响到了a呢?看过我在之前的文章中就说过:序列中保存的都是内存的引用。 所以,当我们通过b去修改里面的空列表的时候,其实就是修改内存中的同一个对象,所以会影响到a。 代码验证无误,所以

  • 本文向大家介绍深拷贝与 浅拷贝的区别?相关面试题,主要包含被问及深拷贝与 浅拷贝的区别?时的应答技巧和注意事项,需要的朋友参考一下 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

  • 本文向大家介绍浅谈Python浅拷贝、深拷贝及引用机制,包括了浅谈Python浅拷贝、深拷贝及引用机制的使用技巧和注意事项,需要的朋友参考一下 这礼拜碰到一些问题,然后意识到基础知识一段时间没巩固的话,还是有遗忘的部分,还是需要温习,这里做份笔记,记录一下 前续 先简单描述下碰到的题目,要求是写出2个print的结果 可以看到,a指向了一个列表list对象,在Python中,这样的赋值语句,其实内