如何实现数组的浅拷贝和深拷贝

大家好,这里是修真院前端小课堂,今天给大家分享的是

《如何实现数组的浅拷贝和深拷贝》

如何实现数组的浅拷贝和深拷贝

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 中浅拷贝和深拷贝的实现 

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);

 

【更多内容,可以加入IT交流群565734203与大家一起讨论交流】

【这里是技能树·IT修真院:IT修真院官网,初学者转行到互联网的聚集地】