zoukankan      html  css  js  c++  java
  • JavaScript中浅拷贝和深拷贝的区别和实现

    深拷贝和浅拷贝的区别

      浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存; 
      深拷贝(deep copy):复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变。

    var a = 25;

    var b = a;

    b = 10;

    console.log(a);//25

    console.log(b);//10

     

    //浅拷贝

    var obj1 = { a: 10, b: 20, c: 30 };

    var obj2 = obj1;

    obj2.b = 40;

    console.log(obj1);// { a: 10, b: 40, c: 30 }

    console.log(obj2);// { a: 10, b: 40, c: 30 }

     

    //深拷贝

    var obj1 = { a: 10, b: 20, c: 30 };

    var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };

    obj2.b = 40;

    console.log(obj1);// { a: 10, b: 20, c: 30 }

    console.log(obj2);// { a: 10, b: 40, c: 30 }

     

    传值与传址

    上一篇博客说明了什么是内存中的堆、栈以及变量类型,实际上是为这篇服务的,就是为了更好的理解什么是“浅拷贝”和“深拷贝”。

    基本类型与引用类型最大的区别实际就是传值与传址的区别。测试用例:

    var a = [1,2,3,4,5];

    var b = a;

    var c = a[0];

    alert(b);//1,2,3,4,5

    alert(c);//1

    //改变数值       

    b[4] = 6;

    c = 7;

    alert(a[4]);//6

    alert(a[0]);//1

    从上面我们可以得知,当我改变b中的数据时,a中数据也发生了变化;但是当我改变c的数据值时,a却没有发生改变。  

    这就是传值与传址的区别

    因为a是数组,属于引用类型,所以它赋予给b的时候传的是栈中的地址(相当于新建了一个不同名“指针”),而不是堆内存中的对象。而c仅仅是从a堆内存中获取的一个数据值,并保存在栈中。所以b修改的时候,会根据地址回到a堆中修改,c则直接在栈中修改,并且不能指向a堆内存中。

     

    浅拷贝

    前面已经提到,在定义一个对象或数组时,变量存放的往往只是一个地址。当我们使用对象拷贝时,如果属性是对象或数组时,这时候我们传递的也只是一个地址。因此子对象在访问该属性时,会根据地址回溯到父对象指向的堆内存中,即父子对象发生了关联,两者的属性值会指向同一内存空间。

    var a = {
        key1:"11111"
    }
    function Copy(p) {
        var c = {};
        for (var i in p) { 
          c[i] = p[i];
        }
        return c;
    }
    a.key2 = ['小辉','小辉'];
     
    var b = Copy(a);
    b.key3 = '33333';
     
    alert(b.key1);     //1111111
    alert(b.key3);    //33333
    alert(a.key3);    //undefined
     

    a对象中key1属性是字符串,key2属性是数组。a拷贝到b,12属性均顺利拷贝。给b对象新增一个字符串类型的属性key3时,b能正常修改,而a中无定义。说明子对象的key3(基本类型)并没有关联到父对象中,所以undefined。

    b.key2.push("大辉");
    alert(b.key2);    //小辉,小辉,大辉
    alert(a.key2);    //小辉,小辉,大辉

    但是,若修改的属性变为对象或数组时,那么父子对象之间就会发生关联。从以上弹出结果可知,我对b对象进行修改,a、b的key2属性值(数组)均发生了改变。其在内存的状态,可以用下图来表示。

     

    原因是key1的值属于基本类型,所以拷贝的时候传递的就是该数据段;但是key2的值是堆内存中的对象,所以key2在拷贝的时候传递的是指向key2对象的地址,无论复制多少个key2,其值始终是指向父对象的key2对象的内存空间。

     

    深拷贝

    或许以上并不是我们在实际编码中想要的结果,我们不希望父子对象之间产生关联,那么这时候可以用到深拷贝。既然属性值类型是数组和或象时只会传址,那么我们就用递归来解决这个问题,把父对象中所有属于对象的属性类型都遍历赋给子对象即可。测试代码如下:

    function Copy(p, c) {
      var c = c || {};
      for (var i in p) {
        if (typeof p[i] === 'object') {
                c[i] = (p[i].constructor === Array) ? [] : {};
                Copy(p[i], c[i]);
        } else {
                c[i] = p[i];
        }
      }
      return c;
    }
    a.key2 = ['小辉','小辉'];
    var b={};
    b = Copy(a,b);        
    b.key2.push("大辉");
    alert(b.key2);    //小辉,小辉,大辉
    alert(a.key2);    //小辉,小辉

    由上可知,修改b的key2数组时,没有使a父对象中的key2数组新增一个值,即子对象没有影响到父对象a中的key2。其存储模式大致如下:

     

     

    如何实现深拷贝

    1递归递归去复制所有层级属性。

    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;

    }   

    let a=[1,2,3,4],

        b=deepClone(a);

    a[0]=2;

    console.log(a,b);

     

    2通过JSON去解析

    let obj = {name:'fiona-SUN'};

    let copyObj = JSON.parse(JSON.stringify(obj));

    copyObj.name = 'fiona';

    console.log(copyObj.name); // 'fiona'

    console.log(obj.name); // 'fiona-SUN'

     

     

    3 es6之展开Object.assign(拷贝obj的内容到一个新的堆内存,copyObj存储新内存的引用)

    let obj = {name:'fiona-SUN'};

    let copyObj = Object.assign({}, obj);

    copyObj.name = 'fiona';

    console.log(copyObj.name); // 'fiona'

    console.log(obj.name); // 'fiona-SUN'

     

    4 es6之展开运算符(仅用于数组)

    let arr = [1,2,3];

    let copyArr = [...obj];

    copyArr[2] = 0;

    console.log(copyArr[2]);  // 0

    console.log(arr[2]);     // 2

     

     

  • 相关阅读:
    unity代码加密for Android,mono编译
    php __invoke 和 __autoload
    VC只运行一个程序实例
    VC单文档对话框添加托盘图标
    技术文档应该怎么写
    项目管理学习
    cannot download, /home/azhukov/go is a GOROOT, not a GOPATH
    Go语言学习
    appium键盘事件
    appium-doctor
  • 原文地址:https://www.cnblogs.com/ranyonsue/p/9287417.html
Copyright © 2011-2022 走看看