众所周知,在 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);