前端踩坑之数组拷贝

2,370 阅读2分钟

众所周知,在 JavaScript 中对象之间的赋值,只是拷贝对象的引用,也就是浅拷贝,不是真正意义上的拷贝,两个对象之间还会相互影响。然而,我一直忽略了一个问题,数组之间的拷贝会不会也有同样的问题呢?果不其然,当我再次用常用的 array.concat() 方法拷贝数组时,踩到坑了。当我改变一个数组里面对象属性的时候,另一个数组里的对象也跟着改变了。。。

数组的拷贝方法有很多,按结果来看就是浅拷贝和深拷贝两种。

一、直接赋值

let arr2 = arr1;

这种拷贝方法是浅拷贝,数组arr1和数组arr2共用同一内存,其中一个数组改变,另一个数组也会跟着改变。

二、使用 slice(),concat(),assign() 方法

let arr2 = arr1.slice(0);
let arr3 = [].concat(arr1);
let arr4 = Object.assign({} , arr1);

这几种方法产生的效果是一样的。

  • 若原数组中不存在引用类型,修改新数组,不会影响到原数组的值。
  • 若原数组中存在引用类型,修改新数组,影响到原数组的值

原因是这样拷贝数组中非引用类型的值属于拷贝,引入类型的值属于拷贝。

三、深拷贝方法

1. JSON复制法

let arr2 = JSON.parse(JSON.stringify(arr1));

但是这种方式有一定的局限性,就是数组必须遵从JSON的格式,当遇到层级较深,且序列化数组不完全符合JSON格式时,使用JSON的方式进行深拷贝就会出现问题。

所有函数及原型成员都会被有意忽略,不体现在结果中。此外,值为 undefined 的任何属性也都会被跳过。结果中最终都是值为有效 JSON 数据类型的实例属性。

2. 使用递归

function deepClone(source) {
  // 递归终止条件
  if (!source || typeof source !== 'object') {
    return source;
  }
  var targetObj = source.constructor === Array ? [] : {};
  for (var key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key) {
      if (source[key] && typeof source[key] === 'object') {
        targetObj[key] = deepClone(source[key]);
      } else {
        targetObj[key] = source[key];
      }
    }
  }
  return targetObj;
}

对于 Function 类型,这里是直接复制的,任然是共享一个内存地址。因为函数更多的是完成某些功能,对函数的更改可能就是直接重新赋值,一般情况下不考虑深拷贝。 上面的深拷贝只是比较简单的实现,没有考虑很复杂的情况,比如:

  • 其他引用类型:Function,Date,RegExp 的拷贝
  • 对象中存在循环引用(Circular references)会导致调用栈溢出
  • 通过闭包作用域来实现私有成员的这类对象不能真正的被拷贝。

3. 使用lodash

import cloneDeep from 'lodash/cloneDeep'; 
let arr2 = cloneDeep(arr1); 

参考

1.深入理解 JavaScript 对象和数组拷贝