JavaScript深拷贝和浅拷贝

1、基础认识

对于js的对象的深拷贝和浅拷贝,必须先提到的是JavaScript的数据类型。Javascript的数据类型分为两类:基本数据类型和引用数据类型 。

Javascript有五种基本数据类型(也就是简单数据类型),它们分别是:Undefined,Null,Boolean,Number和String,并且基本类型存放在栈内存。还含有一种复杂的数据类型(也叫引用类型)存放在堆内存,就是对象Object和Array。堆内存用于存放由new创建的对象,栈内存存放一些基本的类型的变量和对象的引用变量。

JS 中的浅拷贝与深拷贝,只是针对引用数据类型(Object,Array)的复制问题。
  • 对于基本数据类型

他们的值在内存中占据着固定大小的空间,并被保存在栈内存中。当一个变量向另一个变量复制基本类型的值,会创建这个值的副本。

var a = 1;
var b = a;
b = 2;
console.log(a); //1

上面的代码中,a是基本数据类型(Number), b是a的一个副本,它们两者都占有不同位置但相等的内存空间,只是它们的值相等,若改变其中一方,另一方不会随之改变。

  • 对于复杂数据类型

复杂的数据类型即引用类型,它的值是对象,保存在堆内存中,包含引用类型值的变量实际上包含的不是对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针地址而已,因此两个变量最终都指向同一个对象。

var obj1 = {
   name:'zhang san',
   age: 20
}
var obj2 = obj1;
obj2.name = 'li si';
obj2.job = 'engineer';
console.log(obj1); //{name: "li si", age: 20, job: "engineer"}
console.log(obj2); //{name: "li si", age: 20, job: "engineer"}

我们可以看到obj1赋值给obj2后,但我们改变其中一个对象的属性值,两个对象都发生了改变,根本原因就是obj1和obj2两个变量都指向同一个指针,赋值时只是复制了指针地址,它们指向同一个引用,所以当我们改变其中一个的值就会影响到另一个变量的值。

以上内容还没有涉及到深拷贝和浅拷贝,只作为背景介绍。对于复杂数据类型而言,简单的 ‘=’ 赋值显然会带来不必要的麻烦,那么该如何解决呢?

2、深拷贝和浅拷贝

  • 深拷贝和浅拷贝的区别
    浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制。接下来举例介绍:

    浅拷贝的实现:
    对于数组而言,最简单的可以用slice()和concat()方法实现浅拷贝:

    var arr1 = [1, 2, 3];
    var arr2 = arr1.slice();
    arr2[0] = 0;
    console.log(arr1);//[1,2,3]
    console.log(arr2);//[0,2,3]
    var arr3 = [4,5,6];
    var arr4 = arr3.concat();
    arr4[0] = 0;
    console.log(arr3);//[4,5,6]
    console.log(arr4);//[0,5,6]

    另外可以用ES6扩展运算符实现数组的浅拷贝:

    var arr1 = [1,2,3];
    var arr2 = [...arr1];
    arr2[0] = 0;
    console.log(arr1);//[1,2,3]
    console.log(arr2);//[0,2,3]

    或ES6的Array.from()实现数组的浅拷贝:

    var arr1=[1,2,3];
    var arr2=Array.from(arr1);
    arr2[0] = 0;
    console.log(arr1);  //[1,2,3]
    console.log(arr2);  //[0,2,3]

    或者是通过循环来实现浅拷贝:

    var arr1=[1,2,3,4];
    var arr2=[];
    for(var i=0; i<arr1.length; i++){
         arr2[i]=arr1[i];
    }
    arr1.push(5);
    arr2.push(6);
    console.log(arr1); //[1,2,3,4,5]
    console.log(arr2); //[1,2,3,4,6]

    接下来是对象浅拷贝的实现方法:
    可以用ES6扩展运算符实现对象的浅拷贝:

    var obj1 = {
       name:'zhang san',
       age: 20
    }
    var obj2 = {...obj1};
    obj2.name = 'li si';
    console.log(obj1);//{name: "zhang san", age: 20}
    console.log(obj2);//{name: "li si", age: 20}

    也可以用ES6的Object.assign()实现对象的浅拷贝,

    Object.assign是ES6的新函数。Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

var obj1 = { a: 1, b: 2, c: 3 };
var obj2 = Object.assign({}, obj1);
obj2.b = 0;
console.log(obj1);//{a: 1, b: 2, c: 3}
console.log(obj2);//{a: 1, b: 0, c: 3}

或者通过循环实现对象的浅拷贝:

//浅拷贝
var obj1={
    a:10
}
function copy (obj) {
    //复制关系,而不是引用关系
    var newOBJ={};
    for (var arrt in obj) {//循环复制所有属性,可以称为浅拷贝或者叫浅克隆
        newOBJ[arrt]=obj[arrt];
    };
    return newOBJ;
}

var obj2=copy(obj1);
obj2.a=20;
console.log(obj1.a);//10

浅拷贝解决了上面介绍的 ’ = ’ 赋值存在的问题,但是浅拷贝存在问题:只能拷贝一层。

//举例验证,例1:for循环实现对象的浅拷贝
var obj1={
    a:{b:10}
}
function copy (obj) {
    var newOBJ={};
    for (var arrt in obj) {
        newOBJ[arrt]=obj[arrt];
    };
    return newOBJ;
}
var obj2=copy(obj1);
obj2.a.b=20;
alert(obj1.a.b);//20
//es6实现对象的浅拷贝:
var obj3 = {
    a:{b:10}
}
var obj4 = {...obj3};
obj4.a.b = 20;
console.log(obj3);//a:{b: 20}
console.log(obj4);//a:{b: 20}
//slice()实现数组的浅拷贝:
var arr1 = [1,2,{a:2}];
var arr2 = arr1.slice();
arr2[2].a = 0;
console.log(arr1);//[1,2,{a:0}]
console.log(arr2);//[1,2,{a:0}]
//for循环实现数组的浅拷贝:
var arr1=[1,2,3,{a:6}];
var arr2=[];
for(var i=0; i<arr1.length; i++){
         arr2[i]=arr1[i];
}
arr2[3].a = 0;
console.log(arr1);//[1,2,3,{a:0}]
console.log(arr2);//[1,2,3,{a:0}]
//不在一一举例

深拷贝:解决了浅拷贝只能拷贝一层的问题。

深拷贝的实现:用递归实现深度拷贝!
下面的代码用递归实现了数组和对象的深度拷贝:

function clone(Obj) {   
    var buf;   
    if (Obj instanceof Array) {   
        buf = [];  //创建一个空的数组 
        var i = Obj.length;   
        while (i--) {   
            buf[i] = clone(Obj[i]);   
        }   
        return buf;   
    }else if (Obj instanceof Object){   
        buf = {};  //创建一个空对象 
        for (var k in Obj) {  //为这个对象添加新的属性 
            buf[k] = clone(Obj[k]);   
        }   
        return buf;   
    }else{   
        return Obj;   
    }   
}
```javascript
//举例验证:对象
var obj1={
    a:{b:10}
}
var obj2=clone(obj1);
obj2.a.b=20;
alert(obj1.a.b);//10
//数组:
var arr1 = [1,2,3,{a:6}];
var arr2 = clone(arr1);
arr2[3].a = 0;
console.log(arr1);//[1,2,3,{a:6}];
console.log(arr2);//[1,2,3,{a:0}];

另外,对于对象,深拷贝的实现可以用:用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。var obj2 = JSON.parse(JSON.stringify(obj1)),简单粗暴,可以试一下。

网上有关JS深浅拷贝的解释不太一致,有文章把浅拷贝当做深拷贝,并且这样的文章还不少,用《阅后即瞎》的经典台词做结束语吧,祝您心明眼亮。

阅读更多
个人分类: 深入理解JavaScript
上一篇JavaScript闭包
下一篇git使用 --- push代码到github和常用的git指令
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭