数组和对象的浅拷贝,深拷贝

2018-06-14  本文已影响0人  _littleTank_

在说数组和对象的浅拷贝,深拷贝的问题前我们先了解一下javaScript的变量类型。
(1)基本类型:
5种基本数据类型Undefined、Null、Boolean、Number 和 String,变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。

(2)引用类型:
存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

JavaScript存储对象都是存地址的,所以浅拷贝会导致 obj1 和obj2 指向同一块内存地址。
改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变,
而深拷贝是开辟一块新的内存地址,将原对象的各个属性逐个复制进去。
对拷贝对象和源对象各自的操作互不影响。

先看下下边例子:

var a = 10;
var b = a;
b = 20;
console.log(a);//10
console.log(b);//20

上边代码在修改b的值时并不会改到a。

但是对象和数组就不一样了,对象和数组是按引用传值,具体如下:

//浅拷贝,双向改变,指向同一片内存空间
var obj = {'age':20}
var newobj = obj
newobj.age= 30
console.log(obj.age); //30
console.log(newobj.age); // 30

上边的代码在修改新对象newobj的age值,原来的对象obj的age值也改变了,为什么呢?因为对象和数组是按引用传值,所以他们根本是同一个对象,上面代码中,newobj 并不是obj 的克隆,而是指向同一份数据的另一个指针。修改newobj ,会直接导致obj 的变化。这就是所谓的浅拷贝。

var obj1 = { a: 10, b: 20, c: 30 }; 
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 100;
console.log(obj1); // { a: 10, b: 20, c: 30 }  这里b 沒被改到
console.log(obj2); // { a: 10, b: 100, c: 30 }

上面的代码修改拷贝的对象并不会影响原来的对象,这就是属于深拷贝的类型。
关于浅拷贝:浅拷贝有很多种方式,但是最常用的一种就是“=”赋值类型,如下

var obj1 = {'age':30}
var obj2 = obj1

1、数组的拷贝

(注意下边这几种数组拷贝其实是数组的浅拷贝,但是我们可以把他当做数组的深拷贝来用,但事实是浅拷贝)

方法1:循环遍历
var arr1 = [1,2,3]
var arr2 = []
for(var i=0;i<arr1.length;i++){
  arr2[i] = arr1[i]
}
arr2[1]=100
console.log(arr1 );//[1,2,3]
console.log(arr2 );//[1,100,3]
方法2:concat方法,该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
var arr1 = [1,2,3]
var arr2 = [].concat(arr1)
arr2[1]=100
console.log(arr1 );//[1,2,3]
console.log(arr2 );//[1,100,3]
方法3:es6扩展运算符
var arr1 = [1,2,3]
var arr2 = [...arr1]
arr2[1]=100
console.log(arr1);//[1,2,3]
console.log(arr2);//[1,100,3]

到这里或许你已经发现问题了,我们给的例子中的数组都是一维数组,如果换成二维数组,上边的方法还能用吗?答案是否定的,换成了多维数组后上边的方法已经不能适用。

2、对象的拷贝

对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

DeepCopy: 深拷贝

深拷贝的实现一般是递归属性遍历

一般来说,在JavaScript中考虑复合类型的深层复制的时候,往往就是指对于Date、Object与Array这三个复合类型的处理。我们能想到的最常用的方法就是先创建一个空的新对象,然后递归遍历旧对象,直到发现基础类型的子节点才赋予到新对象对应的位置。不过这种方法会存在一个问题,就是JavaScript中存在着神奇的原型机制,并且这个原型会在遍历的时候出现,然后原型不应该被赋予给新对象。那么在遍历的过程中,我们应该考虑使用hasOenProperty方法来过滤掉那些继承自原型链上的属性:

function deepClone(obj) {
    var copy;
    if (null == obj || "object" != typeof obj) return obj;
    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = deepClone(obj[i]);
        }
        return copy;
    }
    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = deepClone(obj[attr]);
        }
        return copy;
    }
    throw new Error("Unable to copy obj! Its type isn't supported.");
}

我们来验证一下,如下代码

let arr = [1,[2,4],3]
let a = deepClone(arr)//这里deepClone函数是上边写的那个深度复制函数
a[0]=100

console.log(arr)  
console.log(a)

结果如下图


0.png
上一篇下一篇

猜你喜欢

热点阅读