数组复制
需要进行数组复制时,最常用的两个方法是slice
和concat
,即:
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 } ]
不是说好的创建新数组,而不是引用呢。
为什么上面的例子就没问题,仔细看看两个例子的区别。
明白了吧。后面这个数组里的元素是对象。
slice
和concat
只是复制了数组的第一维,即复制了数组中的对象的引用。
所以我们修改数组元素对象时,其他包含了这个对象引用的数组相应也变化了。
如果进行的是下面操作:
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
类型,文件对象(Blob
、File
、FileList
)和ImageData
对象,也可以正确的复制有循环引用的对象,但是同样不支持Function
类型。