我知道的深浅拷贝

131 阅读3分钟

我知道的深浅拷贝

145f6429bc89c50.jpg

深拷贝和浅拷贝

  • 浅拷贝:浅拷贝创建一个新的对象,这个对象的属性值和原始对象的属性值有一份精准拷贝,如果属性值是基本类型,那克隆的就是基本属性的值,如果属性值是引用类型,那克隆的就是其内存地址。

  • 深拷贝:深拷贝是从堆内存中开辟出一段新的区域,来放置拷贝的新对象。

// 浅拷贝
let obj = {
  a: 1,
  b: {
    c: 1
  }
}
let obj1 = Object.assign({}, obj)
obj1.b.c = 2
obj1.a = 2
console.log(obj);   //  { a: 1, b: { c: 2 } }

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

赋值,浅拷贝和深拷贝

三者对于引用类型的区别:

  • 赋值:赋值只是将引用类型存放在栈中的指针,赋值给一个给一个新的对象,这两个对象具有相同的内存指针,因此改变其中一个对象的属性值,两个对象的值都会改变。
let obj = {
  a: 1,
  b: {
    c: 1
  }
}
let obj1 = obj
obj1.a = 2
obj1.b.c = 2
console.log(obj);   ///  { a: 2, b: { c: 2 } }
  • 浅拷贝:浅拷贝会重新在堆内存中开辟区域,拷贝的原始类型值互不影响,但是引用类型还是指向同一块地址类似赋值。代码见第一段代码

  • 深拷贝:深拷贝也会重新在堆内存中开辟新的区域,而且深拷贝中属性值为引用类型,也会重新开辟一段地址来存放这个引用类型,因此深拷贝的两个对象互相不影响

let obj = {
  a: 1,
  b: {
    c: 1
  }
}
let obj2 = JSON.parse(JSON.stringify(obj))
obj2.b.c = 3
console.log(obj);   //  { a: 1, b: { c: 1 } }
console.log(obj2);  //  { a: 1, b: { c: 3 } }

浅拷贝的实现方法

1.Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

let obj = {
  a: 1,
  b: {
    c: 1
  }
}
let obj1 = Object.assign({}, obj, {d: 1})

console.log(obj1);   // { a: 1, b: { c: 1 }, d: 1 }

2.解构

ES6新增解构运算符

let obj = {
  a: 1,
  b: {
    c: 1
  }
}
let obj1 = {...obj}
obj1.b.c = 2
console.log(obj);  // { a: 1, b: { c: 2 } }
console.log(obj1);  // { a: 1, b: { c: 2 } }

3.Array.prototype.concat()

let arr = [1, 2, { name: 'xiaoniaoe'}]

let arr1 = [].concat(arr)
arr1[2].name = 'eee'
console.log(arr);   // [ 1, 2, { name: 'eee' } ]

4.Array.prototype.slice()

let arr = [1, 2, { name: 'xiaoniaoe'}]
let arr1 = arr.slice()
arr1[2].name = 'eee'
console.log(arr);   //  [ 1, 2, { name: 'eee' } ]

深拷贝的实现方法

1. JSON.parse(JSON.stringify(obj))

先将对象转为JSON格式字符串,再将其转换回来,从而来达到深拷贝的作用。

缺点

  • 1、对象中有字段值为undefined,转换后则会直接字段消失

  • 2、对象如果有字段值为RegExp对象,转换后则字段值会变成{}

  • 3、对象如果有字段值为NaN、+-Infinity,转换后则字段值变成null

let obj = {
  a: 1,
  b: {
    c: 1
  },
  d: function() {}
}

let obj1 = JSON.parse(JSON.stringify(obj))
console.log(obj1);  // { a: 1, b: { c: 1 } }

2. 自行封装

我们首先要知道深拷贝需要达到的效果是什么样的:

  • 生成的是一个新的对象,有自己独立的内存地址
  • 拷贝的对象可能会存在嵌套对象
  • 对undefined,正则,function等这些特殊值的拷贝
  • 对环引用的解决

解决问题:

  • 创建一个新的空对象,将原对象的值循环复制过来
  • 使用递归调用
  • 环引用,使用map
function deepclone(target, map = new Map()) {
//  递归出口,判断是否是对象
  if(typeof target !== 'object'){
    return target
  }
  //  判断是数组还是对象
  const temp = Array.isArray(target) ? [] : {}
  // 判断是否以及存在,环引用
  if(map.get(target)) {
    return map.get(target)
  }
  map.set(target, temp)
  // 循环递归遍历
  for(const item in target) {
    temp[item] = deepclone(target[item], map)
  }
  return temp
}

对于不遍历的属性

对于function等不可遍历的属性,就手动的判断一下类型,再进行复制就可以了。 需要声明正则的克隆方法,symbol的声明方法等等。

总结

多动手,多实践。