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第三方库

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

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

相关推荐
sg_knight2 分钟前
拥抱未来:ECMAScript Modules (ESM) 深度解析
开发语言·前端·javascript·vue·ecmascript·web·esm
LYFlied2 分钟前
【每日算法】LeetCode 17. 电话号码的字母组合
前端·算法·leetcode·面试·职场和发展
开发者小天3 分钟前
react中useEffect的用法,以及订阅模式的原理
前端·react.js·前端框架
前端白袍23 分钟前
Vue:如何实现一个具有复制功能的文字按钮?
前端·javascript·vue.js
new code Boy1 小时前
escape谨慎使用
前端·javascript·vue.js
奶球不是球1 小时前
elementplus组件中el-calendar组件自定义日期单元格内容及样式
javascript·css·css3
叠叠乐1 小时前
robot_state_publisher 参数
java·前端·算法
Kiri霧1 小时前
Range循环和切片
前端·后端·学习·golang
小张快跑。2 小时前
【Java企业级开发】(十一)企业级Web应用程序Servlet框架的使用(上)
java·前端·servlet
傻啦嘿哟2 小时前
实战:用Splash搞定JavaScript密集型网页渲染
开发语言·javascript·ecmascript