如何实现数组的浅拷贝和深拷贝
1.背景介绍
在JavaScript中,对于Object和Array这类引用类型值,当从一个变量向另一个变量复制引用类型值时,这个值的副本其实是一个指针,两个变量指向同一个堆对象,改变其中一个变量,另一个也会受到影响。
这种拷贝分为两种情况:拷贝引用和拷贝实例,也就是我们说的浅拷贝和深拷贝
2.知识剖析
基本类型:
5种基本数据类型Undefined、Null、Boolean、Number 和 String,变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。
引用类型:
存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。
JavaScript存储对象都是存地址的,所以浅拷贝会导致 obj1 和obj2
指向同一块内存地址。改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变,而深拷贝是开辟一块新的内存地址,将原对象的各个属性逐个复制进去。对拷贝对象和源对象各自的操作互不影响。
JS数组的浅拷贝
简单的赋值就是浅拷贝。因为对象和数组在赋值的时候都是引用传递。赋值的时候只是传递一个指针。
var a = [1,2,3];
var b =a ;
console.log(a,b);
b[0]="a";
console.log(a);
console.log(b);
浅拷贝很容易,但是很多时候我们需要原样的把数组或者对象复制一份,在修改值的时候,不改变初始对象的值。这个时候就需要使用深拷贝。
JS数组的深拷贝
方法一:js的slice函数
slice() 方法可从已有的数组中返回选定的元素。
【语法】arrayObject.slice(start,end)
【参数】arrayObj--必选项:一个Array对象。start--必选项:arrayObj中所指定的部分的开始元素是从零开始计算的下标。end--可选项:arrayObj中所指定的部分的结束元素是从零开始计算的下标。
【说明】
slice 方法返回一个Array对象,其中包含了arrayObj的指定部分。slice方法一直复制到end所指定的元素,但是不包括该元素。如果start为负,将它作为length +
start处理,此处length为数组的长度。如果end为负,就将它作为length + end处理,此处length为数组的长度。如果省略end ,那么slice方法将一直复制到 arrayObj
的结尾。如果end出现在start之前,不复制任何元素到新数组中。
实例:
var a = [1,2,3,4,5];
var b = a.slice(0,2);
var c = a.slice(-3,-1);
var d = a.slice(-1,-3);
console.log(b,c,d);
方法二:js的concat函数
concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
【语法】arrayObject.concat(arrayX,arrayy,......,arrayN)
【参数】arrayX--必需:该参数可以是具体的值,也可以是数组对象。可以是空值,可以是任意多个。
【说明】
返回一个新的数组。该数组是通过把所有 arrayX 参数添加到 arrayObject 中生成的。如果要进行 concat()操作的参数是数组,那么添加的是数组中的元素,而不是数组。
实例:
var a = [1,2,3];
var b = a.concat(4,5);
console.log(b);
var c = [6,7];
var d = a.concat(c);
console.log(d);
3.常见问题
除了上述两个方法外,还有没有其他的深拷贝方法?
4.解决方案
js遍历数组的方法:
var arr1=[1,2,3,4,5],arr2=[];
arr1.forEach(function(val,i){
arr2[i]=val;
})
console.log(arr1,arr2);
arr2[0]="a";
console.log(arr1,arr2);
利用JSON格式
var a=[[1,2,3],4,5,6];
var b=JSON.parse(JSON.stringify(a));
console.log(a,b);
a[0][0]="a";
console.log(a,b);
这种方法使用较为简单,可以满足基本的深拷贝需求,而且能够处理JSON格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深拷贝(而且会直接丢失相应的值)。
5.编码实战
6.扩展思考
slice()、concat()的局限性在哪里?
var arr1=[[1,2,3],4,5,6];
var arr2=arr1.slice();
console.log(arr1,arr2);
arr2[0][0]="a";
console.log(arr1,arr2);
var arr1=[[1,2,3],4,5,6];
var arr2=arr1.concat();
console.log(arr1,arr2);
arr2[0][0]="a";
console.log(arr1,arr2);
由上面的例子可以看出,slice和concat这两个方法,仅适用于对不包含引用对象的一维数组的深拷贝
7.参考文献
参考1:JavaScript中的浅拷贝和深拷贝
参考2:javaScript中浅拷贝和深拷贝的实现
8.更多讨论
还有什么方法实现多维数组的深拷贝?
咱们可以对刚才的数组循环改写一下:
function deepCopy(arr1,arr2){
arr1.forEach(function(val,i){
if(val instanceof Array){
arr2[i]=[];
val.forEach(function(val2,j){
if(val2 instanceof Array){
deepCopy(arr1[j],arr2[j]);
}else{
arr2[i][j]=val2
}
})
}else{
arr2[i]=val;
}
})
}
var a=[[[1],2,3],4,5,6],b=[];
deepCopy(a,b);
console.log(a,b);
b[0][0][0]="a";
console.log(a,b);