Skip to content

Latest commit

 

History

History
358 lines (319 loc) · 14 KB

1. 深拷贝与浅拷贝.md

File metadata and controls

358 lines (319 loc) · 14 KB

1. 深拷贝与浅拷贝

[[toc]]

  • 下面分别测试Object.assign(),lodash中cloneDeep,slice,concat,[...],JSON.parse(JSON.stringify),js方法实现拷贝。
/* 构建对象函数 */
function createObj(obj,prototype) {
  Object.setPrototypeOf(obj,prototype)
  return obj;
}

1. Object.assign()

  var arr=createObj([1,2,{a:'a',b:{c:'c'}}],[3,4,{x:'x',y:{z:'z'}}])
  var obj=createObj({a:'a',b:{c:'c'}},{x:'x',y:{z:'z'}})
  //Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象
  //遇到相同的属性随后的下一个对象的属性会覆盖上一个对象的属性,属性值是对象或数组会引用(直接替换)。
  console.log(Object.assign({},arr));//{0: 1, 1: 2, 2: {b:{c:'c'}}} __proto__原型上没拷贝来
  console.log(Object.assign([],obj));//[a: "a", b: {…}]:length: 0
  console.log(Object.assign(obj,arr))//{0: 1, 1: 2, 2: {b:{c:'c'}}, a: "a", b: {c:'c'}} __proto__:{x: "x",y: {z: "z"}}
  console.log(Object.assign(arr,obj))//[1, 2, {b:{c:'c'}}, a: "a", b: {c:'c'}]:length:3 __proto__:[3,4,{x:'x',y:{z:'z'}}]
  var arr=createObj([1,2,{a:'a',b:{c:'c'}}],[3,4,{x:'x',y:{z:'z'}}])
  copy1=Object.assign([],arr);//[1, 2, {b:{c:'c'}}] __proto__原型上属性不可枚举
  copy1[0]=11;
  copy1[2].a="aa"
  copy1[2].b.c="cc"
  console.log(arr,copy1)//arr上原型不变
  //[1, 2, {a:'aa',b:{c:'cc'}} ,[11, 2, {a:'aa',b:{c:'cc'}}]

  var obj=createObj({a:'a',b:{c:'c'}},{x:'x',y:{z:'z'}})
  copy2=Object.assign({},obj);
  copy2.a='aa';
  copy2.b.c='cc';
  console.log(obj,copy2)//obj上原型不变
  //{a:'a',b:{c:'cc'}},{a:'aa',b:{c:'cc'}}

  var s=[1,2,3];
  var d=[2,3,4];
  var s1=[2,3,[1,2,3],[5,33]];
  var d1=[1,2,[3,2,1],4];
  console.log(Object.assign(s,d))//[2,3,4]
  console.log(Object.assign(s1,d1))//[1,2,[3,2,1],4]
  // 总结:此方法为浅拷贝,相同的属性名(数组的话index相同),后面会覆盖前面
  // lodash中的merge是合并,数组和普通对象会递归合并(数组属性名是下标),值undefined不会覆盖前其他对象和值会被直接分配覆盖。源对象从从左到右分配。后续的来源对象属性会覆盖之前分配的属性。
  var object={a:123,g:undefined,t:123,c:[3,5,{e:1,f:4,d:[1],s:{a:2,b:2}},{a:1},7]}
  var object2={a:undefined,g:123,t:null,b:2,c:[3,4,{e:3,r:4,d:[2],s:{a:1,e:4}},6]}
  console.log(_.merge(object, object2));//{a:123,g:123,t:null,b:2,c:[3,4,{e:3,f:4,r:4,d:[2s:{a:1,b:2,e:4},6,7}]}
  //object和object2中的c是数组递归,然后下标0和1位置都是数字,就直接覆盖。位置3处object是对object2处是6,直接覆盖

2. lodash中cloneDeep

  var arr=createObj([1,2,{a:'a',b:{c:'c'}}],[3,4,{x:'x',y:{z:'z'}}])
  copy3=_.cloneDeep(arr)//[1,2,{a:'a',b:{c:'c'}}] 没有拷贝原型
  copy3[0]='11'
  copy3[2].a="aa"
  copy3[2].b.c="cc"
  console.log(arr,copy3)
  //[1,2,{a:'a',b:{c:'c'}}],[11,2,{a:'aa',b:{c:'cc'}}]

  var obj=createObj({a:'a',b:{c:'c'}},{x:'x',y:{z:'z'}})
  copy4=_.cloneDeep(obj)//{a:'a',b:{c:'c'}} 没有拷贝原型
  copy4.a='aa'
  copy4.b.c="cc"
  console.log(obj,copy4)
  //{a:'a',b:{c:'c'}},{a:'aa',b:{c:'cc'}}

  //总结:lodash中cloneDeep为深拷贝

3. slice

  var arr=createObj([1,2,{a:'a',b:{c:'c'}}],[3,4,{x:'x',y:{z:'z'}}])
  var obj=createObj({a:'a',b:{c:'c'}},{x:'x',y:{z:'z'}})
  //copy5=obj.slice()//obj.slice is not a function
  copy6=arr.slice()//[1,2,{a:'a',b:{c:'c'}}] 没有拷贝原型
  copy6[0]='11'
  copy6[2].a="aa"
  copy6[2].b.c="cc"
  console.log(arr,copy6)//arr上原型不变
  //[1,2,{a:'a',b:{c:'c'}}],[11,2,{a:'aa',b:{c:'cc'}}]

  //总结:和Object.assign作用一样,只不过Object.assign对象和数组都可以用,slice只能用于数组

4. concat

  var arr=createObj([1,2,{a:'a',b:{c:'c'}}],[3,4,{x:'x',y:{z:'z'}}])
  var obj=createObj({a:'a',b:{c:'c'}},{x:'x',y:{z:'z'}})
  // copy7=obj.concat()//obj.concat is not a function
  copy8=arr.concat()//[1,2,{a:'a',b:{c:'c'}}] 没有拷贝原型
  copy8[0]='11'
  copy8[2].a="aa"
  copy8[2].b.c="cc"
  console.log(arr,copy8)//arr上原型不变
  //[1,2,{a:'a',b:{c:'c'}}],[11,2,{a:'aa',b:{c:'cc'}}]

  //总结:和Object.assign与slice作用一样,只不过Object.assign对象和数组都可以用,cancat只能用于数组

5. 扩展运算符

  var arr=createObj([1,2,{a:'a',b:{c:'c'}}],[3,4,{x:'x',y:{z:'z'}}])
  var obj=createObj({a:'a',b:{c:'c'}},{x:'x',y:{z:'z'}})
  copy9=[...arr]//[1,2,{a:'a',b:{c:'c'}}] 没有拷贝原型
  copy9[0]='11'
  copy9[2].a="aa"
  copy9[2].b.c="cc"
  console.log(arr,copy9)//arr上原型不变
  //[1,2,{a:'a',b:{c:'c'}}],[11,2,{a:'aa',b:{c:'cc'}}]

  //copy10=[...obj]//object is not iterable
  copy10={...obj}//{a:'a',b:{c:'c'}}
  copy10.a='aa'
  copy10.b.c="cc"
  console.log(obj,copy10)//obj上原型不变
  //{a:'a',b:{c:'cc'}},{a:'aa',b:{c:'cc'}}

  //总结:...和Object.assign,slice,concat作用是一样的,对象数组都可以用,二级对象及以后为浅拷贝,一级对象为深拷贝

6. JSON.parse(JSON.stringify())

  //虽然可行,但不推荐这种用法,可查看(https://www.jianshu.com/p/b084dfaad501)
  var arr=createObj([1,2,{a:'a',b:{c:'c'}}],[3,4,{x:'x',y:{z:'z'}}])
  var obj=createObj({a:'a',b:{c:'c'}},{x:'x',y:{z:'z'}})
  copy11=JSON.parse(JSON.stringify(arr))//[1,2,{a:'a',b:{c:'c'}}] 没有拷贝原型
  copy11[0]='11'
  copy11[2].a="aa"
  copy11[2].b.c="cc"
  console.log(arr,copy11)//arr上原型不变
  //[1,2,{a:'a',b:{c:'c'}}],[11,2,{a:'aa',b:{c:'cc'}}]

  copy12=JSON.parse(JSON.stringify(obj))//{a:'a',b:{c:'c'} 没有拷贝原型
  copy12.a='aa'
  copy12.b.c="cc"
  console.log(obj,copy12)//obj上原型不变
  //{a:'a',b:{c:'c'}},{a:'aa',b:{c:'cc'}}

  // 总结JSON.parse(JSON.stringify())为深拷贝,但不推荐,
  // 碰见时间对象结果为字符串形式
  // 碰见RegExp、Error对象序列化后为空,
  // 碰见函数,undefined,序列化的结果会把函数或 undefined丢失
  // 碰见NaN、Infinity和-Infinity,则序列化的结果会变成null

7. js方法

  var arr=createObj([1,2,{a:'a',b:{c:'c'}}],[3,4,{x:'x',y:{z:'z'}}])
  var obj=createObj({a:'a',b:{c:'c'}},{x:'x',y:{z:'z'}})
  
  function clone (value) {
    if (Array.isArray(value)) {
      return value.map(clone)
    } else if (value && typeof value === 'object') {
      const res = {}
      for (const key in value) {
        res[key] = clone(value[key])
      }
      return res
    } else {
      return value
    }
  }
  //使用递归的方式实现数组、对象的深拷贝
  function deepClone1(obj) {
    //判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝
    var objClone = Array.isArray(obj) ? [] : {};
    //进行深拷贝的不能为空,并且是对象或者是
    if (obj && typeof obj === "object") {
      for (key in obj) {
        if (obj.hasOwnProperty(key)) {
          if (obj[key] && typeof obj[key] === "object") {
            objClone[key] = deepClone1(obj[key]);
          } else {
            objClone[key] = obj[key];
          }
        }
      }
    }
    return objClone;
  }
  copy13=deepClone1(arr)//[1,2,{a:'a',b:{c:'c'}}] 没有拷贝原型
  copy13[0]='11'
  copy13[2].a="aa"
  copy13[2].b.c="cc"
  console.log(arr,copy13)
  //[1,2,{a:'a',b:{c:'c'}}],[11,2,{a:'aa',b:{c:'cc'}}]

  copy14=deepClone1(obj)//{a:'a',b:{c:'c'} 没有拷贝原型
  copy14.a='aa'
  copy14.b.c="cc"
  console.log(obj,copy14)
  //{a:'a',b:{c:'c'}},{a:'aa',b:{c:'cc'}}

8. 自身属性和原型上的浅拷贝

  var arr=createObj([1,2,{a:'a',b:{c:'c'}}],[3,4,{x:'x',y:{z:'z'}}])
  var obj=createObj({a:'a',b:{c:'c'}},{x:'x',y:{z:'z'}})
  function moreClone(origin) {
      let oriProto = Object.getPrototypeOf(origin);
      return Object.assign(Object.create(oriProto), origin);
  }
  copy15=moreClone(arr);//[1,2,{a:'a',b:{c:'c'}}]  __proto__:[3,4,{x:'x',y:{z:'z'}}]
  copy15[0]='11'
  copy15[2].a="aa"
  copy15[2].b.c="cc"
  copy15.__proto__[0]=33
  copy15.__proto__[2].x='xx'
  copy15.__proto__[2].y.z='zz'
  console.log(arr,copy15)
  // [1,2,{a:'aa',b:{c:'cc'}}]  __proto__:[33,44,{x:'xx',y:{z:'zz'}}],
  // [11,2,{a:'aa',b:{c:'cc'}}]  __proto__:[33,44,{x:'xx',y:{z:'zz'}}]

  copy16=moreClone(obj);//{a:'a',b:{c:'c'}}  __proto__:{x: "x",y: {z: "z"}}
  copy16.a='aa'
  copy16.b.c='cc'
  copy16.__proto__.x='xx'//copy16.x=x是给自身对象加x:x
  copy16.__proto__.y.z='zz'//copy16.y.z='zz'是给原型上y.z改成zz
  console.log(obj,copy16)
  //{a:'a',b:{c:'cc'}}  __proto__:{x: "xx",y: {z: "zz"}},
  //{a:'aa',b:{c:'cc'}}  __proto__:{x: "xx",y: {z: "zz"}}

  //总结:自身和原型上都是浅拷贝

9. 利用7和8实现自身属性浅拷贝和原型上的深拷贝

  var arr=createObj([1,2,{a:'a',b:{c:'c'}}],[3,4,{x:'x',y:{z:'z'}}])
  var obj=createObj({a:'a',b:{c:'c'}},{x:'x',y:{z:'z'}})
  function moreClone1(origin) {
      let oriProto = _.cloneDeep(Object.getPrototypeOf(origin));//当然可以用上面的deepClon 代替_.cloneDeep
      return Object.assign(Object.create(oriProto), origin);
  }
  copy17=moreClone1(arr);//[1,2,{a:'a',b:{c:'c'}}]  __proto__:[3,4,{x:'x',y:{z:'z'}}]
  copy17[0]='11'
  copy17[2].a="aa"
  copy17[2].b.c="cc"
  copy17.__proto__[0]=33
  copy17.__proto__[2].x='xx'
  copy17.__proto__[2].y.z='zz'
  console.log(arr,copy17)
  // [1,2,{a:'aa',b:{c:'cc'}}]  __proto__:[3,4,{x:'x',y:{z:'z'}}],
  // [11,2,{a:'aa',b:{c:'cc'}}]  __proto__:[33,44,{x:'xx',y:{z:'zz'}}
  copy18=moreClone1(obj);//{a:'a',b:{c:'c'}}  __proto__:{x: "x",y: {z: "z"}}
  copy18.a='aa'
  copy18.b.c='cc'
  copy18.__proto__.x='xx'//copy18.x=x是给自身对象加x:x
  copy18.__proto__.y.z='zz'//copy18.y.z='zz'是给原型上y.z改成zz
  console.log(obj,copy18)
  //{a:'a',b:{c:'cc'}}  __proto__:{x: "x",y: {z: "z"}},
  //{a:'aa',b:{c:'cc'}}  __proto__:{x: "xx",y: {z: "zz"}}

10. 利用9实现自身深拷贝和原型上的浅拷贝

  var arr=createObj([1,2,{a:'a',b:{c:'c'}}],[3,4,{x:'x',y:{z:'z'}}])
  var obj=createObj({a:'a',b:{c:'c'}},{x:'x',y:{z:'z'}})
  function moreClone2(origin) {
      let oriProto = Object.getPrototypeOf(origin);//当然可以用上面的deepClone1代替_.cloneDeep
      return Object.assign(Object.create(oriProto), _.cloneDeep(origin));
  }
  copy19=moreClone2(arr);//[1,2,{a:'a',b:{c:'c'}}]  __proto__:[3,4,{x:'x',y:{z:'z'}}]
  copy19[0]='11'
  copy19[2].a="aa"
  copy19[2].b.c="cc"
  copy19.__proto__[0]=33
  copy19.__proto__[2].x='xx'
  copy19.__proto__[2].y.z='zz'
  console.log(arr,copy19)
  // [1,2,{a:'c',b:{c:'c'}}]  __proto__:[33,44,{x:'xx',y:{z:'zz'}}],
  // [11,2,{a:'aa',b:{c:'cc'}}]  __proto__:[33,44,{x:'xx',y:{z:'zz'}}]

  copy20=moreClone2(obj);//{a:'a',b:{c:'c'}}  __proto__:{x: "x",y: {z: "z"}}
  copy20.a='aa'
  copy20.b.c='cc'
  copy20.__proto__.x='xx'//copy20.x=x是给自身对象加x:x
  copy20.__proto__.y.z='zz'//copy20.y.z='zz'是给原型上y.z改成zz
  console.log(obj,copy20)
  //{a:'a',b:{c:'c'}}  __proto__:{x: "xx",y: {z: "zz"}},
  //{a:'aa',b:{c:'cc'}}  __proto__:{x: "xx",y: {z: "zz"}}

11. 利用9实现自身和原型上的深拷贝

  var arr=createObj([1,2,{a:'a',b:{c:'c'}}],[3,4,{x:'x',y:{z:'z'}}])
  var obj=createObj({a:'a',b:{c:'c'}},{x:'x',y:{z:'z'}})
  function moreClone3(origin) {
      let oriProto = _.cloneDeep(Object.getPrototypeOf(origin));//当然可以用上面的deepClone1代替_.cloneDeep
      return Object.assign(Object.create(oriProto), _.cloneDeep(origin));
  }
  copy21=moreClone3(arr);//[1,2,{a:'a',b:{c:'c'}}]  __proto__:[3,4,{x:'x',y:{z:'z'}}]
  copy21[0]='11'
  copy21[2].a="aa"
  copy21[2].b.c="cc"
  copy21.__proto__[0]=33
  copy21.__proto__[2].x='xx'
  copy21.__proto__[2].y.z='zz'
  console.log(arr,copy21)
  // [1,2,{a:'c',b:{c:'c'}}]  __proto__:[3,4,{x:'x',y:{z:'z'}}],
  // [11,2,{a:'aa',b:{c:'cc'}}]  __proto__:[33,44,{x:'xx',y:{z:'zz'}}]
  copy22=moreClone3(obj);//{a:'a',b:{c:'c'}}  __proto__:{x: "x",y: {z: "z"}}
  copy22.a='aa'
  copy22.b.c='cc'
  copy22.__proto__.x='xx'//copy22.x=x是给自身对象加x:x
  copy22.__proto__.y.z='zz'//copy22.y.z='zz'是给原型上y.z改成zz
  console.log(obj,copy22)
  //{a:'a',b:{c:'c'}}  __proto__:{x: "x",y: {z: "z"}},
  //{a:'aa',b:{c:'cc'}}  __proto__:{x: "xx",y: {z: "zz"}}

12. Array.from()

    var arr=createObj([1,2,{a:'a',b:{c:'c'}}],[3,4,{x:'x',y:{z:'z'}}])
    var obj=createObj({a:'a',b:{c:'c'}},{x:'x',y:{z:'z'}})
    //Array.from()是ES6中语法,只能用于js集合(如: 数组、类数组对象(就是键是数组下标,并有length属性,类似{0:1,1:'x',length:2}这样的)、或者是字符串、map 、set 等可迭代对象),所以对于obj是无法使用的。
    copy23=Array.from(obj);//[]
    copy24=Array.from(arr);//[1,2,{a:'a',b:{c:'c'}}] 没有拷贝原型
    copy24[0]='11'
    copy24[2].a="aa"
    copy24[2].b.c="cc"
    console.log(arr,copy24)//arr上原型不变
    //[1,2,{a:'a',b:{c:'c'}}],[11,2,{a:'aa',b:{c:'cc'}}]

    //也可以使用Array.from()实现js集合的深拷贝,包含对象的只能是浅拷贝
    function recursiveClone(val) {
      return Array.isArray(val) ? Array.from(val, recursiveClone) : val;
    }
    var arr=[1,2,[3,4,[5,6]]]
    copy25=Array.from(arr, recursiveClone)
    copy25[0]=11
    copy25[2][1]=33
    copy25[2][2][1]=55
    console.log(arr,copy25)
    //[1,2,[3,4,[5,6]]],[11,2,[32,4,[55,6]]]
    //这种是利用递归,把拷贝嵌套的每一级然后返回。你也可以利用上面的方法实现自身和原型上的深拷贝,但是只能针对js集合

    //总结:和Object.assign,slice,concat作用是一样的,只有类数组可以用,二级对象及以后为浅拷贝,一级对象为深拷贝

总结

深拷贝是指目标对象不会随源对象改变而改变

浅拷贝是指目标对象会随着源对象改变而改变

  1. 对象中只考虑自身的深拷贝可用:lodash.cloneDeep,JSON.parse(JSON.stringify()),js方法

  2. 数组中只考虑自身的深拷贝可用:lodash.cloneDeep,JSON.parse(JSON.stringify()),js方法

  3. 对象中只考虑自身的浅拷贝可用:Object.assign(),扩展运算符...

  4. 数组中只考虑自身的浅拷贝可用:Object.assign(),扩展运算符...,slice,concat

  5. 考虑原型上的复制:js方法