拷贝分为浅拷贝和深拷贝,在JavaScript中能够实现这两种拷贝的方式也是多种多样。以下是一维数组实现深拷贝和浅拷贝的各种方式。
一、浅拷贝
1、赋值
赋值是最直接的一种浅拷贝。
let arr3 = [1,2,3]
let arr4 = arr3
arr4[0] = 11
console.log('arr3:',arr3); // [11,2,3]
2、copyWithin()方法——ES6新增
let arr3 = [1, 2, 3]
let arr4 = arr3.copyWithin(0,0)
arr4[0] = 11
console.log('修改arr4:', arr4); // 11,2,3
console.log('arr3:', arr3); // 11,2,3
二、深拷贝(针对一维纯值数组)
1、concat() 方法
concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。其参数可以是具体的值,也可以是数组对象。可以是任意多个。
arrayObject.concat(arrayX,arrayX,......,arrayX)
参数 描述
arrayX 必需。该参数可以是具体的值,也可以是数组对象。可以是任意多个。
利用concat()方法可以得到一个深拷贝的数组副本。
let arr3 = [1,2,3]
let arr4 = arr3.concat();
arr4[0] = 11
console.log('修改arr4:',arr4); // 11,2,3
console.log('arr3:',arr3); // 1,2,3
2、slice()方法
slice() 方法可从已有的数组中返回选定的元素。返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。该方法并不会修改数组,而是返回一个子数组。
arrayObject.slice(start,end)
参数 描述
start 必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。
end 可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。
利用slice()方法可以得到一个深拷贝的数组副本。
let arr3 = [1,2,3]
let arr4 = arr3.slice()
arr4[0] = 11
console.log('修改arr4:',arr4); // 11,2,3
console.log('arr3:',arr3); // 1,2,3
3、序列化和反序列化方法
利用JSON.parse和JSON.stringify组合得到一个深拷贝的数组副本。
let arr3 = [1,2,3]
let arr4 = JSON.parse(JSON.stringify(arr3))
arr4[0] = 11
console.log('修改arr4:',arr4); // 11,2,3
console.log('arr3:',arr3); // 1,2,3
注:此方法适用于Oject的深度拷贝,因为Array属于Oject类型,所以也适用于此处;需要注意的是:作为Oject的深度拷贝时,要复制的function会直接消失,所以这个方法只能用在单纯只有数据的对象。
4、扩展运算符——ES6新增
ES6新增的扩展运算符可以方便的实现数组的深拷贝(克隆)。
let arr3 = [1,2,3]
let arr4 = [...arr3]
arr4[0] = 11
console.log('修改arr4:',arr4); // 11,2,3
console.log('arr3:',arr3); // 1,2,3
let arr3 = [1,2,3]
let [...arr4] = arr3
arr4[0] = 11
console.log('修改arr4:',arr4);
console.log('arr3:',arr3);
5、Array.from——ES6新增
let arr1 = [1,2,3]
let arr2 = Array.from(arr1)
arr2[0] = 11
console.log('修改learr2');
console.log('arr1是否变化',arr1); // 验证结果:Array.from返回的是一个深度拷贝数组
6、map()方法
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
map() 方法按照原始数组元素顺序依次处理元素。
注意: map() 不会对空数组进行检测。 map() 不会改变原始数组。
array.map(function(currentValue,index,arr), thisValue)
参数 描述
function(currentValue, index,arr) 必须。函数,数组中的每个元素都会执行这个函数
函数参数:
参数 描述
currentValue 必须。当前元素的值
index 可选。当前元素的索引值
arr 可选。当前元素属于的数组对象
thisValue 可选。对象作为该执行回调时使用,传递给函数,用作 "this" 的值。
如果省略了 thisValue,或者传入 null、undefined,那么回调函数的 this 为全局对象。
let arr3 = [1,2,3]
let arr4 = arr3.map(item => item)
arr4[0] = 11
console.log('修改arr4:',arr4); // 11,2,3
console.log('arr3:',arr3); // 1,2,3
7、Object.assign()方法——ES6新增
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
let arr3 = [1,2,3]
let arr4 = []
Object.assign(arr4,arr3)
arr4[0] = 11
console.log('修改arr4:',arr4); // 11,2,3
console.log('arr3:',arr3); // 1,2,3
注意:Object.assign()方法实行的是浅拷贝,而不是深拷贝。如果数组中的项是一个对象,那么目标数组中的该项拷贝得到的是这个对象的引用。
let arr3 = [{name:'aaa'},2,3]
let arr4 = []
Object.assign(arr4,arr3)
arr4[0].name = 11
console.log('修改arr4:',arr4); // {name:11},2,3
console.log('arr3:',arr3); // {name:11},2,3
8、扩展原型链
Array.prototype.clone = function () {
let arr = []
for (let i = 0, len = this.length; i < len; i++) {
arr.push(this[i])
}
return arr
}
let arr3 = [1, 2, 3]
let arr4 = arr3.clone()
arr4[0] = 11
console.log('修改arr4:', arr4); // 11,2,3
console.log('arr3:', arr3); // 1,2,3
9、filter方法
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
注意: filter() 不会对空数组进行检测。filter() 不会改变原始数组。
array.filter(function(currentValue,index,arr), thisValue)
参数 描述
function(currentValue, index,arr) 必须。函数,数组中的每个元素都会执行这个函数。返回true表示保留该元素(通过测试),false则不保留
函数参数:
参数 描述
currentValue 必须。当前元素的值
index 可选。当前元素的索引值
arr 可选。当前元素属于的数组对象
thisValue 可选。对象作为该执行回调时使用,传递给函数,用作 "this" 的值。
如果省略了 thisValue ,"this" 的值为 "undefined"
返回值:返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组。
let arr3 = [1, 2, 3]
let arr4 = arr3.filter(item => true)
arr4[0] = 11
console.log('修改arr4:', arr4); // 11,2,3
console.log('arr3:', arr3); // 1,2,3
总结
以上各种深拷贝方式只针对一维并且每一项值为数值和字符串的数组。多维数组或者一维但包含对象的数组除了第三种方式外,其他方式并不能实行深拷贝,实行的是浅拷贝。
三、多维数组的深拷贝
function deepcopy(obj) {
let out = [],len = obj.length;
for (let i = 0; i < len; i++) {
if (obj[i] instanceof Array){
out[i] = deepcopy(obj[i]);
} else {
out[i] = obj[i];
}
}
return out;
}
另一种方式:采用上面的第三种方式:序列化和反序列化结合实现。