白瑕 2023-01-12 10:52 采纳率: 80%
浏览 53
已结题

JavaScript 关于深拷贝问题

问题

我正在尝试完成操作回撤功能, 需要保存各个操作后的状态, 状态内有一个对象存在循环引用和对象引用的问题, 我尝试通过这个深拷贝函数将它保存下来:

const is = {
    Array: Array.isArray,
    Date: (val) => val instanceof Date,
    Set: (val) => Object.prototype.toString.call(val) === '[object Set]',
    Map: (val) => Object.prototype.toString.call(val) === '[object Map]',
    Object: (val) => Object.prototype.toString.call(val) === '[object Object]',
    Symbol: (val) => Object.prototype.toString.call(val) === '[object Symbol]',
    Function: (val) => Object.prototype.toString.call(val) === '[object Function]'
}

function deepCopy(value, weakMap = new WeakMap()) { // 新增box的world无法深拷贝?
    // Function
    if (is.Function(value)) {
        return value;
    }

    if (is.Date(value)) return new Date(value.valueOf())

    if (is.Symbol(value)) return Symbol(value.description)

    if (is.Set(value)) {
        const newSet = new Set()
        for (const item of value) newSet.add(deepCopy(item), weakMap)
        return newSet
    }

    if (is.Map(value)) {
        const newMap = new Map()
        for (const item of value) newMap.set(deepCopy(item[0], weakMap), deepCopy(item[1], weakMap))
        return newMap
    }

    if (weakMap.has(value)) return weakMap.get(value)

    if (!(is.Object(value))) return value

    const newObj = is.Array(value) ? [] : {}

    weakMap.set(value, newObj)

    for (const key in value) {
        weakMap.set(value, newObj);
        newObj[key] = deepCopy(value[key], weakMap);
        if (is.Object(value[key])) {
            const proto = Object.getPrototypeOf(value[key]);
            Object.setPrototypeOf(newObj[key], proto);
        }
    }

    const symbolKeys = Object.getOwnPropertySymbols(value)
    for (const sKey of symbolKeys) {

        newObj[sKey] = deepCopy(value[sKey], weakMap)
    }

    return newObj
}
遇到的现象和发生背景,请写出第一个错误信息

对象内有一个数组, 该数组应当反应页面内所有的box, 但现在用户增加box后状态无法保存(即该数组内元素增加后的状态经过深拷贝依然受到对象引用影响).
如下图box数组:

img

增加操作会导致所有由增加操作得到的状态发生同步改变.

但用户删除box, 即该数组内元素减少后的状态可以正常保存下来, 由减少操作得到的状态之间却互不影响.

在调用该深拷贝函数前, 取到的状态均是完全正确.

我想知道这是否是深拷贝函数内的问题?
谢谢

  • 写回答

2条回答 默认 最新

  • m0_54204465 2023-01-12 12:45
    关注

    你的深拷贝代码中存在一些问题。

    首先,在使用 WeakMap 时,你在第一次遇到一个对象时将其设置为了 WeakMap 的键值,但在遍历对象中的属性时再次使用了 weakMap.set 方法,这样就又新建了一个键值。

    其次,你在遍历对象属性时,在 newObj[key] = deepCopy(value[key], weakMap) 后再使用了 Object.setPrototypeOf(newObj[key], proto) 方法,但这不会影响到 newObj[key] 的原型链,只会在 newObj[key] 上设置一个不同的原型对象。

    修改的方法如下:

    function deepCopy(value, weakMap = new WeakMap()) {
    if (is.Function(value)) return value;
    if (is.Date(value)) return new Date(value.valueOf())
    if (is.Symbol(value)) return Symbol(value.description)
    if (is.Set(value)) {
    const newSet = new Set()
    for (const item of value) newSet.add(deepCopy(item), weakMap)
    return newSet
    }
    if (is.Map(value)) {
    const newMap = new Map()
    for (const item of value) newMap.set(deepCopy(item[0], weakMap), deepCopy(item[1],weakMap))
    return newMap
    }
    if (weakMap.has(value)) return weakMap.get(value)
    if (!(is.Object(value))) return value
    const newObj = is.Array(value) ? [] : {}
    weakMap.set(value, newObj)
    for (const key in value) {
    newObj[key] = deepCopy(value[key], weakMap);
    }
    const symbolKeys = Object.getOwnPropertySymbols(value)
    for (const sKey of symbolKeys) {
    newObj[sKey] = deepCopy(value[sKey], weakMap)
    
    }
    return newObj
    }
    
    

    这样深拷贝函数就可以递归拷贝对象中的对象,也就能解决循环引用和对象引用的问题了。第二个问题就是, 数组中新增元素会被修改, 是因为在数组中使用的引用是指向同一地址的, 对数组的深拷贝需要特殊处理.
    一种方法是使用循环,将数组中的每个元素单独深拷贝,将深拷贝后的元素放入新的数组中.
    另一种方法是使用 JSON.parse(JSON.stringify(arr)) 转换成字符串再转换成对象的方式进行深拷贝,这样就可以完成对数组的深拷贝。

    如果你需要更简单的做法可以使用工具库如 lodash 的 _.cloneDeep(arr) 方法,或者 使用 spread operator [...arr]

    const newArr = [...arr]

    或者

    const newArr = Array.from(arr)

    这些都可以完成数组的深拷贝.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

问题事件

  • 系统已结题 1月20日
  • 已采纳回答 1月12日
  • 创建了问题 1月12日

悬赏问题

  • ¥15 51寻迹小车定点寻迹
  • ¥15 谁能帮我看看这拒稿理由啥意思啊阿啊
  • ¥15 关于vue2中methods使用call修改this指向的问题
  • ¥15 idea自动补全键位冲突
  • ¥15 请教一下写代码,代码好难
  • ¥15 iis10中如何阻止别人网站重定向到我的网站
  • ¥15 滑块验证码移动速度不一致问题
  • ¥15 Utunbu中vscode下cern root工作台中写的程序root的头文件无法包含
  • ¥15 麒麟V10桌面版SP1如何配置bonding
  • ¥15 Marscode IDE 如何预览新建的 HTML 文件