js中深拷贝和浅拷贝的理解

121 阅读4分钟

深拷贝和浅拷贝是针对对象属性为对象的,因为基本数据类型在进行赋值操作时(也就是拷贝)是直接将值赋给了新的变量,也就是该变量是原变量的一个副本

那么这里,先了解一下基本数据类型与引用数据类型的概念了。

基本数据与复杂(引用)数据面试常问基本数据类型有哪些,number,string,boolean,null,undefined,symbol以及未来ES10新增的BigInt(任意精度整数)七类。 引用数据类型(Object类)有常规名值对的无序对象{a:1},数组[1,2,3],以及函数等

给一个只有一层的对象
var a = { a: 'a', b: 'b', c: 'c' }
     b = a
     // 此时如果对b.a进行赋值,我们来看看a.a会不会发生变化
     b.a = '222'
     console.log(b);//{a: '222', b: 'b', c: 'c'}
     console.log(a);//{a: '222', b: 'b', c: 'c'}

此时你会发现a.a变了,b.a也发生变化,这就是引用数据类型只是复制了地址,指向的值一致时堆内存中的那个值

再来看一个,给一个复杂的对象,数据是一层套一层,这时候使用浅拷贝,
相当于只是拷贝了地址过来,拷贝过来的时候,
对深层的数据进行修改的时候,就会发现原来的数据也被修改了,看一下这个例子
var obj = {
         name: "zs",
         age: 20,
         father: [2, 3, 4],
         mother: {
             a: 'a', b: 'b', c: 'c'
         },
         a: function () {
             console.log('深拷贝浅拷贝的理解');
         }
     };
      var objCopy = { ...obj }
      objCopy.father[2] = 5
      console.log(obj.father[2]);//结果5
      console.log(objCopy.father[2]);//结果5

你可以直接使用延展运算符拷贝过来,这是浅拷贝,对数据拷贝的时候只拷贝一层,深层次的只拷贝了地址,这就是浅拷贝

你会发现这两个对象.father[2]的值都变成了5,这就出问题了,我们是想完全拷贝过来,不想只是对原来的数据造成影响,也就是深度的拷贝,但是却发现这种方式不行。同样的使用b = Object.assign({}, a)这个对象的方法也是浅拷贝,也不能实现深拷贝

这种数据怎么实现深拷贝
 var objDeepClone1 = JSON.parse(JSON.stringify(obj))
        console.log(objDeepClone1);
        //结果时{age: 20, father: (3) [2, 3, 5],mother: {a: 'a', b: 'b', c: 'c'},name: "zs"}

a的方法不见了,出问题了,使用这个方法会有如下问题

  • 造成数据丢失和数据异常
  • function、undefined 直接丢失
  • NaN、Infinity 和-Infinity 变成 null
  • RegExp、Error对象只得到空对象;

如果你的数据中没有这些,可以使用这种简单的深度拷贝的方法,尽量避免使用这个吧,使用递归的方式进行拷贝,才是正确的选择,上面的问题基本时可以避免,来看看实例代码:

var obj = {
            name: "zs",
            age: 20,
            father: [2, 3, 4],
            mother: {
                a: 'a', b: 'b', c: 'c'
            },
            a: function () {
                console.log('深拷贝浅拷贝的理解');
            }
        };
 function deepClone (target) {
            // 这一行如果不用三元判断 如果是数组会有bug会被拷贝成伪数组对象
            var tempObj = Array.isArray ? [] : {}
            // 使用for in 遍历对象
            for (var key in target) {
                // 如果这个元素是一个对象,就将这个对象传入deepClone中继续遍历
                if (typeof target[key] === 'object') {
                    tempObj[key] = deepClone(target[key])
                } else {
                    // 如果他不是一个对象,就将他们的值进行赋值
                    tempObj[key] = target[key]
                }
            }
            return tempObj //需要一个退出条件
        }
        var obj1=deepClone(obj)
        console.log(obj);

这种递归也会遇到上面同样的问题: 数据丢失和异常处理 处理函数 Symbol 正则 Error 等数据类型正常拷贝,但是做好在递归的时候,做一些判断,如下

// 日期格式
if (obj instanceof Date) {
  return new Date(obj)
}
// Symbol
if (obj instanceof Symbol) {
  return new Symbol(obj)
}
// 函数
if (obj instanceof Function) {
  return new Function(obj)
}
// 正则
if (obj instanceof RegExp) {
  return new RegExp(obj)
}

循环引用问题

数据自己引用自己,此时拷贝就会进入死循环,

解决思路:

将每次拷贝的数据进行存储,每次在拷贝之前,先看该数据是否拷贝过,如果拷贝过,直接返回,不在拷贝,如果没有拷贝,对该数据进行拷贝并记录该数据以拷贝。