zoukankan      html  css  js  c++  java
  • 深拷贝和浅拷贝

    关于JavaScript的浅拷贝和深拷贝

     

    在 JS 中有一些基本类型像是NumberStringBoolean,而对象就是像这样的东西{ name: 'Larry', skill: 'Node.js' },对象跟基本类型最大的不同就在于他们的传值方式。

    基本类型是按值传递,像是这样:在修改a时并不会改到b

    var a = 25;
    var b = a;
    b = 18;
    console.log(a);//25
    console.log(b);//18

    但对象就不同,对象传的是按引用传值:

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

    复制一份obj1叫做obj2,然后把obj2.b改成100,但却不小心改到obj1.b,因为他们根本是同一个对象,这就是所谓的浅拷贝。

    要避免这样的错误发生就要写成这样:

     
    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 }
     

    这样就是深拷贝,不会改到原本的obj1。

    浅拷贝(Shallow Copy) VS 深拷贝(Deep Copy)

    浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

    浅拷贝的实现方式

    也就是简单地复制而已

    1、简单地复制语句

     
      <script type="text/javascript">
        function simpleClone(initalObj) {    
          var obj = {};    
          for ( var i in initalObj) {
            obj[i] = initalObj[i];
          }    
          return obj;
        }
    
        var obj = {
          a: "hello",
          b:{
              a: "world",
              b: 21
            },
          c:["Bob", "Tom", "Jenny"],
          d:function() {
              alert("hello world");
            }
        }
        var cloneObj = simpleClone(obj); 
        console.log(cloneObj.b); 
        console.log(cloneObj.c);
        console.log(cloneObj.d);
    
        cloneObj.b.a = "changed";
        cloneObj.c = [1, 2, 3];
        cloneObj.d = function() { alert("changed"); };
        console.log(obj.b);
        console.log(obj.c);
        console.log(obj.d);
      </script>
     

    结果为:

    2、Object.assign()

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

    Object.assign(target, ...sources)

    参数:

    target:目标对象。
    sources:任意多个源对象。
    返回值:目标对象会被返回。

    var obj = { a: {a: "hello", b: 21} };
    var initalObj = Object.assign({}, obj);
    
    initalObj.a.a = "changed";
    console.log(obj.a.a); // "changed"

    兼容性:

    需要注意的是:

    Object.assign()可以处理一层的深度拷贝,如下:
     
    var obj1 = { a: 10, b: 20, c: 30 };
    var obj2 = Object.assign({}, obj1);
    obj2.b = 100;
    console.log(obj1);
    // { a: 10, b: 20, c: 30 } <-- 沒被改到
    console.log(obj2);
    // { a: 10, b: 100, c: 30 }
     

    深拷贝的实现方式

    要完全复制又不能修改到原对象,这时候就要用 Deep Copy,这里会介绍几种Deep Copy 的方式。

    1、手动复制

    把一个对象的属性复制给另一个对象的属性

     
    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 } <-- 沒被改到
    console.log(obj2);
    // { a: 10, b: 100, c: 30 }
     

    但这样很麻烦,要一个一个自己复制;而且这样的本质也不能算是 Deep Copy,因为对象里面也可能回事对象,如像下面这个状况:

     
    var obj1 = { body: { a: 10 } };
    var obj2 = { body: obj1.body };
    obj2.body.a = 20;
    console.log(obj1);
    // { body: { a: 20 } } <-- 被改到了
    console.log(obj2);
    // { body: { a: 20 } }
    console.log(obj1 === obj2);
    // false
    console.log(obj1.body === obj2.body);
    // true
     

    虽然obj1obj2是不同对象,但他们会共享同一个obj1.body所以修改obj2.body.a时也会修改到旧的。

    2、对象只有一层的话可以使用上面的:Object.assign()函数

    Object.assign({}, obj1)的意思是先建立一个空对象{},接着把obj1中所有的属性复制过去,所以obj2会长得跟obj1一样,这时候再修改obj2.b也不会影响obj1。

    因为Object.assign跟我们手动复制的效果相同,所以一样只能处理深度只有一层的对象,没办法做到真正的 Deep Copy。不过如果要复制的对象只有一层的话可以考虑使用它。

    3、转成 JSON 再转回来

    JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。

     
    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
     

    这样做是真正的Deep Copy,这种方法简单易用。

    但是这种方法也有不少坏处,譬如它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。

    这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝。

    也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON。

     
    var obj1 = { fun: function(){ console.log(123) } };
    var obj2 = JSON.parse(JSON.stringify(obj1));
    console.log(typeof obj1.fun);
    // 'function'
    console.log(typeof obj2.fun);
    // 'undefined' <-- 没复制
     

    要复制的function会直接消失,所以这个方法只能用在单纯只有数据的对象。

    4、递归拷贝

     
    function deepClone(initalObj, finalObj) {    
      var obj = finalObj || {};    
      for (var i in initalObj) {        
        if (typeof initalObj[i] === 'object') {
          obj[i] = (initalObj[i].constructor === Array) ? [] : {};            
          arguments.callee(initalObj[i], obj[i]);
        } else {
          obj[i] = initalObj[i];
        }
      }    
      return obj;
    }
    var str = {};
    var obj = { a: {a: "hello", b: 21} };
    deepClone(obj, str);
    console.log(str.a);
     

    上述代码确实可以实现深拷贝。但是当遇到两个互相引用的对象,会出现死循环的情况。

    为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环。

    改进版代码如下:

     
    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) ? [] : {};            
          arguments.callee(prop, obj[i]);
        } else {
          obj[i] = prop;
        }
      }    
      return obj;
    }
    var str = {};
    var obj = { a: {a: "hello", b: 21} };
    deepClone(obj, str);
    console.log(str.a);
     

    5、使用Object.create()方法

    直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。

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

    6、jquery

    jquery 有提供一个$.extend可以用来做 Deep Copy。

     
    var $ = require('jquery');
    var obj1 = {
        a: 1,
        b: { f: { g: 1 } },
        c: [1, 2, 3]
    };
    var obj2 = $.extend(true, {}, obj1);
    console.log(obj1.b.f === obj2.b.f);
    // false
     

    7、lodash

    另外一个很热门的函数库lodash,也有提供_.cloneDeep用来做 Deep Copy。

     
    var _ = require('lodash');
    var obj1 = {
        a: 1,
        b: { f: { g: 1 } },
        c: [1, 2, 3]
    };
    var obj2 = _.cloneDeep(obj1);
    console.log(obj1.b.f === obj2.b.f);
    // false
     
  • 相关阅读:
    获取非行间样式
    获取非行间样式
    prompt 方法显示输入对话框
    comfirm 方法显示对话框
    移动端页面常见问题及解决方案
    原生js怎样获取后台端口数据
    canvas描绘渐变的矩形
    cookie 的增加,销毁,读取
    canvas 绘制图形
    数组的排序,去重复
  • 原文地址:https://www.cnblogs.com/mengshi-web/p/9780152.html
Copyright © 2011-2022 走看看