美文网首页
js的深拷贝和浅拷贝

js的深拷贝和浅拷贝

作者: 魅眼ALong | 来源:发表于2019-07-30 17:26 被阅读0次

    学习之前,先了解下知识,js 的数据类型

    堆和栈的区别

    其实深拷贝和浅拷贝的主要区别就是其在内存中的存储类型不同。
    堆和栈都是内存中划分出来用来存储的区域。

    栈(stack)为自动分配的内存空间,它由系统自动释放;
    而堆(heap)则是动态分配的内存,大小不定也不会自动释放。

    数据类型
    • 基本数据类型:String、Number、Boolean、Symbol、Undefined、Null
    • 引用数据类型:Object
    • 基本数据类型存放在栈中:存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。
      基本数据类型值不可变:基本数据类型的值是不可变的,动态修改了基本数据类型的值,它的原始值也是不会改变的
      例如:
        var str = "abc";
        console.log(str[1]="f");    // f
        console.log(str);           // abc
    
    • 引用类型存放在堆中
      引用类型(object)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配
      image.png

    引用数据类型值可变

        var a = [1,2,3];
        a[1] = 5;
        console.log(a[1]); // 5
    

    浅拷贝 VS深拷贝

    • 传值和传址(通过基本数据和引用数据类型的却别之后,我们就应该能明白传值与传址的区别了)。
      在我们进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中
    var a = 10;
    var b = a;
    a ++ ;
    console.log(a); // 11
    console.log(b); // 10
    
    image.png

    所以说,基本类型的赋值的两个变量是两个独立相互不影响的变量。

    但是引用类型的赋值是传址。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响

    var a = {}; // a保存了一个空对象的实例
    var b = a;  // a和b都指向了这个空对象
    
    a.name = 'jozo';
    console.log(a.name); // 'jozo'
    console.log(b.name); // 'jozo'
    
    b.age = 22;
    console.log(b.age);// 22
    console.log(a.age);// 22
    
    console.log(a == b);// true
    
    image.png

    浅拷贝

    看完上面的文章,其实上面的代码是我们常用的赋值引用,还不能算浅拷贝
    那么

    • 赋值和浅拷贝有什么区别?
        var obj1 = {
            'name' : 'zhangsan',
            'age' :  '18',
            'language' : [1,[2,3],[4,5]],
        };
    
        var obj2 = obj1;
    
    
        var obj3 = shallowCopy(obj1);
        function shallowCopy(src) {
            var dst = {};
            for (var prop in src) {
                if (src.hasOwnProperty(prop)) {
                    dst[prop] = src[prop];
                }
            }
            return dst;
        }
    
        obj2.name = "lisi";
        obj3.age = "20";
    
        obj2.language[1] = ["二","三"];
        obj3.language[2] = ["四","五"];
    
        console.log(obj1);  
        //obj1 = {
        //    'name' : 'lisi',
        //    'age' :  '18',
        //    'language' : [1,["二","三"],["四","五"]],
        //};
    
        console.log(obj2);
        //obj2 = {
        //    'name' : 'lisi',
        //    'age' :  '18',
        //    'language' : [1,["二","三"],["四","五"]],
        //};
    
        console.log(obj3);
        //obj3 = {
        //    'name' : 'zhangsan',
        //    'age' :  '20',
        //    'language' : [1,["二","三"],["四","五"]],
        //};
    
    • obj1 原始数据
    • obj2 赋值数据
    • obj3 浅拷贝数据
      接下来说下浅拷贝。
      为什么改变了赋值得到的对象 obj2 和浅拷贝得到的 obj3 中的 language) 属性的第二个值和第三个值(language 是一个数组,也就是引用类型)。结果见输出,可以看出来,无论是修改赋值得到的对象 obj2 和浅拷贝得到的 obj3 都会改变原始数据。

    【因为浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据。所以就会出现改变浅拷贝得到的 obj3 中的引用类型时,会使原始数据得到改变。】
    用一句话简单理解 :

    浅拷贝就是拷贝了一层,除了对象是拷贝的引用类型,其他(一开始讲到的基本数据类型)都是直接将值传递,有自己的内存空间的

    深拷贝:将 B 对象拷贝到 A 对象中,包括 B 里面的子对象,
    浅拷贝:将 B 对象拷贝到 A 对象中,但不包括 B 里面的子对象

    深拷贝

    看完对浅拷贝的理解,可以知道:深拷贝就是对对象以及对象的所有子对象进行拷贝。
    接下来改探讨的就是如何对对象进行深拷贝?

    • 1、用 JSON.stringify 把对象转成字符串,再用 JSON.parse 把字符串转成新的对象(使用JSON)。
    var obj1 = { body: { a: 10 } };
    var obj2 = JSON.parse(JSON.stringify(obj1));
    obj2.body.a = 20;
    console.log(obj1);
    // { body: { a: 10 } } <-- 沒被改到
    console.log(obj2);
    // { body: { a: 20 } }
    console.log(obj1 === obj2);
    // false
    console.log(obj1.body === obj2.body);
    // false
    

    坏处:它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。所以只适合 Number, String, Boolean, Array 的扁平对象

    • 2、递归拷贝
    function deepClone(obj) {
      let objClone = Array.isArray(obj) ? [] : {};
      if (obj && typeof obj === "object") {
        for (key in obj) {
          if (obj.hasOwnProperty(key)) {
            //判断ojb子元素是否为对象,如果是,递归复制
            if (obj[key] && typeof obj[key] === "object") {
              objClone[key] = deepClone(obj[key]);
            } else {
              //如果不是,简单复制
              objClone[key] = obj[key];
            }
          }
        }
      }
      return objClone;
    } 
    
    var obj1 = {
       a: 1,
       b: 2,
       c: {
         d: 3
       }
    }
    var obj2 = deepClone(obj1);
    obj2.a = 3;
    obj2.c.d = 4;
    alert(obj1.a); // 1
    alert(obj2.a); // 3
    alert(obj1.c.d); // 3
    alert(obj2.c.d); // 4
    

    或者使用es6中的 Object.create()方法

    function deepClone(initalObj, finalObj) {    
      var obj = finalObj || {};    
      for (var i in initalObj) {        
        var prop = initalObj[i];        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
        if(prop === obj) {            
          continue;
        }        
        if (typeof prop === 'object') {
          obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
        } else {
          obj[i] = prop;
        }
      }    
      return obj;
    }
    

    参考文章

    相关文章

      网友评论

          本文标题:js的深拷贝和浅拷贝

          本文链接:https://www.haomeiwen.com/subject/wjpnrctx.html