JavaScript之深浅拷贝

前端开发中,对象拷贝就像现实世界的复印机------但当你按下"复印"键时,有时得到的竟是会同步变化的"魔法纸张"。这背后的秘密,就是深浅拷贝的奇妙世界

一、初识拷贝:复印机还是传送门?

简单数据类型(数字、字符串等)的拷贝像真正的复印机:

ini 复制代码
let a = 100;
let b = a;  // 复印一份
b = 200;    // 修改复印件
console.log(a); // 100 → 原文件纹丝不动

复杂数据类型(对象、数组)的拷贝却像传送门:

ini 复制代码
let obj1 = { name: '张三' };
let obj2 = obj1;  // 不是复印!是给同一对象贴新标签
obj2.name = '李四';
console.log(obj1.name); // '李四' → 原对象被修改了!

这就是引用传递的陷阱------我们操作的始终是同一个内存地址的数据。

二、浅拷贝:表面复印术

1. Object.assign():对象合并利器

ini 复制代码
const target = { a: 1 };
const source = { b: 2 };
const result = Object.assign(target, source);

result.a = 11;
console.log(target.a); // 11 → 目标对象被修改!

关键特性

  • 后来者居上:同名属性会被覆盖

    css 复制代码
    Object.assign({a:1}, {a:2}) // {a:2}
  • 可拷贝Symbol类型

    less 复制代码
    const s = Symbol();
    Object.assign({}, {[s]:123}) // {Symbol():123}
  • 忽略nullundefined

    javascript 复制代码
    Object.assign({}, null) // 安全跳过

2. 数组的浅拷贝三剑客

ini 复制代码
// 方法1:扩展运算符
const newArr = [...arr];

// 方法2:slice()
const arr2 = arr.slice();

// 方法3:concat()
const arr3 = arr.concat();

致命缺陷:嵌套对象仍是传送门!

ini 复制代码
const matrix = [[1,2], [3,4]];
const copy = matrix.slice();
copy[0][0] = 99;
console.log(matrix[0][0]); // 99 → 原数组被污染!

三、深拷贝:制造平行宇宙

1. JSON大法:简单粗暴

ini 复制代码
const source = { 
  b: { name: 'xht' } 
};
const newObj = JSON.parse(JSON.stringify(source));
newObj.b.name = '小红';
console.log(source.b.name); // 'xht' → 原对象安然无恙!

三大局限

  1. 函数属性 → 消失!

  2. undefined/Symbol → 消失!

  3. 循环引用 → 直接报错!

    ini 复制代码
    const obj = { a:1 };
    obj.self = obj; // 循环引用
    JSON.stringify(obj); // 报错!

2. 手写深拷贝:解决JSON的遗憾

基础版递归实现

bash 复制代码
function clone(source) {
  if (typeof source !== 'object') return source;
  
  const cloneTarget = Array.isArray(source) ? [] : {};
  for (const key in source) {
    cloneTarget[key] = clone(source[key]); // 递归拷贝
  }
  return cloneTarget;
}

进阶问题

  • 循环引用导致栈溢出

    ini 复制代码
    const obj = {a:1};
    obj.self = obj; // 自引用
    clone(obj); // 无限递归!

3. 终极方案:WeakMap破循环

arduino 复制代码
function clone(target, map = new WeakMap()) {
  if (typeof target !== 'object') return target;

  // 检查是否已拷贝过
  if (map.get(target)) return map.get(target);

  const cloneTarget = Array.isArray(target) ? [] : {};
  map.set(target, cloneTarget); // 存储映射关系

  for (const key in target) {
    cloneTarget[key] = clone(target[key], map); // 递归传递map
  }
  return cloneTarget;
}

为什么用WeakMap?

  • 弱引用特性:不阻止垃圾回收

  • 内存安全:避免内存泄漏

  • 支持对象作为键名

    ini 复制代码
    const obj = {a:1};
    const map = new WeakMap();
    map.set(obj, 'value');

四、实战中的拷贝艺术

1. 配置合并:Object.assign的舞台

arduino 复制代码
function createUser(options) {
  const defaults = { name: '匿名', age:18 };
  // 用户参数覆盖默认值
  const config = Object.assign({}, defaults, options); 
  console.log(config);
}
createUser({ name: '李四' }); // {name:'李四', age:18}

2. 深浅拷贝选择指南

场景 推荐方案 示例
无嵌套对象 Object.assign/扩展运算符 配置合并
简单嵌套结构 JSON.parse(JSON.stringify) 保存状态快照
含函数/Symbol 手写深拷贝 复杂状态管理
循环引用 WeakMap版深拷贝 树形数据结构

五、核心原理透析

  1. 内存模型
    简单类型直接存储值,复杂类型存储内存地址*
  2. 递归的本质
    "剥洋葱式"逐层拷贝,每层创建新对象
  3. WeakMap魔法

结语:选择你的武器

  • 日常开发:优先考虑Object.assignJSON方法
  • 特殊需求:手写深拷贝解决复杂场景
  • 终极武器:lodash.cloneDeep第三方库

记住这个黄金法则
当修改拷贝对象时,如果不想影响原对象------请用深拷贝!

就像在现实世界中,当你想真正复制一份契约而不是共享同一份时,你需要的是真正的复印机,而不是魔法传送门。深浅拷贝的选择,决定了你的代码世界是独立宇宙还是纠缠态的空间!

相关推荐
slim~4 分钟前
javaweb基础第一天总结(HTML-CSS)
前端·css·html
一支鱼8 分钟前
leetcode常用解题方案总结
前端·算法·leetcode
小浣熊喜欢揍臭臭18 分钟前
react+umi项目如何添加electron的功能
javascript·electron·react
惜.己21 分钟前
针对nvm不能导致npm和node生效的解决办法
前端·npm·node.js
乖女子@@@34 分钟前
React笔记_组件之间进行数据传递
javascript·笔记·react.js
F2E_Zhangmo1 小时前
基于cornerstone3D的dicom影像浏览器 第二章 加载本地文件夹中的dicom文件并归档
前端·javascript·css
念念不忘 必有回响1 小时前
js设计模式-装饰器模式
javascript·设计模式·装饰器模式
零千叶1 小时前
【面试】RabbitMQ 常见问题
面试·职场和发展·rabbitmq
用户21411832636021 小时前
Nano Banana免费方案来了!Docker 一键部署 + 魔搭即开即用,小白也能玩转 AI 图像编辑
前端
weixin_584121431 小时前
vue3+ts导出PDF
javascript·vue.js·pdf