前端必看!JSON.parse(JSON.stringify())竟不是万能解?深拷贝的黑暗森林法则

在 JavaScript 中,深拷贝和浅拷贝是处理对象和数组复制时的两种重要概念,它们的主要区别在于复制对象内部嵌套对象或数组时的行为。

浅拷贝

  1. 定义:浅拷贝会创建一个新的对象或数组,这个新的对象或数组会复制原对象或数组的基本类型值和引用类型的引用地址。也就是说,对于引用类型(如对象、数组),新对象和原对象会共享这些引用,对其中一个对象中引用类型值的修改会反映到另一个对象上。

  2. 实现方式

  • 对象的浅拷贝 :可以使用Object.assign()方法。它将所有可枚举属性的值从一个或多个源对象复制到目标对象。例如:
js 复制代码
let originalObject = { a: 1, b: { c: 2 } };
let shallowCopiedObject = Object.assign({}, originalObject);
console.log(shallowCopiedObject); 
// { a: 1, b: { c: 2 } }

// 修改浅拷贝对象中的嵌套对象
shallowCopiedObject.b.c = 3;
console.log(originalObject.b.c); 
// 3,原对象也被改变,因为共享引用
  • 数组的浅拷贝 :可以使用数组的slice()方法或concat()方法。slice()方法会返回一个新的数组对象,这一对象是一个由beginend决定的原数组的浅拷贝(包括begin,不包括end)。concat()方法用于合并两个或多个数组,返回一个新数组,也是浅拷贝。例如:
js 复制代码
let originalArray = [1, { value: 2 }];
let slicedArray = originalArray.slice();
let concatenatedArray = [].concat(originalArray);

// 修改浅拷贝数组中的嵌套对象
slicedArray[1].value = 3;
console.log(originalArray[1].value); 
// 3,原数组也被改变,因为共享引用

深拷贝

  1. 定义:深拷贝会递归地复制对象及其所有嵌套的对象和数组,创建一个与原对象完全独立的新对象。对新对象的任何修改都不会影响原对象,反之亦然。

  2. 实现方式

  • 使用JSON.parse(JSON.stringify()) :这是一种简单但有局限性的深拷贝方法。它先将对象转换为 JSON 字符串,再将其解析回对象,从而实现深拷贝。例如:
js 复制代码
let original = { a: 1, b: { c: 2 } };
let deepCopied = JSON.parse(JSON.stringify(original));

// 修改深拷贝对象中的嵌套对象
deepCopied.b.c = 3;
console.log(original.b.c); 
// 2,原对象不受影响
  • 局限性 :这种方法不能处理函数、undefinedSymbol等特殊类型。例如:
js 复制代码
let originalWithFunction = { a: 1, func: function() { return 'Hello'; } };
let copiedWithFunction = JSON.parse(JSON.stringify(originalWithFunction));
console.log(copiedWithFunction.func); 
// undefined,函数丢失
  • 手动实现深拷贝:可以通过递归的方式手动实现深拷贝,以处理各种复杂情况。以下是一个简单的深拷贝函数示例:
js 复制代码
function deepCopy(obj) {
    if (typeof obj!== 'object' || obj === null) {
        return obj;
    }
    let copy = Array.isArray(obj)? [] : {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            copy[key] = deepCopy(obj[key]);
        }
    }
    return copy;
}

let complexObject = { a: 1, b: [2, 3], c: { d: 4 } };
let deeplyCopied = deepCopy(complexObject);

// 修改深拷贝对象中的嵌套对象
deeplyCopied.c.d = 5;
console.log(complexObject.c.d); 
// 4,原对象不受影响

在这个函数中,首先判断传入的obj是否为对象或null,如果不是则直接返回。然后根据obj是数组还是对象创建相应的空容器copy。接着通过for...in循环遍历obj的所有属性,并使用递归调用deepCopy来复制属性值,确保所有嵌套的对象和数组都被深拷贝。

  • 多方面考虑深拷贝 :考虑不同的数据类型,比如基本类型(number, string,boolean, null, undefined)、对象、数组、日期、正则表达式、MapSet等等。不同的数据类型可能需要不同的处理方式。比如,处理日期对象时,应该创建一个新的Date实例,而不是直接复制引用。然后,如何处理循环引用的问题。比如,如果一个对象的属性引用了自身,或者对象之间相互引用,普通的深拷贝方法可能会进入无限递归,导致栈溢出。这时候需要一种机制来记录已经拷贝过的对象,避免重复拷贝,可以使用WeakMap来保存已拷贝的对象,键是原对象,值是拷贝后的对象。接下来,我需要思考如何递归地处理对象的属性。对于普通对象和数组,需要遍历它们的属性或元素,并对每个属性值进行深拷贝。对于其他特殊对象类型,比如RegExpDateMapSet,需要分别处理。例如,正则表达式可以直接复制,但可能需要保留flagssourceDate对象需要创建新的实例;MapSet需要遍历其元素并进行深拷贝。以下是一个完整的深拷贝过程代码示例:
js 复制代码
function deepClone(target, map = new WeakMap()) {
  // 处理非对象类型(基本类型、函数)
  if (typeof target !== 'object' || target === null) return target

  // 处理循环引用
  if (map.has(target)) return map.get(target)

  // 处理特殊对象类型
  switch (true) {
    case target instanceof Date:
      return new Date(target)
    case target instanceof RegExp:
      return new RegExp(target.source, target.flags)
    case target instanceof Map:
      return cloneMap(target, map)
    case target instanceof Set:
      return cloneSet(target, map)
    case ArrayBuffer.isView(target):  // 处理 TypedArray
      return target.slice()
    case target instanceof ArrayBuffer: // 处理 ArrayBuffer
      return cloneArrayBuffer(target)
  }

  // 处理普通对象/数组
  const clone = Array.isArray(target) 
    ? []
    : Object.create(Object.getPrototypeOf(target))

  map.set(target, clone) // 记录已克隆对象

  // 处理 Symbol 属性
  Reflect.ownKeys(target).forEach(key => {
    clone[key] = deepClone(target[key], map)
  })

  return clone
}

深拷贝和浅拷贝各有其适用场景。浅拷贝适用于只需要复制对象的顶层结构,且不担心共享引用带来的问题时;而深拷贝则适用于需要完全独立的对象副本,避免对新对象的修改影响原对象的情况。

相关推荐
成都渲染101云渲染66664 小时前
blender云渲染指南2025版
前端·javascript·网络·blender·maya
聆听+自律4 小时前
css实现渐变色圆角边框,背景色自定义
前端·javascript·css
行走__Wz5 小时前
计算机学习路线与编程语言选择(信息差)
java·开发语言·javascript·学习·编程语言选择·计算机学习路线
-代号95275 小时前
【JavaScript】二十九、垃圾回收 + 闭包 + 变量提升
开发语言·javascript·ecmascript
halo14165 小时前
vue中scss使用js的变量
javascript·vue3·scss
Mr.闻吉安6 小时前
什么是变量提升?
javascript·es6
huohuopro6 小时前
Vue3快速入门/Vue3基础速通
前端·javascript·vue.js·前端框架
草巾冒小子6 小时前
vue3中解决 return‘ inside ‘finally‘ block报错的问题
前端·javascript·vue.js
MossGrower6 小时前
65.Three.js案例-使用 MeshNormalMaterial 和 MeshDepthMaterial 创建 3D 图形
javascript·threejs·spheregeometry·torusknotgeome
-Camellia007-11 小时前
TypeScript学习案例(1)——贪吃蛇
javascript·学习·typescript