当前位置: 首页 > news >正文

JavaScript 深拷贝:从基础到实践的全面指南

关键点

  • JavaScript 深拷贝创建对象的完全独立副本,包括所有嵌套对象,修改副本不会影响原对象。
  • 常见方法包括 JSON.parse(JSON.stringify())、structuredClone()、手动递归复制和 Lodash 的 _.cloneDeep()。
  • 每种方法有其适用场景,需根据数据类型和项目需求选择。
  • 截至 2025 年 5 月,structuredClone() 是现代浏览器的首选,广泛支持复杂数据类型。
什么是深拷贝?

在 JavaScript 中,对象和数组是引用类型,简单赋值只会复制引用,导致修改副本会影响原对象。深拷贝通过复制所有层级的属性,创建独立的对象,确保修改副本不影响原对象。例如,复制一个包含嵌套对象的购物车,修改副本的价格不会改变原购物车。

为什么需要深拷贝?

深拷贝适用于需要独立操作对象副本的场景,如编辑用户数据、处理临时状态或生成数据快照。相比浅拷贝(只复制顶层属性),深拷贝更彻底,适合复杂数据结构。

深拷贝方法

以下是四种常见深拷贝方法:

  • JSON.parse(JSON.stringify()):简单但不支持函数、undefined 或循环引用。
  • structuredClone():现代内置方法,支持循环引用和多种数据类型。
  • 手动递归复制:灵活但实现复杂,需处理特殊类型。
  • Lodash _.cloneDeep():可靠但需引入依赖。
实际应用

深拷贝常用于前端开发,如复制状态以避免意外修改、生成数据备份或处理复杂表单数据。

在 JavaScript 中,复制对象听起来简单,但实际操作却可能让你抓狂。直接赋值一个对象只是复制了它的“地址”,修改新对象会连带改变原对象。深拷贝(deep copy)解决了这个问题,它创建了一个完全独立的副本,包括所有嵌套对象。本文将以轻松、易懂的风格,详细讲解 JavaScript 深拷贝的原理、方法和应用场景,适合初学者和有经验的开发者。基于 2025 年 5 月的 ECMAScript 标准,我们将涵盖最新技术,确保你能轻松掌握深拷贝。

1. 理解 JavaScript 中的复制

JavaScript 中的数据分为两种类型:基本类型(如数字、字符串、布尔值)和引用类型(如对象、数组)。基本类型的赋值会复制值,互不影响:

let a = 5;
let b = a;
b = 10;
console.log(a); // 5
console.log(b); // 10

引用类型的赋值只复制引用,指向同一块内存:

let obj1 = { name: '张伟' };
let obj2 = obj1;
obj2.name = '李娜';
console.log(obj1.name); // 李娜
console.log(obj2.name); // 李娜

这说明 obj1 和 obj2 指向同一个对象。想要独立修改副本而不影响原对象,就需要深拷贝。

2. 浅拷贝与深拷贝的区别

浅拷贝只复制对象的顶层属性。对于基本类型属性,复制值;对于引用类型属性(如嵌套对象),复制引用。常见浅拷贝方法包括扩展运算符(...)和 Object.assign():

let original = { name: '张伟', address: { city: '上海' } };
let shallowCopy = { ...original };
shallowCopy.name = '李娜';
shallowCopy.address.city = '北京';
console.log(original.name); // 张伟
console.log(original.address.city); // 北京

修改 shallowCopy.address.city 会影响 original,因为 address 是引用类型,两个对象共享同一引用。

深拷贝则递归复制所有层级的属性,创建完全独立的副本。修改深拷贝的任何属性都不会影响原对象。以下是实现深拷贝的几种方法。

3. 深拷贝方法详解
3.1 使用 JSON.parse(JSON.stringify())

这是最简单的深拷贝方法,通过将对象序列化为 JSON 字符串再解析回对象。由于 JSON 不支持引用,生成的副本是独立的。

优点

  • 简单,无需额外依赖。
  • 适合简单对象(如纯 JSON 数据)。

缺点

  • 不支持函数、undefined、循环引用、Date、RegExp 等特殊类型。
  • 性能可能较低,处理大对象时较慢。

示例

let original = { name: '张伟', address: { city: '上海' } };
let deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.address.city = '北京';
console.log(original.address.city); // 上海
console.log(deepCopy.address.city); // 北京

局限性:如果对象包含函数或 undefined,它们会被忽略:

let obj = { name: '张伟', sayHi: () => console.log('你好'), age: undefined };
let copy = JSON.parse(JSON.stringify(obj));
console.log(copy); // { name: '张伟' }
3.2 使用 structuredClone()

structuredClone() 是 2022 年引入的内置方法,使用结构化克隆算法,支持更多数据类型,包括循环引用、Date 和 RegExp。自 2022 年 3 月起,所有主流浏览器均支持此方法。

优点

  • 内置,无需依赖。
  • 支持循环引用和复杂类型(如 Date、RegExp)。
  • 性能优化,适合现代应用。

缺点

  • 不支持函数,会抛出 DataCloneError。
  • 需确保浏览器支持(现代环境通常无问题)。

示例

let original = { name: '张伟', birthDate: new Date('1990-01-01') };
original.self = original; // 循环引用
let deepCopy = structuredClone(original);
deepCopy.name = '李娜';
deepCopy.birthDate.setFullYear(2000);
console.log(original.name); // 张伟
console.log(original.birthDate); // 1990-01-01
console.log(deepCopy.name); // 李娜
console.log(deepCopy.birthDate); // 2000-01-01
console.log(deepCopy.self === deepCopy); // true

局限性:尝试克隆函数会报错:

let obj = { name: '张伟', sayHi: () => console.log('你好') };
try {let copy = structuredClone(obj);
} catch (error) {console.error(error.message); // DataCloneError
}
3.3 手动递归复制

手动实现深拷贝通过递归遍历对象属性,复制每一层。对于数组和对象,递归调用复制函数;对于基本类型,直接返回。

优点

  • 完全控制,可定制特殊类型处理。
  • 支持函数和其他非标准类型。

缺点

  • 实现复杂,需处理循环引用。
  • 性能较低,需额外逻辑支持特殊类型。

基本实现

function deepCopy(obj) {if (obj === null || typeof obj !== 'object') {return obj;}if (obj instanceof Date) {return new Date(obj.getTime());}if (Array.isArray(obj)) {return obj.map(deepCopy);}const copy = {};for (let key in obj) {if (obj.hasOwnProperty(key)) {copy[key] = deepCopy(obj[key]);}}return copy;
}let original = { name: '张伟', address: { city: '上海' }, birthDate: new Date('1990-01-01') };
let deepCopy = deepCopy(original);
deepCopy.address.city = '北京';
deepCopy.birthDate.setFullYear(2000);
console.log(original.address.city); // 上海
console.log(original.birthDate); // 1990-01-01

处理循环引用:需使用 Map 跟踪已复制对象:

function deepCopy(obj, seen = new Map()) {if (obj === null || typeof obj !== 'object') {return obj;}if (seen.has(obj)) {return seen.get(obj);}if (obj instanceof Date) {return new Date(obj.getTime());}if (Array.isArray(obj)) {const copy = [];seen.set(obj, copy);obj.forEach(item => copy.push(deepCopy(item, seen)));return copy;}const copy = {};seen.set(obj, copy);for (let key in obj) {if (obj.hasOwnProperty(key)) {copy[key] = deepCopy(obj[key], seen);}}return copy;
}let original = { name: '张伟' };
original.self = original;
let deepCopy = deepCopy(original);
console.log(deepCopy.self === deepCopy); // true
3.4 使用 Lodash 的 _.cloneDeep()

Lodash 是一个流行的工具库,其 _.cloneDeep() 方法提供可靠的深拷贝功能,支持循环引用和多种数据类型。

优点

  • 可靠,处理多种边缘情况。
  • 社区支持广泛,适合大型项目。

缺点

  • 需引入依赖,增加项目体积。

使用方法: 先安装 Lodash:

npm install lodash

然后:

import _ from 'lodash';let original = { name: '张伟', address: { city: '上海' } };
let deepCopy = _.cloneDeep(original);
deepCopy.address.city = '北京';
console.log(original.address.city); // 上海
console.log(deepCopy.address.city); // 北京
4. 实际应用场景

深拷贝在前端开发中非常实用,以下是几个常见场景:

4.1 购物车折扣

假设有一个购物车对象:

let cart = {items: [{ name: '笔记本电脑', price: 1000, specs: { ram: '16GB', cpu: 'i7' } },{ name: '鼠标', price: 50 }],total: 1050
};
let discountedCart = structuredClone(cart);
discountedCart.items.forEach(item => item.price *= 0.9);
discountedCart.total = discountedCart.items.reduce((sum, item) => sum + item.price, 0);
console.log(cart.total); // 1050
console.log(discountedCart.total); // 945

深拷贝确保折扣操作不影响原购物车。

4.2 状态管理

在 React 或 Vue 中,深拷贝状态对象以避免直接修改原始状态:

let state = { user: { name: '张伟', preferences: { theme: 'dark' } } };
let newState = structuredClone(state);
newState.user.preferences.theme = 'light';
console.log(state.user.preferences.theme); // dark
4.3 数据备份

在编辑表单时,深拷贝原始数据以支持撤销操作:

let formData = { name: '张伟', address: { city: '上海' } };
let backup = JSON.parse(JSON.stringify(formData));
formData.address.city = '北京';
console.log(backup.address.city); // 上海
5. 选择合适的方法

以下是各方法的对比:

方法优点缺点
JSON.parse(JSON.stringify())简单,无依赖不支持函数、undefined、循环引用
structuredClone()内置,支持循环引用和复杂类型不支持函数,需现代浏览器支持
手动递归复制完全控制,可定制实现复杂,需处理特殊类型和循环引用
Lodash _.cloneDeep()可靠,处理多种边缘情况需引入依赖,增加项目体积

选择建议

  • 简单对象:使用 JSON.parse(JSON.stringify()),快速且无需依赖。
  • 现代项目:优先选择 structuredClone(),支持复杂类型和循环引用。
  • 特殊需求:手动实现深拷贝,适合定制化场景。
  • 已有 Lodash:使用 _.cloneDeep(),可靠且省心。
6. 注意事项
  • 性能:对于大对象,JSON.parse(JSON.stringify()) 和 structuredClone() 性能较优,但手动复制可能较慢。
  • 原型链:JSON.parse(JSON.stringify()) 创建普通对象,可能丢失原型信息;structuredClone() 保留部分原型。
  • 特殊类型:如需复制函数或非枚举属性,需手动实现或使用 Lodash。
  • 浏览器兼容性:structuredClone() 自 2022 年起广泛支持,适用于现代环境。
7. 2025 年趋势

JavaScript 仍是前端开发的核心,深拷贝在状态管理、数据处理中需求旺盛。structuredClone() 因其内置性和广泛支持,成为首选方法。开发者可结合项目需求选择合适方案。

8. 总结

深拷贝是 JavaScript 中处理复杂对象的重要技术,确保副本独立性。通过 JSON.parse(JSON.stringify())、structuredClone()、手动递归复制或 Lodash 的 _.cloneDeep(),你可以根据数据类型和项目需求选择最佳方法。希望能帮助你轻松掌握深拷贝!

相关文章:

  • 2025年- H38-Lc146 --142.环形链表(快慢指针,快2慢1,快1慢1)--Java版
  • 前端流行框架Vue3教程:21. 插槽(3)
  • C语言| 指针变量的初始化
  • 如何测试北斗卫星通讯终端的性能?
  • DEBUG:Lombok 失效
  • C++类与对象--6 特性二:继承
  • std::vector<>.emplace_back
  • flutter设置最大高度,超过最大高度时滑动显示
  • 使用frp内网穿透本地的虚拟机
  • spring event事件(四)内部事件(1)ApplicationReadyEvent
  • 介绍Buildroot
  • 2025ICPC南昌邀请赛题解
  • 记录学习的第三十六天
  • ZYNQ Cache一致性问题解析与实战:从原理到创新优化
  • PEFT简介及微调大模型DeepSeek-R1-Distill-Qwen-1.5B
  • mysql不能聚合之数据清洗逗号
  • 第7天-Python+PyEcharts实现股票分时图实战教程
  • OD 算法题 B卷 【需要打开多少监视器】
  • 算法打卡第一天
  • 每日算法刷题Day10 5.19:leetcode不定长滑动窗口求最长/最大4道题,结束定长滑动窗口,用时1h
  • 甘肃多地发生旱情,三大骨干工程已累计调水2.45亿立方米
  • 以安全部门确认哈马斯加沙地带军事部门领导人被打死
  • 外交部:中方高度重视同太平洋岛国的关系,双方友好合作关系正不断深化和发展
  • 《适合我的酒店》:让读者看到梦想,也是作家的职责
  • 这群“工博士”,把论文“写”在车间里
  • 韦尔股份拟更名豪威集团:更全面体现公司产业布局,准确反映未来战略发展方向