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. 引用类型要小心,修改之前想清楚。

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

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

相关推荐
逻辑驱动的ken1 小时前
Java高频面试考点场景题26
java·开发语言·面试·职场和发展·求职招聘
yqcoder1 小时前
JavaScript 闭包:函数背后的“背包”
开发语言·javascript·ecmascript
阿里嘎多学长1 小时前
2026-05-08 GitHub 热点项目精选
开发语言·程序员·github·代码托管
threelab1 小时前
挑战AI辅助从零构建3D模型编辑器:01基于Vue3 + Three.js的现代化架构设计
javascript·人工智能·3d·前端框架·着色器
张元清1 小时前
React 浏览器标签页 UX:用标题、Favicon 和通知把用户拉回来
前端·javascript·面试
知识分享小能手1 小时前
R语言入门学习教程,从入门到精通,集成开发环境RStudio(2)
开发语言·学习·r语言
葛兰岱尔1 小时前
葛兰岱尔rapid3D Loader for Three.js使用方式及7个基础API说明
开发语言·javascript·3d
Lkstar1 小时前
读完红宝书和YDKJS,我终于搞懂了原型链、闭包和this
javascript·面试
用户11489669441051 小时前
JavaScript原型链解析
javascript