JavaScript之深浅拷贝
在 JavaScript 中,对象和数组的复制是开发中常见的操作,但由于基本类型与引用类型的内存存储机制不同,复制行为会呈现出不同的特性。本文将从底层原理出发,详细解析浅拷贝与深拷贝的区别,并通过代码示例演示如何正确实现,帮助开发者避免数据操作中的常见陷阱。
一、基础概念:值类型 vs 引用类型
在理解深浅拷贝之前,必须先明确 JavaScript 中的两种数据类型:
1. 值类型(基本类型)
- 包括
Number
、String
、Boolean
、Null
、Undefined
、Symbol
、BigInt
- 特点:数据直接存储在栈内存中,赋值时会创建新的副本
let a = 10;
let b = a; // 栈内存中创建新值 10,b 与 a 互不影响
b = 20;
console.log(a); // 10
2. 引用类型
- 包括
Object
、Array
、Function
、Date
等 - 特点:栈内存存储对象的引用地址,堆内存存储实际数据
let obj1 = { name: 'Alice' };
let obj2 = obj1; // obj2 与 obj1 指向同一个堆内存地址
obj2.name = 'Bob';
console.log(obj1.name); // 'Bob'(修改会同步影响原对象)
二、直接赋值
开发中我们经常需要复制一个对象,如果直接赋值会有下面问题
直接赋值只是创建了一个新的变量,该变量和原变量指向同一个对象。这意味着两个变量实际上引用的是内存中的同一个对象,无论通过哪个变量对对象进行修改,另一个变量访问的对象也会发生改变。
示例代码如下:
let original = { a: 1, b: { c: 2 } };
let copied = original;copied.a = 10;
copied.b.c = 20;console.log(original.a); // 输出: 10
console.log(original.b.c); // 输出: 20
在这个例子中,copied
变量和 original
变量指向的是同一个对象,因此修改 copied
也会影响到 original
。
深浅拷贝只针对引用类型
三、浅拷贝(Shallow Copy)
1. 核心定义
- 创建一个新对象,浅层复制原对象的属性
- 对于基本类型属性:复制值本身(独立副本)
- 对于引用类型属性:复制引用地址(新旧对象共享同一堆内存)
2.实现方法
⑴.展开运算符 ...
const obj = {uname: 'pink',age: 18}const o = { ...obj }console.log(o)o.age = 20console.log(o)console.log(obj)
⑵.Object.assgin( )
const obj = {uname: 'pink',age: 18}const o = {}Object.assign(o, obj)console.log(o)o.age = 20console.log(o)console.log(obj)
3.浅拷贝,拷贝的是地址
const obj = {uname: 'pink',age: 18,family: {baby: '小pink'}}const o = {}Object.assign(o, obj)console.log(o)o.age = 20o.family.baby = '老pink'console.log(o)console.log(obj)
- 创建一个新对象,浅层复制原对象的属性
- 对于基本类型属性:复制值本身(独立副本)
- 对于引用类型属性:复制引用地址(新旧对象共享同一堆内存)
四、深拷贝(Deep Copy)
1. 核心定义
- 创建一个完全独立的新对象,递归复制原对象的所有层级属性
- 无论基本类型还是引用类型,新对象与原对象在内存中完全隔离
2.常见方法:
⑴方法①:递归实现深拷贝
原理:手动遍历对象的每一层,递归复制所有基本类型和引用类型。
function deepClone(obj) {if (typeof obj !== 'object' || obj === null) {return obj; // 如果是基本类型或null,直接返回}const clone = Array.isArray(obj) ? [] : {}; // 判断是数组还是对象for (let key in obj) {if (obj.hasOwnProperty(key)) {clone[key] = deepClone(obj[key]); // 递归复制嵌套属性}}return clone;
}// 测试例子
const original = { a: 1, b: { c: 2 } };
const copy = deepClone(original);
copy.b.c = 999;
console.log(original.b.c); // 输出 2(原对象未被修改)
缺点:
-
无法处理特殊对象(如 Date、RegExp)。
-
无法处理循环引用(如
obj.self = obj
会栈溢出)。
⑵方法②:使用 Lodash 的 cloneDeep
原理:Lodash 是一个流行的工具库,它的 cloneDeep
方法可以处理各种复杂对象的深拷贝。
-
安装并引入 Lodash:
npm install lodash
使用示例:
const _ = require('lodash');const original = { a: 1, b: { c: 2 } };
const copy = _.cloneDeep(original);copy.b.c = 999;
console.log(original.b.c); // 输出 2(原对象未被修改)
优点:
-
支持所有数据类型(包括 Date、RegExp 等)。
-
处理循环引用不会报错。
⑶方法③:通过 JSON.stringify()
实现
原理:将对象转为 JSON 字符串,再解析回对象,间接实现深拷贝。
const original = { a: 1, b: { c: 2 } };
const copy = JSON.parse(JSON.stringify(original));copy.b.c = 999;
console.log(original.b.c); // 输出 2(原对象未被修改)
缺点:
-
无法复制函数、
undefined
、Symbol 等特殊类型。 -
会丢失 Date 对象(转为字符串)。
-
循环引用会报错(如
obj.self = obj
)。
方法 | 优点 | 缺点 |
---|---|---|
递归实现 | 无需依赖第三方库 | 无法处理复杂对象和循环引用 |
Lodash.cloneDeep | 功能强大,支持所有数据类型 | 需要安装第三方库 |
JSON.stringify | 简单快速 | 丢失函数、特殊类型,不支持循环引用 |