docs/Glossary
浅拷贝
🤔 提出一些问题
- 浅拷贝对象和源对象之间怎样互相影响?
- “浅拷贝”是指:考虑拷贝对象的深浅的意思吗?
- “浅”是指第一层共享,第二层、第三层就不共享了吗?
- “浅拷贝”与状态管理中的概念
不可变更新(immutable) 有怎样的关系?
1. 数组里嵌套对象 + 内存分析
- 有一个初始数组:
const arr1 = ["面条", { list: {...} }]; // 栈内存: arr1 → 0x0010 // 堆内存: 0x0010 → Array object(arr1) ├─ 0 → "面条" └─ 1 → 0x0020 0x0020 → { list: {...} }
- 使用
Array.from(arrayLike)进行浅拷贝:const arr2 = Array.from(arr1); // 栈内存: arr1 → 0x0010 arr2 → 0x0030 // 堆内存: 0x0010 → Array object(arr1) ├─ 0 → "面条" └─ 1 → 0x0020 0x0030 → Array object(arr2) ├─ 0 → "面条" └─ 1 → 0x0020 // 同一个引用❗️ 0x0020 → { list: {...} }
- 改值类型:
// 第一种: 改源对象 arr1[0] = "火鸡"; 此时arr1 = ["火鸡", { list: {...} }]; 此时arr2 = ["面条", { list: {...} }]; // 第二种: 改浅拷贝对象 arr2[0] = "火鸡"; 此时arr1 = ["面条", { list: {...} }]; 此时arr2 = ["火鸡", { list: {...} }];可以看到,无论是改源对象,还是改浅拷贝对象,另一方都没有改变。
所以,值类型并没有被共享。
- 改引用类型:
// 第一种: 改源对象 arr1[1].list = { "小红帽", "大灰狼" }; 此时arr1 = ["面条", { "小红帽", "大灰狼" }]; 此时arr2 = ["面条", { "小红帽", "大灰狼" }];(跟随改变了❗️) // 第二种: 改浅拷贝对象 arr2[1].list = { egg: '3个' , money: '10块零三毛'}; 此时arr1 = ["面条", { egg: '3个' , money: '10块零三毛'}];(跟随改变了❗️) 此时arr2 = ["面条", { egg: '3个' , money: '10块零三毛'}];这证明了:arr1的索引1和arr2的索引1,指向了同一个对象
- 再来一个例子,只要上面的例子你看懂了,这个例子你就会觉得轻而易举:
// 第一种: 改源对象 arr1[1].list.egg.count.number = 99; 此时arr1 = ["面条", { list: { egg: { count: { number: 99 }, unit: "个" }, flour: "面粉", water: "水" }}]; 此时arr2 = ["面条", { list: { egg: { count: { number: 99 }, unit: "个" }, flour: "面粉", water: "水" }}];(发生改变了❗️) // 第二种: 改拷贝对象 arr2[1].list.egg.unit = { id: 'asdfasd001', emoji: '🐶' }; 此时arr1 = ["面条", { list: { egg: { count: { number: 99 }, unit: { id: 'asdfasd001', emoji: '🐶' } }, flour: "面粉", water: "水" }}];(发生改变了❗️) 此时arr2 = ["面条", { list: { egg: { count: { number: 99 }, unit: { id: 'asdfasd001', emoji: '🐶' } }, flour: "面粉", water: "水" }}];解释
因为arr1的索引1和arr2的索引1指向的是同一个对象,所以只要是这个共享对象(
0x0020)内部的属性,无论这个属性多深,它都会跟随改变。这也验证了我们刚刚的结论:arr1的索引1和arr2的索引1,指向了同一个对象
总结
官方MDN中的定义:浅拷贝是属性与拷贝的源对象属性共享相同引用的副本。
-
通俗版解释:
-
浅拷贝对象的变量本身(
arr2),会获得一个新的地址(0x0030) -
对于浅拷贝对象内部首层的每一个属性,如果该属性的值是引用类型,就会引用源对象中对应属性的对应值,也就是共享了相同的引用(
0x0020)。 -
所以我们管浅拷贝对象,叫做一个“副本”。
-
-
专业版解释:
- 浅拷贝会创建一个新的顶层对象(新的堆地址
0x0030),因此拷贝对象变量本身是独立的。 - 对于浅拷贝对象内部首层属性,如果该属性的值是引用类型(对象、数组、函数等),则复制的是引用的副本,即两个对象共享相同的底层引用地址(
0x0020)。 - 所以“浅拷贝对象”被称为源对象的副本,指的是它的外层结构独立于源对象,而内部引用保持共享。
- 浅拷贝会创建一个新的顶层对象(新的堆地址
2. 对象里嵌套数组 + 内存分析
==这次可以尝试猜一猜输出结果哦==
- 有一个初始对象:
const obj1 = { emoji: '🥹', userList: [ { name: 'A', age: 7 }, { name: 'B', age: 10 }, { name: 'C', age: 1 }, { name: 'D', age: 32 } ] }; // 栈内存: obj1 = 0x0030; // 堆内存: 0x0030 → { emoji: '🥹', userList: [ { name: 'A', age: 7 }, { name: 'B', age: 10 }, { name: 'C', age: 1 }, { name: 'D', age: 32 } ] };
- 使用
Object.assign(target, ...source)进行浅拷贝:const obj2 = Object.assign({}, obj1); // 栈内存: obj1 -> 0x0030 obj2 → 0x0040 // 堆内存: 0x0030 → { emoji: '🥹', userList: 0x0050 } 0x0040 → { emoji: '🥹', userList: 0x0050 // 同一个引用❗️ } 0x0050 → [ { name: 'A', age: 7 }, { name: 'B', age: 10 }, { name: 'C', age: 1 }, { name: 'D', age: 32 } ]