在使用JavaScript对数组进行操作的时候,我们经常需要将数组进行备份,事实证明如果只是简单的将它赋予其他变量,那么我们只要更改其中的任何一个,然后其它的也会跟着改变,这就导致了问题的发生。原因是内存中仅保留一份数据。这时候需要制作一份数据的副本。只有复杂类型变量(引用类型)存在深拷贝与浅拷贝的问题,而基本类型没有深拷贝的概念。
“堆内存”和“栈内存”
首先JavaScript中的变量分为基本类型和引用类型。基本类型就是保存在栈内存中的简单数据段,而引用类型指的是那些保存在堆内存中的对象。
基本类型
基本类型有Undefined、Null、Boolean、Number 和String。这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,我们通过按值来访问的。
引用类型
引用类型,值大小不固定,栈内存中存放地址指向堆内存中的对象。是按引用访问的。栈内存中存放的只是该对象的访问地址,在堆内存中为这个值分配空间。由于这种值的大小不固定,因此不能把它们保存到栈内存中。但内存地址大小的固定的,因此可以将内存地址保存在栈内存中。这样,当查询引用类型的变量时,先从栈中读取内存地址, 然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。
当我们看到一个变量类型是已知的,就分配在栈里面,比如INT,Double等。其他未知的类型,比如自定义的类型,因为系统不知道需要多大,所以程序自己申请,这样就分配在堆里面。
JS数组的浅拷贝
简单的赋值就是浅拷贝。因为对象和数组在赋值的时候都是引用传递。赋值的时候只是传递一个指针。
1 | var a = [1,2,3]; |
浅拷贝很容易,但是很多时候我们需要原样的把数组或者对象复制一份,在修改值的时候,不改变初始对象的值。这个时候就需要使用深拷贝。
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之前,不复制任何元素到新数组中。
实例:
1 | var a = [1,2,3,4,5]; |
js的concat函数
concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
【语法】arrayObject.concat(arrayX,arrayX,……,arrayX)
【参数】arrayX–必需:该参数可以是具体的值,也可以是数组对象。可以是任意多个。
【说明】
返回一个新的数组。该数组是通过把所有 arrayX 参数添加到 arrayObject 中生成的。如果要进行 concat()操作的参数是数组,那么添加的是数组中的元素,而不是数组。
实例:
1 | var a = [1,2,3]; |
\1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
## js遍历数组的方法
js遍历数组的方法
```javascript
var a = [1,2,3];
var b = [];
function deepCopy(arry1,arry2) {
for(i=0;i < arry1.length;i++) {
arry2[i] = arry1[i];
}
}
deepCopy(a,b);
a[0] = "A";
console.log(a,b);
slice()、concat()的局限性
1 | var a = [1,2,{"name":"张三"},{"name":"李四"},[4,5]]; |
由上面的例子可以看出,由于数组内部存在对象和数组,当改变对象属性和内部数组的元素后,深拷贝的b和c同样也发生了改变。因此,slice和concat这两个方法,仅适用于对不包含引用对象的一维数组的深拷贝。