深拷贝和浅拷贝

109 阅读3分钟

image.png

JS的数据类型

基本数据类型:String,Number,Boolean,Symbol,Null,Undefined,BigInt

  • 基本数据类型存放在栈中

引用数据类型:Object

  • 引用数据存放在堆内存中,栈空间只是存放堆内存中的地址

赋值,浅拷贝和深拷贝之间的区别

赋值:

  1. 对于基本数据类型,完全复制了一份新值
  2. 对于引用类型是将A的引用地址拷贝给了B,但共用一个实体

image.png

浅拷贝和深拷贝只针对引用数据类型

浅拷贝:

创建一个新对象

  1. 属性是基本数据类型,拷贝的是基本数据类型,
  2. 属性是引用类型,拷贝的是内存地址(单层对象可以,多层对象不行)
// 浅拷贝
let obj = { a: 1, b: { c: 2 } };
// 使用扩展运算符进行拷贝
let obj2 = { ...obj };

obj2.a = 3;
obj2.b.c = 4;

console.log(obj); // {a:1, b: { c: 4 }}
console.log(obj2); // {a: 3, b: { c: 4 }}

浅拷贝存在一个问题,当对象里面包含对象时,修改多层对象中的值会影响原始数据,所以浅拷贝修改新复制对象的一层属性时,原数据不会发生改变。为了不影响原始数据所有属性,需要深拷贝

深拷贝:

层层递归拷贝, 修改基本数据类型和引用数据类型都不会影响原有的数据

浅拷贝常用方法

  • Object.assign
  • 扩展运算符
  • slice
  • concat
let obj = ['iyongbao', { vue: 98 }]

let obj2 = obj.slice();
let obj3 = obj.concat();

obj[0] = "zhangsan";
obj[1].vue = 60;

console.log(obj); // {name: "zhangsan", score: { vue: 60 }}
console.log(obj2); // {name: "iyongbao", score: { vue: 60 }}
console.log(obj3); // {name: "iyongbao", score: { vue: 60 }}

手写浅拷贝

const shallowClone = (target)=> {
  // 对于基本数据类型,直接返回
  if(typeof target !== 'object' || target === null) {
    return target
  }
  let cloneTarget = Array.isArray(target) ? []:{}
  Object.keys(target).forEach(item=> {
    cloneTarget[item] = target[item]
  })
  return cloneTarget
}

深拷贝常用方法

  • JSON.parse(JSON.stringify())
  • loadash第三方库
  • 手写

手写深拷贝

简洁版

const deepClone = (target) => {
  // 对于基本数据类型,直接返回
  if(typeof target !== 'object' || target == null){
    return target
  }
  const cloneTarget = Array.isArray(target) ? [] : {}
  // 递归调用
  for (let key in target) {
    // 只拷贝对象上显示具有的属性
    if (target.hasOwnProperty(key)){
      cloneTarget[key] = deepClone(target[key])
    } else {
      cloneTarget[key] = target[key]
    }
  }
  return cloneTarget
}

存在一些问题: 循环引用,Date,RegExp,Symbol,Set,Map等类型不能正确深拷贝

解决循环引用

循环引用:子对象中又引用了父对象,对象中存在循环引用的情况,即对象的属性间接或者直接引用了自身的情况

const c = {name: "muyiy"};
c.test = c;
const d = deepClone(c); // 循环引用,栈溢出

用WeakMap,用来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间WeakMap中找,有没有拷贝过这个对象,如果有的话直接返回,没有的话继续拷贝

WeakMap:

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。当下一次垃圾回收机制执行时,这块内存就会被释放掉。

const deepClone = (target, map=new WeakMap()) => {
  // 对于基本数据类型,直接返回
  if(typeof target !== 'object' || target == null){
    return target
  }
  // 处理循环引用,判断有没有拷贝这个对象,有的话直接返回
  if(map.has(target)) return map.get(target)
  const cloneTarget = Array.isArray(target) ? [] : {}
  map.set(target, cloneTarget)
  // 递归调用
  for (let key in target) {
    // 只拷贝对象上显示具有的属性
    if (target.hasOwnProperty(key)){
      cloneTarget[key] = deepClone(target[key])
    } else {
      cloneTarget[key] = target[key]
    }
  }
  return cloneTarget
}

处理其他数据类型

Symbol类型

(1)for...of和for...in不能遍历Symbol类型数据,Object.keys()也会忽略

(2)Symbol可以作为对象属性

解决方法:

(1) Object.getOwnPropertySymbols(obj):遍历Symbol类型的属性

(2) Reflect.ownKeys(obj):返回对象的所有键,包括Symbol属性

(3) Symbol.prototype.valueOf():返回当前Symbol对象所包含的symbol包含值

const deepClone = (target, map=new WeakMap()) => {
  // 对于基本数据类型,直接返回
  if(typeof target !== 'object' || target == null){
    return target
  }
  // 遍历Symbol类型的值
  if(Object.prototype.toString.call(target).slice(8,-1) === 'Symbol'){
    return Object(Symbol.prototype.valueOf.call(target))
  }
  // 循环引用,判断有没有拷贝这个对象,有的话直接返回
  if(map.has(target)) return map.get(target)
  const cloneTarget = Array.isArray(target) ? [] : {}
  map.set(target, cloneTarget)
  // 递归调用,遍历所有类型
  Reflect.ownKeys(target).forEach(key=> {
    if (target.hasOwnProperty(key)){
      cloneTarget[key] = deepClone(target[key])
    } else {
      cloneTarget[key] = target[key]
    }
  })
  return cloneTarget
}

参考

juejin.cn/post/726785…