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

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

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

相关推荐
一个专注api接口开发的小白16 分钟前
Python/Node.js 调用taobao API:构建实时商品详情数据采集服务
前端·数据挖掘·api
掘金一周23 分钟前
我开源了一款 Canvas “瑞士军刀”,十几种“特效与工具”开箱即用 | 掘金一周 8.14
前端·人工智能·后端
嘘不要声张32 分钟前
地图点聚合(谷歌)
前端
缉毒英雄祁同伟39 分钟前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
har01d39 分钟前
在 uniapp 里使用 unocss,vue3 + vite 项目
前端·uni-app·vue·uniapp·unocss
OLong1 小时前
React Update Queue 源码全链路解析:从 setState 到 DOM 更新
前端·react.js
知识浅谈1 小时前
OpenLayers与Vue.js结合实现前端地图应用
前端
掘金011 小时前
Vue3 项目中实现特定页面离开提示保存功能方案
javascript·vue.js
答案answer1 小时前
three.js 实现几个好看的文本内容效果
前端·webgl·three.js
余_弦1 小时前
区块链钱包开发(十九)—— 构建账户控制器(AccountsController)
javascript·区块链·以太坊