JavaScript深拷贝的一些坑

简介: 之前去一家公司面试的时候,面试官问了我一个问题,说:"如何才能深拷贝一个对象"。当时我心里有些窃喜,这么简单的问题还用想吗?于是脱口而出:"平时常用的有两种办法,第一种用JSON.parse(JSON.stringify(obj)),第二种可以使用for...in加递归完成"。

前言

之前去一家公司面试的时候,面试官问了我一个问题,说:"如何才能深拷贝一个对象"。当时我心里有些窃喜,这么简单的问题还用想吗?于是脱口而出:"平时常用的有两种办法,第一种用JSON.parse(JSON.stringify(obj)),第二种可以使用for...in加递归完成"。面试官听了以后点了点头觉得挺满意的。 当时我也并没有太过在乎这个问题,直到前段时间又想起这个问题,发现上面说的两种方法都是有Bug的。

提出问题

那么上面所说的Bug是什么呢?

  • 特殊对象拷贝

首先让我们试想有这么一个对象,在不考虑普通类型的情况下,它有如下成员:




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

然后我们用上面两种方式分别拷贝一次

JSON法


JSON.parse(JSON.stringify(obj))

输出结果:
489daa1361cbb57373a3a0abb5b777c05a7bd815

可以从中看出,obj中的普通对象和数组都能拷贝,然而date对象成了字符串,函数直接就不见了,正则成了一个空对象。

再来看看for...in加递归的方法

递归


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
}

结果:
c9f095d6c1a4bfefb02707f32e2f0dcb8e3e7d9a
结论

通过上面的测试可知,这两个方法都无法拷贝函数,datereg类型的对象;

什么是环?

环就是对象循环引用,导致自己成为一个闭环,例如下面这个对象:



var a = {}

a.a = a

136b6c8325ddd1268bc44d44a45362faddad134e

使用上面两个方法拷贝一下会直接报错

70d2a7207d2f673c3deac6ee117665d72e70309b

解决方案

可以使用一个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
}


拷贝环结果:

1756c2bc20bfdcdc88e501b005dd4aa7638d6193

  • 特殊对象的拷贝

这个问题的解决比较麻烦,因为需要特别对待的对象种类实在太多,于是我参考了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
}

拷贝结果:

89440f0c55d3ecdc19953f213dd053dd8753d9f9

完整版可以查看lodash深拷贝

  • 函数的拷贝

但是MDN上的结构化拷贝依旧没有解决函数的拷贝

f7a5933ff665fac881cc969c938b19fc020df7ea

目前为止,我只想到使用eval的方法对函数进行拷贝,但是这种方法只能对箭头函数生效,如果是fun(){}这种形式的则会出错

拷贝函数增加函数类型

8b44dd25475226f892d1b69893d15f0f4d7cf1b8

出错类型

5458b6356747f226f3f6428a2a08c0b439d9d8f0

后记

JavaScript的深拷贝还不止上面所说的这些坑,还存在的问题有如何拷贝原型链上的属性?如何拷贝不可枚举属性? 如何拷贝Error对象等等的坑,在这里就不一一赘述了。

不过在日常过程中还是建议使用JSON方法,这个方法已经覆盖了绝大部分的业务需求,所以不需要把简单的事情复杂化,不过面试中如果遇到面试官钻牛角尖对这个问题的解答绝对可以秀他一脸了。



原文发布时间为:2018年06月15日
原文作者:YDJFE
本文来源: 掘金  如需转载请联系原作者

相关文章
|
11月前
|
JSON JavaScript 数据格式
深拷贝和浅拷贝(js的问题)
深拷贝和浅拷贝(js的问题)
56 0
|
6月前
|
JavaScript 前端开发
JavaScript中的深拷贝与浅拷贝
JavaScript中的深拷贝与浅拷贝
74 4
|
7月前
|
JSON JavaScript 数据格式
手写JS实现深拷贝函数
本文介绍了如何实现一个深拷贝函数`deepClone`,该函数可以处理对象和数组的深拷贝,确保拷贝后的对象与原始对象在内存中互不干扰。通过递归处理对象的键值对和数组的元素,实现了深度复制,同时保留了函数类型的值和基础类型的值。
47 3
|
8月前
|
JavaScript 前端开发
JavaScript中的深拷贝与浅拷贝
JavaScript中的深拷贝与浅拷贝
72 2
|
8月前
|
JavaScript 前端开发
JavaScript中的深拷贝和浅拷贝的实现讲解
在JavaScript中,浅拷贝与深拷贝用于复制对象。浅拷贝仅复制基本类型属性,对于引用类型仅复制引用,导致双方共享同一数据,一方修改会影响另一方。深拷贝则完全复制所有层级的数据,包括引用类型,确保双方独立。浅拷贝可通过简单属性赋值实现,而深拷贝需递归复制各层属性以避免共享数据。
110 1
|
10月前
|
JSON JavaScript 前端开发
【JavaScript】JavaScript中的深拷贝与浅拷贝详解:基础概念与区别
JavaScript 中,理解数据拷贝的深浅至关重要。浅拷贝(如扩展运算符`...`、`Object.assign()`)仅复制对象第一层,共享内部引用,导致修改时产生意外联动。深拷贝(如自定义递归函数、`_.cloneDeep`或`JSON.parse(JSON.stringify())`)创建独立副本,确保数据隔离。选择哪种取决于性能、数据独立性和资源需求。深拷贝虽慢,但确保安全;浅拷贝快,但需小心引用共享。在面试中,理解这些概念及其应用场景是关键。
224 4
【JavaScript】JavaScript中的深拷贝与浅拷贝详解:基础概念与区别
|
8月前
|
JavaScript 前端开发
js中浅拷贝和深拷贝的区别
js中浅拷贝和深拷贝的区别
45 0
|
8月前
|
JavaScript 前端开发
js中浅拷贝,深拷贝的实现
js中浅拷贝,深拷贝的实现
53 0
|
11月前
|
JSON JavaScript 前端开发
总结JavaScript中的深拷贝与浅拷贝
总结JavaScript中的深拷贝与浅拷贝
|
9月前
|
JavaScript
js【详解】深拷贝 (含 JSON.parse(JSON.stringify(obj)) 的缺陷,5种手写深拷贝)
js【详解】深拷贝 (含 JSON.parse(JSON.stringify(obj)) 的缺陷,5种手写深拷贝)
270 0
下一篇
oss创建bucket