说起深浅拷贝其实就是数据内存,堆栈问题。

基本数据类型值指保存在栈内存中的简单数据段

var a=1
1.png
操作的是变量实际保存的值。a = 2;
2.png

基本类型变量的复制:从一个变量向一个变量复制时,会在栈中创建一个新值,然后把值复制到为新变量分配的位置上。var b= a;
栈内存
3.png

引用数据类型:引用数据类型值指保存在堆内存中的对象。也就是,变量中保存的实际上的只是一个指针,这个指针指向内存中的另一个位置,该位置保存着对象。访问方式是按引用访问。

vara = new Object();
ulStr8.png
当操作时,需要先从栈中读取内存地址,然后再延指针找到保存在堆内存中的值再操作
a.name= 'xz';
ulS6MV.png

  • 引用类型变量的复制:复制的是存储在栈中的指针,将指针复制到栈中未新变量分配的空间中,而这个指针副本和原指针指向存储在堆中的同一个对象;复制操作结束后,两个变量实际上将引用同一个对象。因此,在使用时,改变其中的一个变量的值,将影响另一个变量。
    var b= a;
    ulpPL8.png

    所以深浅拷贝。就是对堆内存(复杂数据类型)而言。

  • 浅拷贝就是只拷贝相同的指针,跟第六张图一样。js内有些方法也是现实了浅拷贝,例如对象的Object.assign()第一个参数是我们最终复制的目标对象,后面的所有参数是我们的即将复制的源对象,支持对象或数组,一般调用的方式为

    var newObj = Object.assign({}, originObj);

    另外[].slice()方法可以视为数组对象的浅拷贝。

  • 深拷贝则是指针也发生了改变,为什么js内部不提供统一的方法进行深拷贝?

    • 1,如何定义深拷贝?
    • 2,怎么处理拷贝后的原型?
    • 3,原生BOM/DOM怎么拷贝?
    • 4,函数对象是新创建还是引用?太多极端情况无法统一。
    • 2,内部循环引用怎么处理。是遍历过的对象都进行缓存,每次进行对比,然后再造一个循环引用来?这样带来的内存消耗可接受吗。

      var obj = {};
      obj.b = obj;
      //深拷贝obj对象时,就会循环的遍历b属性,直到栈溢出。
  • 经典题

    var a = { name: '前端' }  
    var b = a;  
    a = null;    //释放引用  
    console.log(a)  //null  
    console.log(b)  //{ name: '前端' }  
    var a = {n: 1};
    var b = a;
    a = {n: 2};
    a.x = a 
    console.log(a.x)//{n:2}
    console.log(b.x)//undefined
    var a = {n: 1};  
    var b = a;
    a.x=a={n:2}//等价于b.x = a = {n: 2};
    //先是预编译ab同时指向的对象增加x属性,
    //该对象也就是后来都没变的b指向的对象,
    //这里a= {n: 2};改变了a的指向。
    console.log(a.x)//undefined
    console.log(b.x)//{n:2}

对象实现深拷贝的方案

  • 通过JSON对象来回转换。JSON.parse(JSON.stringify())
    缺点:

    • 1)拷贝的对象的值中如果有函数、undefined、symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失
    • 2)无法拷贝不可枚举的属性,无法拷贝对象的原型链
    • 3)拷贝Date引用类型会变成字符串
    • 4)拷贝RegExp引用类型会变成空对象
    • 5)对象中含有NaN、Infinity和-Infinity,则序列化的结果会变成null
    • 6)无法拷贝对象的循环应用(即obj[key] = obj)所以这个一般只适合纯数据对象
  • 通过{}赋值创建,开辟新的引用地址,在将需要拷贝的键值放在新的堆内存里面。实现深拷贝

    var obj1 = {
      a: {
          b: 1
      },
      c: 1
    };
    var obj2 = {};
    obj2.a = {}
    obj2.c = obj1.c
    obj2.a.b = obj1.a.b;
    console.log(obj1); //{a:{b:1},c:1};
    console.log(obj2); //{a:{b:1},c:1};
    obj1.a.b = 2;
    console.log(obj1); //{a:{b:2},c:1};
    console.log(obj2); //{a:{b:1},c:1};
  • 通过插件,或第三方库。

    1. lodash
    2. jQuery

      var oldJson = {
        Name: 'quber',
        List: [1, 2, 3, 4],
        Obj: [
      { name: 'qubernet', fun: function () { return 1; } },
      { name: 'qubernet1', fun: function () { return 2; } }
        ]
      };
      var newJson = $.extend(true, {}, oldJson);
      console.log(JSON.stringify(newJson));
  • 封装深拷贝方法

    function deepClone (target) {
      if (typeof target !== 'object') {
        return target
      }
      //对于Array,Date,RegExp,Error,Function引用类型正确拷贝
      let copy = Array.isArray(target) ? [] : {} 
      for (let key in target) {
        console.log(key)
        copy[key] = deepClone(target[key])
      }
      return copy
    }
    var target = {
      a: 1,
      b: {
        c: [2]
      },
      d:function () { console.log(123) }
    }
    var copyTarget = deepClone(target)
    target.a = 0
    target.b.c = [2,3]
    target.d = function(){console.log(456)}
    console.log(target, copyTarget)
    target.d()
    copyTarget.d()

    首先这个deepClone函数并不能复制不可枚举的属性以及Symbol类型
    对象循环引用成环了的情况

    //上面封装对于循环引用(对象内部存在引用关系)则会造成无线循环爆栈问题
    var target = {
      a: {
        b: 1,
      },
    };
    target.c=target
    let clonedTarget = deepClone(target);
    target.a.b = 2;
    console.log(target, clonedTarget);
    //所以在递归的时候需要判断


用户bPbuFxB
51 声望4 粉丝