JavaScript 浅拷贝:只复制“第一层”的艺术

📋 JavaScript 浅拷贝:只复制"第一层"的艺术

🤔 什么是浅拷贝?

定义

浅拷贝是指创建一个新对象,这个新对象拥有原对象属性值的精确拷贝

  • 如果属性是基本类型 (String, Number, Boolean...),拷贝的是
  • 如果属性是引用类型 (Object, Array...),拷贝的是内存地址(指针)

通俗比喻

想象你有一本相册(原对象)。

  • 浅拷贝 :你复印了相册的封面和目录。对于照片本身,你没有重新打印,而是把原相册里的照片拿出来,放进了新相册。
    • 结果:如果你在新相册里撕掉一张照片(修改引用类型的内部属性),原相册里的那张照片也坏了,因为它们其实是同一张物理照片
    • 但是,如果你给新相册换了一个封面(修改第一层基本属性),原相册不受影响。

📂 目录

  1. [🖼️ 图解:浅拷贝 vs 深拷贝](#🖼️ 图解:浅拷贝 vs 深拷贝)
  2. [🛠️ 实现浅拷贝的 4 种主流方法](#🛠️ 实现浅拷贝的 4 种主流方法)
  3. [⚠️ 浅拷贝的陷阱:什么时候会出问题?](#⚠️ 浅拷贝的陷阱:什么时候会出问题?)
  4. [💡 总结与选型建议](#💡 总结与选型建议)

1. 🖼️ 图解:浅拷贝 vs 深拷贝

假设我们有如下数据结构:

javascript 复制代码
const original = {
  name: "Lingma", // 基本类型
  info: {
    // 引用类型
    age: 1,
  },
};

✅ 浅拷贝后内存结构

text 复制代码
Stack (栈)
+----------+             Heap (堆)
| original |-----------> +------------------+
+----------+             | name: "Lingma"   |
                         | info: [Addr A] --+--> +------------------+
+----------+             +------------------+     | age: 1           |
| copy     |-----------> +------------------+     +------------------+
+----------+             | name: "Lingma"   |
                         | info: [Addr A] --+----^ (指向同一个对象!)
                         +------------------+

结论original.infocopy.info 指向堆中的同一个对象

❌ 深拷贝后内存结构(对比参考)

text 复制代码
Stack (栈)
+----------+             Heap (堆)
| original |-----------> +------------------+
+----------+             | name: "Lingma"   |
                         | info: [Addr A] --+--> +------------------+
+----------+             +------------------+     | age: 1           |
| copy     |-----------> +------------------+     +------------------+
+----------+             | name: "Lingma"   |
                         | info: [Addr B] --+--> +------------------+
                         +------------------+     | age: 1           |
                                                  +------------------+

结论copy 拥有完全独立的内存空间。


2. 🛠️ 实现浅拷贝的 4 种主流方法

方法一:Object.assign() (经典常用)

这是 ES6 之前最常用的浅拷贝方法。它将源对象的所有可枚举属性复制到目标对象。

javascript 复制代码
const original = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, original);

// 测试基本类型
copy.a = 99;
console.log(original.a); // 1 ✅ 互不影响

// 测试引用类型
copy.b.c = 99;
console.log(original.b.c); // 99 😱 原数据被修改!

注意 :第一个参数 {} 是目标对象,必须传空对象,否则 original 自身会被修改。

方法二:展开运算符 ... (现代首选)

ES6 引入的语法糖,代码更简洁,可读性更高。

javascript 复制代码
const original = { a: 1, b: { c: 2 } };
const copy = { ...original };

// 效果同 Object.assign
copy.b.c = 99;
console.log(original.b.c); // 99 😱

对于数组的浅拷贝:

javascript 复制代码
const arr = [1, 2, [3, 4]];
const newArr = [...arr];
// 或者
const newArr2 = arr.slice();
// 或者
const newArr3 = Array.from(arr);

方法三:Array.prototype.slice() / concat() (数组专用)

在 ES6 之前,这是数组浅拷贝的标准做法。

javascript 复制代码
const arr = [1, 2, 3];
const copy1 = arr.slice();
const copy2 = [].concat(arr);

方法四:手写浅拷贝函数(面试/定制需求)

如果需要兼容旧环境或处理特殊逻辑,可以手写。

javascript 复制代码
function shallowClone(obj) {
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  // 判断是数组还是对象
  const cloneObj = Array.isArray(obj) ? [] : {};

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = obj[key]; // 直接赋值,引用类型拷贝地址
    }
  }

  return cloneObj;
}

3. ⚠️ 浅拷贝的陷阱:什么时候会出问题?

浅拷贝最大的风险在于嵌套对象

❌ 事故现场

javascript 复制代码
const user = {
  name: "Lingma",
  settings: {
    theme: "dark",
    notifications: true,
  },
};

// 浅拷贝
const newUser = { ...user };

// 用户修改设置
newUser.settings.theme = "light";

console.log(user.settings.theme); // "light" 😱
// 糟糕!原用户的设置也被改了!

✅ 如何避免?

  1. 如果只有一层嵌套:使用浅拷贝完全没问题,性能最好。
  2. 如果有多层嵌套
    • 使用深拷贝structuredClone, JSON.parse/stringify, Lodash cloneDeep)。
    • 或者使用不可变数据库(如 Immer, Immutable.js),它们通过结构共享实现了高效的"看似可变,实则不可变"。

4. 💡 总结与选型建议

方法 适用场景 优点 缺点
{ ...obj } 对象浅拷贝 语法简洁,现代标准 仅浅拷贝,兼容性需 babel
Object.assign 对象浅拷贝/合并 兼容性好,可合并多个对象 语法稍啰嗦
[...arr] 数组浅拷贝 简洁,速度快 仅适用于数组/可迭代对象
arr.slice() 数组浅拷贝 经典写法,兼容 IE 语义不如展开运算符清晰

🚀 博主寄语

浅拷贝不是"低级"的拷贝,而是高性能 的拷贝。

在 React/Vue 的状态管理中,如果状态层级不深,浅拷贝配合不可变思维(Immutability)是最佳实践。

记住原则

  1. 一层用浅,多层用深。
  2. 数组展开最香,对象合并 assign。
  3. 引用类型要小心,修改之前想清楚。

希望这篇文档能帮你彻底掌握浅拷贝!如果有疑问,欢迎在评论区留言。👇

喜欢这篇文章吗?记得点赞、收藏、转发哦! ❤️

相关推荐
星梦清河17 分钟前
Java—异步编程
java·开发语言
接着奏乐接着舞28 分钟前
dto 转entity方法
java·开发语言
0x000737 分钟前
译 Anders Hejlsberg 谈 C# 与 .NET
开发语言·c#·.net
czhaii43 分钟前
基于51单片机的Modbus从机通信系统
开发语言·单片机
elseif1231 小时前
【C++】vector 详细版
开发语言·c++·算法
codingPower1 小时前
JAVA后端安全进阶:基于HMAC-SHA256+Nonce+Timestamp的API防重放攻击方案
java·开发语言·spring boot·安全
暗冰ཏོ1 小时前
Go 语言从入门到后端项目实战完整指南
开发语言·后端·golang·go·go语言
Xin_ye100861 小时前
C# 零基础到精通教程 - 第十七章:前端集成——Blazor 基础
开发语言·c#
LDR0061 小时前
LDR6020:多 Type‑C 端口角色管理与外设上电顺序的智慧核心
c语言·开发语言·云计算
小杍随笔1 小时前
【Rust 工具链管理完全指南:rustup toolchain 命令实战详解】
开发语言·后端·rust