浅拷贝、深拷贝与引用赋值
在处理引用类型(对象、数组)时,「引用赋值」「浅拷贝」「深拷贝」是核心概念,其本质差异体现在内存地址的指向关系上。
一、引用赋值:非拷贝操作,共享内存地址
引用赋值是将引用类型的内存地址(指针)赋值给变量,未创建任何新的对象或数组,赋值后的变量与原变量指向同一块内存空间。对任一变量的修改,都会直接作用于共享的内存数据,进而影响所有指向该地址的变量。
1. 对象的引用赋值
变量存储对象的内存地址,赋值后两变量指向同一对象内存空间。
1 | |
2. 数组的引用赋值
变量存储数组的内存地址,赋值后两变量指向同一数组内存空间。
1 | |
核心结论
引用赋值的本质是「指针复用」:变量仅存储引用类型的内存地址,未创建新内存空间,因此所有指向同一地址的变量会共享数据修改结果。
二、浅拷贝:创建新容器,嵌套引用共享地址
浅拷贝会为对象或数组创建新的顶层内存空间(新对象/新数组),但对嵌套的引用类型(如子对象、子数组)仅复制其内存地址,未创建新的嵌套内存空间。即顶层数据独立,嵌套数据共享。
1. 对象的浅拷贝实现与特性
常用实现方式:Object.assign({}, obj)、扩展运算符 { ...obj }。
1 | |
2. 数组的浅拷贝实现与特性
常用实现方式:Array.from(arr)、扩展运算符 [ ...arr ]、arr.slice()。
1 | |
核心结论
浅拷贝的核心是「顶层独立,嵌套共享」:新对象/数组拥有独立顶层内存地址,但嵌套引用类型仍复用原内存地址,因此嵌套数据的修改会同步影响原引用类型。
三、深拷贝:全层级独立,无共享内存地址
深拷贝会递归复制引用类型的所有层级,为顶层及嵌套的每一个引用类型(对象、数组)都创建新的内存空间。新对象/数组与原引用类型完全独立,不存在任何内存地址共享,修改任一数据均不影响对方。
1. 对象的深拷贝实现与特性
常用实现方式:JSON.parse(JSON.stringify(obj))(基础场景)、第三方库(如 lodash _.cloneDeep)。
1 | |
2. 数组的深拷贝实现与特性
同对象实现逻辑,JSON.parse(JSON.stringify(arr)) 可直接用于数组深拷贝。
1 | |
核心结论
深拷贝的核心是「全层级独立」:新引用类型及其所有嵌套引用类型均拥有独立内存地址,与原引用类型无任何内存共享,数据修改完全隔离。
四、核心疑难点解答
问:{ ...obj } 与 [ ...arr ] 已创建新对象/数组,为何修改仍影响原数据?
{ ...obj } 和 [ ...arr ] 仅为顶层创建新内存地址,属于浅拷贝。对于嵌套的引用类型(如 obj.nested、arr[1]),新对象/数组仍存储原嵌套类型的内存地址,未创建新嵌套内存空间。因此,修改嵌套数据时,操作的是原嵌套类型的内存地址,会同步影响原对象/数组。
问:let ref = obj1 后执行 ref = obj2,obj1 会变为 obj2 吗?
不会。ref 作为变量,存储的是引用类型的内存地址(指针)。初始 ref = obj1 时,ref 指向 obj1 的内存地址(如 0x001);执行 ref = obj2 后,ref 的指针仅从 0x001 切换为 obj2 的内存地址(如 0x002),并未修改 obj1 所在内存空间(0x001)的数据。同理,数组场景 let ref = arr1; ref = arr2 中,arr1 的内存数据也不会变化。
问:引用赋值、浅拷贝、深拷贝的内存地址关系差异?
| 操作类型 | 对象/数组顶层地址 | 嵌套引用类型地址 | 修改嵌套数据影响原数据? |
|---|---|---|---|
| 引用赋值 | 与原数据相同 | 与原数据相同 | 是 |
| 浅拷贝 | 与原数据不同 | 与原数据相同 | 是 |
| 深拷贝 | 与原数据不同 | 与原数据不同 | 否 |
五、其他语言的概念一致性
「浅拷贝」「深拷贝」的核心定义在所有支持引用类型的语言中一致:浅拷贝仅复制顶层,嵌套引用共享内存;深拷贝递归复制所有层级,全内存独立。差异仅体现在实现方式,且均需兼顾对象(如 Java 类实例、Python 字典)与数组(如 Java 数组、Python 列表):
| 语言 | 浅拷贝实现(对象/数组) | 深拷贝实现(对象/数组) |
|---|---|---|
| TypeScript | 对象:{ ...obj }/Object.assign;数组:[ ...arr ] |
对象/数组:JSON.parse(JSON.stringify())/_.cloneDeep |
| Java | 对象:clone()(默认);数组:Arrays.copyOf() |
对象/数组:重写 clone() 递归拷贝/序列化(如 ObjectOutputStream) |
| Python | 对象:copy.copy();数组(列表):copy.copy() |
对象/数组:copy.deepcopy() |
| C# | 对象:MemberwiseClone();数组:Array.Copy() |
对象/数组:序列化(如 BinaryFormatter)/手动递归 |
六、总结
- 引用赋值是指针复用,未创建新内存空间,所有变量共享同一引用类型数据;
- 浅拷贝为顶层创建新内存空间,但嵌套引用类型仍共享原地址,仅顶层数据独立;
- 深拷贝递归创建全层级新内存空间,新引用类型与原数据完全独立,无任何共享;
- 所有语言中,浅拷贝与深拷贝的核心定义一致,差异仅在实现方式,需同时关注对象与数组的嵌套场景。
掌握三者的内存地址关系,是避免引用类型操作Bug的关键,也是后续学习复杂数据结构(如树形结构)的基础。