JavaScript深拷贝的常见陷阱与解决方案

作者:闭包旅人

时间:2026-10-24

浏览:171

本文深入探讨JavaScript中深拷贝的常见误区,分析JSON.parse与递归方法的局限性,并针对Date、RegExp及循环引用等复杂场景提供基于WeakMap的解决方案,同时简述函数拷贝的挑战。

文章目录

前言

在之前的面试经历中,面试官曾询问如何实现对象的深拷贝。当时我认为这是一个基础问题,便直接回答通常使用JSON.parse结合JSON.stringify,或者通过for...in循环配合递归来实现。面试官对此表示认可,但我后来发现这两种方法均存在明显的缺陷。

提出问题

上述两种常见方法具体存在哪些Bug?

特殊对象拷贝

假设有一个对象,忽略基本数据类型,其包含以下成员:


const obj = {
    arr: [111, 222],
    obj: {key: '对象'},
    a: () => {console.log('函数')},
    date: new Date(),
    reg: /正则/ig
}

接下来,我们分别使用上述两种方法进行拷贝测试。

JSON法


JSON.parse(JSON.stringify(obj))

输出结果:

前言

从结果可以看出,普通对象和数组能够被正确拷贝,但Date对象被转换为了字符串,函数直接丢失,而正则表达式则变成了空对象。

递归


function isObj(obj) {
    return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}
function deepCopy(obj) {
    let tempObj = Array.isArray(obj) ? [] : {}
    for(let key in obj) {
        tempObj[key] = isObj(obj[key]) ? deepCopy(obj[key]) : obj[key]
    }
    return tempObj
}

结果:

前言

结论

通过测试可知,这两种方法均无法正确处理函数、Date和RegExp类型的对象。

  • 环

什么是环?

环指的是对象之间的循环引用,导致形成闭环。例如以下对象:


var a = {}

a.a = a

前言

使用上述两种方法进行拷贝会直接报错。

前言

解决方案

  • 环

可以使用WeakMap结构来存储已拷贝的对象。每次执行拷贝时,先查询WeakMap中是否已存在该对象,若存在则直接返回。改造后的deepCopy函数如下:


function deepCopy(obj, hash = new WeakMap()) {
    if(hash.has(obj)) return hash.get(obj)
    let cloneObj = Array.isArray(obj) ? [] : {}
    hash.set(obj, cloneObj)
    for (let key in obj) {
        cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
    }
    return cloneObj
}

拷贝环结果:

前言

特殊对象的拷贝

解决特殊对象拷贝较为复杂,因为需要特别处理的对象类型众多。参考MDN的结构化克隆算法,并结合解决循环引用的方案,实现如下:


// 只解决date,reg类型,其他的可以自己添加

function deepCopy(obj, hash = new WeakMap()) {
    let cloneObj
    let Constructor = obj.constructor
    switch(Constructor){
        case RegExp:
            cloneObj = new Constructor(obj)
            break
        case Date:
            cloneObj = new Constructor(obj.getTime())
            break
        default:
            if(hash.has(obj)) return hash.get(obj)
            cloneObj = new Constructor()
            hash.set(obj, cloneObj)
    }
    for (let key in obj) {
        cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
    }
    return cloneObj
}

拷贝结果:

前言

完整实现可参考lodash的深拷贝源码。

  • 函数的拷贝

然而,MDN的结构化克隆依然未解决函数的拷贝问题。

前言

目前我想到的一种方法是使用eval进行函数拷贝,但该方法仅对箭头函数有效,对于function(){}形式的函数则会出错。

拷贝函数增加函数类型

前言

拷贝结果

前言

出错类型

前言

后记

JavaScript的深拷贝还存在其他问题,例如如何拷贝原型链上的属性、不可枚举属性以及Error对象等,此处不再赘述。

在日常开发中,建议优先使用JSON方法,因为它已覆盖大部分业务需求,无需过度复杂化。但在面试中,若面试官深入追问,上述解答足以展示你的技术深度。

到此这篇关于JavaScript深拷贝的一些踩坑记录的文章就介绍到这了,更多相关JavaScript深拷贝内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章