js中的深拷贝与浅拷贝

217 阅读3分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

深拷贝与浅拷贝

浅拷贝:

创建一个新的对象,这个对象身上有着原始对象的所有属性值。如果属性是基本类型,则拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址。当其中一个对象的属性改变了,另一个对象也会受到影响。 下面就是一个浅拷贝的方法:

function shallowClone(target) {
    let cloneTarget = {};
    for (const key in target) {
        cloneTarget[key] = target[key];
    }
    return cloneTarget;
};

深拷贝:

深拷贝就是将一个对象从内存中完整的拷贝一份,在堆内存中重新开辟一块新的空间存放新对象,这样修改其中一个对象就不会影响另一个对象。

深拷贝的方法

1.JSON.parse(JSON.stringify());

在平常的工作中,我们经常会使用到深拷贝的方法,我们使用最多的就是下面这个方法。

JSON.parse(JSON.stringify());

这种写法非常简单,使用得非常普遍,它的确是可以实现对象的深拷贝,可以满足工作中的绝大多数场景,但是它有很多的缺陷。比如:

  1. 会忽略undefined

  2. 会忽略symbol

  3. 不能拷贝函数

  4. 不能解决循环引用的对象

看一下下面的例子:

let a = {
  age: undefined,
  sex: Symbol('male'),
  jobs: function() {},
  name: 'jack'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "jack"}

在上面的这种情况,这种深拷贝的方法会忽略到函数和undefinedsymbol,这样就是出现问题。

2. $.extend()

在项目中我们经常会使用到jQuery,其实jQuery中的$.extend()也可以实现深拷贝。

let obj = {
    a:1,
    b:2,
    c: {
      x: 0,
      y: 1
    }
};
let cloneObj = $.extend(true, {}, obj);
obj.c.x = 666;
console.log(cloneObj);

在上面的例子中,我们使用jQuery$.extend()方法深拷贝了一份obj对象,然后修改了里层的对象,发现并不会互相影响,所以$.extend()也可以实现深拷贝,只不过需要依赖jQuery库。

手动实现深拷贝的方法

深拷贝的原理也很简单,我们只需要创建一个新的对象,然后将原始对象上的属性依次添加到新的对象上,如果是基本类型的话就直接添加到新对象上,如果是引用类型的就递归直到属性为基本类型。于是我们可以实现一个简单的深拷贝方法:

function _deepClone(target) {
    if (typeof target === 'object') {
      let cloneTarget = Array.isArray(target) ? [] : {};
      for(key in target) {
        cloneTarget[key] = _deepClone(target[key]);
      }
      return cloneTarget
    } else {
      return target
    }
}

但是这个依然存在循环引用的问题,比如下面这个例子:

const target = {
    field1: 1,
    field2: undefined,
    field3: {
        child: 'child'
    },
    field4: [2, 4, 8]
};
target.target = target;

let res = _deepClone(target);
console.log(res);

当我们执行上面代码的时候,控制台会抛出错误:Uncaught RangeError: Maximum call stack size exceeded原因就是上面的对象存在循环引用的情况,即对象的属性间接或直接的引用了自身的情况。

为解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解了循环引用的问题。

function _deepClone(target, map = new Map()) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);
        for (const key in target) {
            cloneTarget[key] = _deepClone(target[key], map);
        }
        return cloneTarget;
    } else {
        return target;
    }
};

这里实现的深拷贝方法只是一个比较简单的深拷贝,其实实现一个真正的深拷贝是很困难的,我们需要考虑好多种情况,比如原型链如何处理、DOM 如何处理、函数怎么处理等等,因为本人水平有限现在只能理解到这里,后续会慢慢实现更加完整的深拷贝方法。