前言
在前端开发中 浅拷贝
和 深拷贝
可以说是面试中经常碰到的经典问题, 并且在实战项目开发过程中, 也常常会因为数据拷贝问题, 导致一些隐藏的 BUG
;到底什么是 深浅拷贝
呢,又有哪些开发场景会遇到,带着这些疑惑咱们一起来学习吧, 本文将会挨个进行揭秘
简介
我们来通过内存分配学习一下深浅拷贝吧, 首先复习下赋值
赋值
一个变量的数据,赋值给另一个变量;
const data1 = 1
const data2 = data1
const data3 = {a:1,b:2}
const data4 = data3 // 新栈旧堆
/*
栈 堆
data1:1 地址1:{a:1,b:2}
data2:1
data3:地址1
data4:地址1
*/
这里要小心,原始类型直接开辟新的栈空间放数据,互不影响;对象类型也是开辟新的内存空间,但是放的是堆中地址,相互影响
浅拷贝
我们接着来看浅拷贝和对象赋值内存中的变化
赋值后生成一个新的对象也就是有新的【堆】地址,并且这个对象里有原对象的所有数据,其中原始类型拷贝值,引用/对象类型拷贝地址
const data1 = { a: 1, b: 2, c: [3, 4, 5] };
const data2 = data1
/*
# 普通赋值:新栈旧堆
栈 堆
data1:地址1 地址1: { a: 1, b: 2, c: [3, 4, 5] }
data2:地址1
# 浅拷贝赋值:新栈新堆(大家重点看c的变化)
栈 堆
data1:地址1 地址1: { a: 1, b: 2, c: [3, 4, 5] };
data2:地址2 地址2: { a: 1, b: 2, c:地址1中的c }
*/
深拷贝
明确对象赋值和浅拷贝区别后,咱们再来看看深拷贝的内存是怎么发生变化的,期待吗哈哈(*❦ω❦)
赋值后生成一个新的对象也就是有新的【堆】地址,并且这个对象里有原对象的所有数据,完全独立互不影响
const data1 = { a: 1, b: 2, c: [3, 4, 5] };
const data2 = data1
/*
# 普通赋值:新栈旧堆
栈 堆
data1:地址1 地址1: { a: 1, b: 2, c: [3, 4, 5] }
data2:地址1
# 深拷贝赋值:新栈新堆(完全独立互不影响)
栈 堆
data1:地址1 地址1: { a: 1, b: 2, c: [3, 4, 5] }
data2:地址2 地址2: { a: 1, b: 2, c: [3, 4, 5] }
*/
和浅拷贝的区别:浅拷贝里面有原对象的地址 存在修改相互影响,而深拷贝完全独立互补影响
特性
咱们来汇总下三者之间的特性吧
种类 | 和原数据是否指向同一个对象地址 | 数据原始类型 | 数据引用类型 |
---|---|---|---|
赋值 | 同一个地址 | 影响 | 影响 |
浅拷贝 | 新地址 | 不影响 | 影响 |
深拷贝 | 新地址 | 不影响 | 不影响 |
浅拷贝
知道深浅拷贝相关概念后,如何实现浅拷贝呢?
通过下述几种方式都可以实现
-
展开运算符
-
Object.assign(目标对象, 对象1, 对象2,...)
-
函数库lodash的_.clone方法
-
Array.prototype.concat()
-
Array.prototype.slice()
<script src="https://unpkg.com/lodash@4.17.21/lodash.js"></script>
<script>
const obj1 = { a: 1, b: 2, c: [3, 4, 5] };
// 浅拷贝
// const obj2 = { ...obj1 };
// const obj2 = Object.assign({}, obj1);
// const obj2 = _.clone(obj1);
// 数据是原始类型互不影响
// console.log(obj1.a, obj2.a); // 1、1
// obj1.a = 11;
// console.log(obj1.a, obj2.a); // 11、1
// 数据是对象类型相互影响
console.log(obj1.c[0], obj2.c[0]); // 3、3
obj1.c[0] = 33;
console.log(obj1.c[0], obj2.c[0]); // 33、33
</script>
深拷贝
接着咱们来说深拷贝的实现方式
内置对象JSON
这种方式相对实战公司用的多一些,主要方便省事吧 但并不是最优解
const obj1 ={num:1,d: (new Date)}
const obj2 = JSON.parse(JSON.stringify(obj1))
{num: 1, d: '2021-10-15T05:08:09.012Z'}
对于部分谁类型,拷贝之后数据会丢失,例如日期对象拷贝后就会丢失 这个点也是面试官经常会追问的
lodash
通过lodash第三方模块的cloneDeep
<script src="https://unpkg.com/lodash@4.17.21/lodash.js"></script>
<script>
const obj1 = {
msg: 1,
likes: ["吃饭", "睡觉", "挤痘痘"],
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.likes[0]); // 1
console.log(obj2.likes[0]); // 1
obj1.likes[0] = 2;
console.log(obj1.likes[0]); // 2
console.log(obj2.likes[0]); // 深拷贝1、浅拷贝2
</script>
自己写递归
这种方式一般不会用,常出现在面试说lodash的cloneDeep原理,或者不用第三方的自己写如何实现
<script src="https://unpkg.com/lodash@4.17.21/lodash.js"></script>
<script>
const obj1 = {
msg: 1,
likes: ["吃饭", "睡觉", "挤痘痘"],
};
const obj2 = deepCopy(obj1);
console.log(obj1.likes[0]); // 1
console.log(obj2.likes[0]); // 1
obj1.likes[0] = 2;
console.log(obj1.likes[0]); // 2
console.log(obj2.likes[0]); // 深拷贝1、浅拷贝2
function deepCopy(obj) {
var result = Array.isArray(obj) ? [] : {};
for (var key in obj) {
if (typeof obj[key] === "object" && obj[key] !== null) {
result[key] = deepCopy(obj[key]); //递归复制
} else {
result[key] = obj[key];
}
}
return result;
}
</script>
Facebook Immutable 推荐
这种方式可以有效规避传统深拷贝的内存占用,不过容易和es6中Set、Map数据结构搞混淆 本文不做详细解释,下述是大致代码大家可以参考
<script src="https://unpkg.com/immutable@3.8.2/dist/immutable.js"></script>
<script>
// Immutable
// const a = { msg: "hello 1" };
// const b = a;
// console.log(a.msg, b.msg);
// b.msg = "hello 2";
// console.log(a.msg, b.msg); // 深 1 2、 浅 2 2
// Map 是immutable中 用来标识对象的
// List 是immutable中 用来标识数组的
// 切记切记切记 千万别和es6的Map、Set数据结构搞混淆
// const a = Immutable.fromJS({ msg: "hello 1", msg2: 666 });
// console.log(a.getIn(["msg"]));
// console.log(a.getIn(["msg2"]));
// const b = a.setIn(["msg"], "hello 2");
// console.log(a.getIn(["msg"]));
// console.log(a.getIn(["msg2"]));
// console.log(b.getIn(["msg"]));
// console.log(b.getIn(["msg2"]));
// ==========
// ==========
// ==========
// ==========
const a = Immutable.fromJS({
num: 666,
list: [
{ id: 1, title: "商品1", num: 1, price: 1.11 },
{ id: 2, title: "商品2", num: 2, price: 2.22 },
{ id: 3, title: "商品3", num: 3, price: 3.33 },
],
});
console.log(a.getIn(["list", 1, "num"])); // 2
// a.setIn(['list', 1, 'num'], 5)
const b = a.updateIn(["list", 1, "num"], (oldData) => oldData + 1);
console.log(a.getIn(["list", 1, "num"])); // 2
console.log(b.getIn(["list", 1, "num"])); // 3
</script>
immer.js 推荐
immer
的作者同时也是 mobx
的作者。mobx
又像是把 Vue
的一套东西融合进了 React
,已经在社区取得了不错的反响。immer
则是他在 immutable
方面所做的另一个实践。
本文主要讲深浅拷贝的视线方案,在这里就不详细介绍immer.js
大家可以进一步查阅相关资料学习
好了 到这里咱们就讲完了 最后来说一下先关使用场景吧为啥面试频率如此之高,实战哪里又会遇到呢? 带着这个好奇怎么最后来学习下💪🏻ヾ(◍°∇°◍)ノ゙
场景
浅拷贝:
- vue3 ref修改子组件数据
Object.assign(模型数据, 新数据)
- vue/react移动端分页上拉合并数据
[...旧数据,...新数据]
等等
深拷贝:
- vue2/react数据回显
- react-redux 状态管理 等等
你实战工作哪些具体场景用到深浅拷贝了呢?
不防评论区告诉大家吧 ThanksO(∩_∩)O谢谢~