JavaScript深拷贝的一些踩坑记录

前言

之前去一家公司面试的时候,面试官问了我一个问题,说:"如何才能深拷贝一个对象"。当时我心里有些窃喜,这么简单的问题还用想吗?于是脱口而出:"平时常用的有两种办法,第一种用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))

输出结果:

可以从中看出,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
}

结果:

结论

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

什么是环?

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

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的方法对函数进行拷贝,但是这种方法只能对箭头函数生效,如果是fun(){}这种形式的则会出错

拷贝函数增加函数类型

拷贝结果

出错类型

后记

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

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

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

(0)

相关推荐

  • javascript 深拷贝

    我们先看一下浅复制的缺陷,不知多少人中过招呢? 复制代码 代码如下: var oOriginal = { memNum: 1, // number memStr: "I am a string", // string memObj: { test1: "Old value" // we'll test }, memArr: [ // array "a string", // string member of array { // object m

  • js对象浅拷贝和深拷贝详解

    本文为大家分享了JavaScript对象的浅拷贝和深拷贝代码,供大家参考,具体内容如下 1.浅拷贝 拷贝就是把父对像的属性,全部拷贝给子对象. 下面这个函数,就是在做拷贝: var Chinese = { nation:'中国' } var Doctor = { career:'医生' } function extendCopy(p) { var c = {}; for (var i in p) { c[i] = p[i]; } c.uber = p; return c; } 使用的时候,这样写

  • js 深拷贝函数

    function objectClone(obj,preventName){ if((typeof obj)=='object'){ var res=(!obj.sort)?{}:[]; for(var i in obj){ if(i!=preventName) res[i]=objectClone(obj[i],preventName); } return res; }else if((typeof obj)=='function'){ return (new obj()).construct

  • JS实现数组深拷贝的方法分析

    本文实例讲述了JS实现数组深拷贝的方法.分享给大家供大家参考,具体如下: 最近在网上看到一篇关于js数组复制最有效的方法是直接使用slice和concat方法.这2个方法的确是最快的把数组成功复制,而不是引用.可以运行实例: <script type="text/javascript"> <!-- var arr1=["1","2","3"],arr2; arr2=arr1.slice(0); arr1[0]

  • javascript深拷贝(deepClone)详解

    javascript深拷贝是初学者甚至有经验的开发着,都会经常遇到问题,并不能很好的理解javascript的深拷贝. 深拷贝(deepClone)? 与深拷贝相对的就是浅拷贝,很多初学者在接触这个感念的时候,是很懵逼的. 为啥要用深拷贝? 在很多情况下,我们都需要给变量赋值,给内存地址赋予一个值,但是在赋值引用值类型的时候,只是共享一个内存区域,导致赋值的时候,还跟之前的值保持一直性. 看一个具体的例子 // 给test赋值了一个对象 var test = { a: 'a', b: 'b' }

  • JavaScript基础心法 深浅拷贝(浅拷贝和深拷贝)

    前言 说到深浅拷贝,必须先提到的是JavaScript的数据类型,之前的一篇文章JavaScript基础心法--数据类型说的很清楚了,这里就不多说了. 需要知道的就是一点:JavaScript的数据类型分为基本数据类型和引用数据类型. 对于基本数据类型的拷贝,并没有深浅拷贝的区别,我们所说的深浅拷贝都是对于引用数据类型而言的. 浅拷贝 浅拷贝的意思就是只复制引用,而未复制真正的值. const originArray = [1,2,3,4,5]; const originObj = {a:'a'

  • JavaScript 中对象的深拷贝

    对象的深拷贝与浅拷贝的区别如下: 浅拷贝:仅仅复制对象的引用,而不是对象本身: 深拷贝:把复制的对象所引用的全部对象都复制一遍. 一. 浅拷贝的实现 浅拷贝的实现方法比较简单,只要使用是简单的复制语句即可. 1.1 方法一:简单的复制语句 /* ================ 浅拷贝 ================ */ function simpleClone(initalObj) {     var obj = {};     for ( var i in initalObj) {   

  • javascript深拷贝、浅拷贝和循环引用深入理解

    一.为什么有深拷贝和浅拷贝? 这个要从js中的数据类型说起,js中数据类型分为基本数据类型和引用数据类型. 基本类型值指的是那些保存在栈内存中的简单数据段,即这种值是完全保存在内存中的一个位置.包含Number,String,Boolean,Null,Undefined ,Symbol. 引用类型值指的是那些保存在堆内存中的对象,所以引用类型的值保存的是一个指针,这个指针指向存储在堆中的一个对象.除了上面的 6 种基本数据类型外,剩下的就是引用类型了,统称为 Object 类型.细分的话,有:O

  • JavaScript数组深拷贝和浅拷贝的两种方法

    例如这个例子: 复制代码 代码如下: var arr = ["One","Two","Three"]; var arrto = arr;arrto[1] = "test";document.writeln("数组的原始值:" + arr + "<br />");//Export:数组的原始值:One,test,Threedocument.writeln("数组的新值

  • JS中实现浅拷贝和深拷贝的代码详解

    (一)JS中基本类型和引用类型 JavaScript的变量中包含两种类型的值:基本类型值 和 引用类型值,在内存中的表现形式在于:前者是存储在栈中的一些简单的数据段,后者则是保存在堆内存中的一个对象. 基本类型值 在JavaScript中基本数据类型有 String , Number , Undefined , Null , Boolean ,在ES6中,又定义了一种新的基本数据类型 Symbol ,所以一共有6种. 基本类型是按值访问的,从一个变量复制基本类型的值到另一个变量后,这两个变量的值

随机推荐