基础理解 - 深拷贝与浅拷贝

177 阅读3分钟

简介

对于深浅拷贝的概念来自于 JS 存储数据的方式,在 JS 里面有两种数据类型:基本类型和复杂类型;

  • 基本类型没有深浅拷贝的概念,因为基本类型的存储在栈内存中,在赋值的时候生成一个栈值,且互相不影响;
  • 复杂类型产生了深浅拷贝的概念,因为复杂类型的值存储在堆内存中,然后变量存储的时候一个指向堆内存的地址的变量值;

在复杂类型赋值的时候,不做任何操作,赋值的是栈内存中的变量值指向真实对象地址的指向链条值(类似于图书馆每本书的索引号),这种赋值操作叫做浅拷贝;

如果我们赋值的时候,想要一个新的一模一样的对象,那么这种赋值操作叫做深拷贝;

  • 简单数据类型存储图
  • 复杂数据类型存储图

如何实现?

浅拷贝:直接赋值就行

深拷贝:根据对象的属性值的数据类型一个一个的进行新的生成然后赋值;

JSON序列化实现

在 JS 中复杂类型都存在自己的序列化方法,支持序列化,通过 JSON.stringify() 序列复杂类型的时候会触发它的序列化方法。然后通过 JSON.parse() 把这个序列化后的字符串还原成一个对象。一序一转在新的地址生成了新的对象;

function cloneDeep(obj){
	return JSON.parse(JSON.stringify(obj));
}

但是因为对象的序列化方法实现各不一样,在解析的时候并不能完全还原一些类型。

能正确处理的情况:

  • Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。

不能处理的情况:

  • function
  • 循环引用(json 序列化不能控制复制的层次)
  • Maps,Sets,EegExps,Dates,ArrayBuffers等其他内置对象序列化可能存在问题,如 Date 序列化后在反序列化回来就变成字符串了,而不是 Date 对象。
  • NaN,Infinity,精确的浮点数

循环递归

循环对象的属性值,处理特殊情况,数组,循环引用,处理特殊类型等等;

// 简单的深拷贝
function cloneDeep(obj) {
  const cloneObj = {};

  for(let key in obj) { // 判断key 是不是当前对象的属性
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      cloneObj[obj] = obj[key];
    }
  }
}

// 循环递归拷贝
function cloneDeep(obj) {
  const cloneObj = {};

  for(let key in obj) { // 判断key 是不是当前对象的属性
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      if (isObject(obj[key])) {
        cloneObj[obj] = cloneDeep(obj[key])
      } else {
        cloneObj[obj] = obj[key];
      }
    }
  }
}

// 兼容数组
function cloneDeep(obj) {
  if (typeof obj === 'object') {
    const cloneObj =  Array.isArray(obj) ? [] : {};

    for(let key in obj) { // 判断key 是不是当前对象的属性
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        if (isObject(obj[key])) {
          cloneObj[obj] = cloneDeep(obj[key])
        } else {
          cloneObj[obj] = obj[key];
        }
      }
    }
  } else {
    return obj;
  }
}

// 解决循环引用,通过 map 存储每次的对象来,然后查询解决
// 检查map中有无克隆过的对象
// 有 - 直接返回
// 没有 - 将当前对象作为key,克隆对象作为value进行存储
// 继续克隆
function cloneDeep(obj) {
  const map = new Map();
  if (typeof obj === 'object') {
    const cloneObj =  Array.isArray(obj) ? [] : {};

    if (map.get(obj)) {
        return map.get(obj);
    }
    map.set(obj, cloneObj);

    for(let key in obj) { // 判断key 是不是当前对象的属性
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        if (isObject(obj[key])) {
          cloneObj[obj] = cloneDeep(obj[key])
        } else {
          cloneObj[obj] = obj[key];
        }
      }
    }
  } else {
    return obj;
  }
}

更多的实现参考,这篇文章,写的很详细:juejin.cn/post/684490…