文章目录
- 前言
- 一、什么是拷贝
- 二、为什么会有深浅拷贝
- 三、什么是浅拷贝
- 四、常见的浅拷贝方式
-
- [1. Object.assign()](#1. Object.assign())
- [2. 展开运算符](#2. 展开运算符)
- [3. slice()](#3. slice())
- 五、什么是深拷贝
- 六、常见的深拷贝方式
-
- [1. structuredClone(推荐)](#1. structuredClone(推荐))
- [2. JSON 方式](#2. JSON 方式)
- [3. 递归手写深拷贝](#3. 递归手写深拷贝)
- 七、深拷贝与浅拷贝的核心区别
- 八、实际开发中该怎么选
- 总结
前言
在学习 JavaScript、Python、Java 等编程语言时,"深拷贝"和"浅拷贝"几乎是绕不开的知识点。
很多初学者都会遇到这种情况:
js
const a = { name: "Tom" };
const b = a;
b.name = "Jack";
console.log(a.name);
结果输出:
js
Jack
很多人会疑惑:
"我改的是 b,为什么 a 也变了?"
其实,这背后就涉及到"拷贝"的本质。
一、什么是拷贝
所谓"拷贝",本质上就是:
把一个变量的数据复制给另一个变量。
但这里有一个关键问题:
到底复制的是"数据本身",还是"数据地址"?
这就引出了:
- 浅拷贝(Shallow Copy)
- 深拷贝(Deep Copy)
二、为什么会有深浅拷贝
先理解一个核心概念:
基本类型
例如:
js
Number
String
Boolean
null
undefined
它们的数据直接存储在变量里。
例如:
js
let a = 10;
let b = a;
b = 20;
这时候:
js
a === 10
b === 20
因为复制的是值本身。
引用类型
例如:
js
Object
Array
Function
它们真正存储的是:
内存地址
举个例子:
js
const obj1 = {
name: "Tom"
};
const obj2 = obj1;
实际上:
text
obj1 ---> 内存地址A
obj2 ---> 内存地址A
两个变量指向的是同一块内存。
所以修改 obj2 时,obj1 也会变化。
三、什么是浅拷贝
浅拷贝:
只复制第一层数据。
如果对象里面还有对象,那么内部对象仍然共享同一个地址。
例如:
js
const obj1 = {
name: "Tom",
info: {
age: 18
}
};
const obj2 = { ...obj1 };
obj2.name = "Jack";
obj2.info.age = 20;
console.log(obj1);
结果:
js
{
name: "Tom",
info: {
age: 20
}
}
为什么 name 没变,而 age 变了?
因为:
js
name
是第一层,已经复制了一份。
而:
js
info
是对象。
浅拷贝只复制它的地址。
所以:
text
obj1.info === obj2.info
结果为:
js
true
四、常见的浅拷贝方式
1. Object.assign()
js
const newObj = Object.assign({}, oldObj);
2. 展开运算符
这是现在最常用的方式:
js
const newObj = { ...oldObj };
数组也一样:
js
const arr2 = [...arr1];
3. slice()
数组浅拷贝:
js
const arr2 = arr1.slice();
五、什么是深拷贝
深拷贝:
完全复制一个对象,包括对象里面的对象。
复制之后:
- 两个对象完全独立
- 修改新对象不会影响旧对象
例如:
js
const obj1 = {
name: "Tom",
info: {
age: 18
}
};
const obj2 = structuredClone(obj1);
obj2.info.age = 20;
console.log(obj1.info.age);
结果:
js
18
因为:
text
obj1.info !== obj2.info
它们已经不是同一个对象了。
六、常见的深拷贝方式
1. structuredClone(推荐)
现代浏览器与 Node.js 已支持:
js
const newObj = structuredClone(oldObj);
优点:
- 真正深拷贝
- 支持数组、对象
- 使用简单
缺点:
- 老版本环境可能不支持
2. JSON 方式
经典写法:
js
const newObj = JSON.parse(JSON.stringify(oldObj));
这是很多初学者最先接触的方法。
优点:
- 简单
- 兼容性好
缺点非常明显:
无法处理:
js
function
undefined
Symbol
Date
Map
Set
循环引用
例如:
js
const obj = {
time: new Date()
};
const copy = JSON.parse(JSON.stringify(obj));
console.log(copy.time);
结果:
js
字符串
Date 类型丢失了。
3. 递归手写深拷贝
面试里非常经典。
核心思想:
text
如果是基本类型,直接返回
如果是对象,则递归复制每个属性
简单实现:
js
function deepClone(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
const newObj = Array.isArray(obj) ? [] : {};
for (let key in obj) {
newObj[key] = deepClone(obj[key]);
}
return newObj;
}
这是理解深拷贝原理最好的方式。
七、深拷贝与浅拷贝的核心区别
| 对比项 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 是否复制嵌套对象 | 否 | 是 |
| 是否共享引用地址 | 会 | 不会 |
| 修改新对象影响旧对象 | 可能影响 | 不影响 |
| 性能 | 较快 | 较慢 |
| 使用场景 | 简单数据 | 复杂嵌套数据 |
你可以简单理解:
text
浅拷贝:复制"表面"
深拷贝:彻底复制
八、实际开发中该怎么选
很多人学完后最想知道的是:
"项目里到底该用哪个?"
其实很简单。
用浅拷贝的情况
如果数据结构不复杂:
js
{
name: "Tom",
age: 18
}
直接:
js
...
Object.assign()
就够用了。
性能更好。
用深拷贝的情况
如果存在:
- 对象嵌套对象
- 数组嵌套对象
- React/Vue 状态管理
- 后端接口数据加工
那么更推荐:
js
structuredClone()
或者成熟库:
js
lodash.cloneDeep()
否则很容易出现:
text
一个地方修改数据
整个页面一起变化
这种 Bug 在真实项目里非常常见。
总结
深拷贝和浅拷贝,本质区别只有一句话:
text
是否复制"引用地址"
浅拷贝:
text
只复制第一层
深拷贝:
text
递归复制所有层
初学阶段最重要的不是死记 API,而是理解:
text
对象存的是地址
一旦理解这一点,深浅拷贝的问题就会瞬间清晰很多。