JavaScript 深浅拷贝详解

JavaScript 深浅拷贝详解

一、为什么需要拷贝?

JavaScript 中数据类型分为两类:

  • 基本类型 (Number、String、Boolean、null、undefined、Symbol、BigInt):存储在栈中,赋值即复制值。
  • 引用类型 (Object、Array、Function 等):栈中存的是地址,堆中存的是数据,赋值复制的是地址。
js 复制代码
const a = { name: 'Tom' };
const b = a;
b.name = 'Jerry';
console.log(a.name); // 'Jerry'  ← a 也被改了

b = a 仅复制了引用地址,两者指向同一对象。要避免这种联动,就需要"拷贝"。


二、浅拷贝(Shallow Copy)

只拷贝第一层属性。如果属性值是引用类型,仍然共享地址。

1. Object.assign

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

copy.a = 99;        // 不影响 obj
copy.b.c = 99;      // 影响 obj.b.c ← 第二层共享

2. 扩展运算符 ...

js 复制代码
const copy = { ...obj };
const arrCopy = [...arr];

行为与 Object.assign 一致,只拷贝一层。

3. 数组方法 slice / concat

js 复制代码
const arr = [1, [2, 3]];
const copy = arr.slice();
copy[1][0] = 99; // arr 也变 [1, [99, 3]]

手写一个浅拷贝

js 复制代码
function shallowCopy(target) {
  if (target === null || typeof target !== 'object') return target;
  const result = Array.isArray(target) ? [] : {};
  for (const key in target) {
    if (Object.prototype.hasOwnProperty.call(target, key)) {
      result[key] = target[key];
    }
  }
  return result;
}

三、深拷贝(Deep Copy)

递归拷贝所有层级,新旧对象完全独立。

1. JSON.parse(JSON.stringify(obj))

最常见的"偷懒"写法:

js 复制代码
const copy = JSON.parse(JSON.stringify(obj));

优点: 简洁。 缺点(坑很多):

  • undefined、function、Symbol 会被丢弃或转 null
  • Date 变成字符串
  • RegExp、Map、Set 变成 {}
  • NaN、Infinity 变成 null
  • 不能处理循环引用(直接报错)

2. structuredClone(推荐 ✅)

浏览器和 Node.js 17+ 原生支持:

js 复制代码
const copy = structuredClone(obj);

支持 Date、Map、Set、RegExp、循环引用等,目前最优解。 不能拷贝函数和 DOM 节点。

3. 第三方库

  • lodash.cloneDeep(obj) --- 兼容性好,处理边界场景全面。

4. 手写一个深拷贝

要点:递归 + 处理引用类型 + 防循环引用。

js 复制代码
function deepClone(target, hash = new WeakMap()) {
  if (target === null || typeof target !== 'object') return target;
  if (target instanceof Date) return new Date(target);
  if (target instanceof RegExp) return new RegExp(target);

  if (hash.has(target)) return hash.get(target); // 循环引用

  const result = Array.isArray(target) ? [] : {};
  hash.set(target, result);

  Reflect.ownKeys(target).forEach(key => {
    result[key] = deepClone(target[key], hash);
  });

  return result;
}

关键点:

  • WeakMap 缓存已拷贝对象,解决循环引用
  • Reflect.ownKeys 同时拿到字符串键和 Symbol 键
  • 单独处理 Date、RegExp 等特殊对象

四、对比总结

方式 深/浅 函数 Date 循环引用 Symbol
Object.assign / ... 浅 ✅ ✅ ✅ ✅
JSON 大法 深 ❌ ❌字符串 ❌报错 ❌
structuredClone 深 ❌ ✅ ✅ ✅
lodash.cloneDeep 深 ✅ ✅ ✅ ✅
手写 deepClone 深 视实现 视实现 ✅ ✅

五、选型建议

  • 只需一层独立 → 扩展运算符 ...
  • 现代环境深拷贝 → structuredClone
  • 数据是纯 JSON → JSON.parse(JSON.stringify(...)) 够用
  • 复杂场景 / 老环境 → lodash.cloneDeep
  • 理解原理 / 面试 → 手写 deepClone
相关推荐
Su米苏2 分钟前
基于 Token 预算的上下文压缩控制器(Context Compaction Controller)
前端·数据库·人工智能
骇客野人3 分钟前
Java 开发组件大全覆盖后端主流技术栈(基础、Web、ORM、中间件、微服务、安全、工具、测试、运维、信创适配常用组件)
java·前端·中间件
谢亮_vipxieliang7 分钟前
深入理解 JavaScript 数据类型与类型转换
开发语言·javascript·ecmascript
风骏时光牛马33 分钟前
大模型底层架构与推理流程AI源码深度剖析
前端
程序员Sunday9 小时前
JavaScript 事件循环面试题,宏任务与微任务怎么执行|Sunday面试指南
开发语言·javascript·面试·校招·事件循环·程序员sunday
Setsuna_F_Seiei10 小时前
前端转型 Agent 开发 06 之 Agent Memory 记忆系统(让 Agent 更智能,更懂你)
前端·agent·ai编程
zhangzeyuaaa10 小时前
Ruby 多线程、GVL(GIL)与 Mutex 完全指南
开发语言·前端·ruby
默_笙11 小时前
🌊 向量库和 ES 都查不出"关系",我只好给奶茶建了一张人脉网
前端·javascript
优选资讯11 小时前
标签打印软件怎么对接 Excel 数据
前端·excel
思无邪6612 小时前
用 AI 做 JS 逆向:从抓包到复现的完整方法论
开发语言·javascript·人工智能