从数组拷贝来看 Javascript 深复制问题

979 阅读2分钟
原文链接: shrimp6.com

数组复制

需要进行数组复制时,最常用的两个方法是sliceconcat,即:

const arr = [1, 2, 3, 4, 5];	//原数组
const newArr1 = arr.slice(0);
const newArr2 = arr.concat();

这两种方式都是复制原数组的数据并创建了一个新的数组,而不是原数组的引用。

newArr1.push(6);	// [ 1, 2, 3, 4, 5, 6 ]
newArr2[3] = 7;		// [ 1, 2, 3, 7, 5 ]

console.log(arr);	// [ 1, 2, 3, 4, 5 ]

坑来了

考虑下面的例子:

const arr = [{name: 'item1'}, {name: 'item2'}, {name: 'item3'}, {name: 'item4'}];	//原数组
const newArr1 = arr.slice(0);
const newArr2 = arr.concat();

数组复制完了,进行如下操作:

newArr1.forEach((item, i) => {
    item.key = i
});
newArr2[2].name = 'item7';

这时再打印三个数组,结果三个数组竟然是相同的!相同的!相同的!

前方高能

[ { name: 'item1', key: 0 },
  { name: 'item2', key: 1 },
  { name: 'item7', key: 2 },
  { name: 'item4', key: 3 } ]

不是说好的创建新数组,而不是引用呢。

为什么上面的例子就没问题,仔细看看两个例子的区别。

明白了吧。后面这个数组里的元素是对象。

sliceconcat只是复制了数组的第一维,即复制了数组中的对象的引用。

所以我们修改数组元素对象时,其他包含了这个对象引用的数组相应也变化了。

如果进行的是下面操作:

newArr1.push({name: 'item6', key: 6});

打印三个数组就会发现,其他两个数组没有收到影响。

深复制

如何避免上述问题。其实就是要对数组进行深复制,即对数组包含的元素也要根据属性创建新对象,而不是单纯地复制其引用。

深复制的方法有很多种,可以自己实现也可以采用现有的api,下面列举两种:

  • JSON复制法 newArr1 = JSON.parse(JSON.stringify(arr)); 这种方式能够满足基本的深复制要求(如本文数组的深复制),但是对与RegExp类型和Function类型则无法完全满足,而且不支持有循环引用的对象。
  • lodash库 import cloneDeep from 'lodash/cloneDeep'; newArr1 = cloneDeep(arr); 这种方式采用的是结构化克隆算法,相比于JSON复制方法,这种方法支持RegExp类型,文件对象(BlobFileFileList)和ImageData对象,也可以正确的复制有循环引用的对象,但是同样不支持Function类型。