笔者最近看了js的深拷贝浅拷贝的问题,刚开始比较难以理解,因为不同文章质量参差不齐,有的越看越懵,但是在看了好几篇文章,外加自己梳理后,慢慢搞清楚了,本文将尽量以通俗易懂简洁的方式讲解相关知识,以供大家更直观深入的理解。
学好深拷贝浅拷贝,这一篇就够了!
目录
首先,要想弄清楚深拷贝,浅拷贝问题,我们必须先弄清楚一个问题:
首先,要想弄清楚深拷贝,浅拷贝问题,我们必须先弄清楚一个问题:
js中的数据类型
js中数据类型共分为两大种:
1.基本数据类型:7个 Number,String,Boolean,null,undefined,symbol独一无二的值,bigint表示任意大的整数。(后两个为ES6新增)
2.引用数据类型:object(对象类型)(其中数组(Array)、函数(function)是一种特殊的对象)
关于这两大种数据类型的差别,主要有两方面:
1.存储在内存里的位置不一样。
基本数据类型是直接存储在栈中的简单数据段,占据空间小、大小固定,属于被频繁使用的数据。栈是存储基本类型值和执行代码的空间。
引用数据类型是存储在堆内存中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆 中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。
即引用数据类型存的值(即地址)在栈,但是值(地址)指向的数据在堆区
2.基本数据类型变量,对应的内存区域存储的直接是值。而引用数据类型存储的是存储的是相关值的地址。
例:(写function 是为了表示year,fruit为局部变量)
function test(){
var year=2022;
var fruit=[banana,apple,orange]
}
对应的内存图:
因为year为基本数据类型,所以其存储在栈区,而fruie为引用数据类型,其存的值为地址在栈区,其地址指向的值存在堆区。
综上,我们可以做一个小结:基本数据类型直接存的值和引用数据类型直接存的值(地址)都存在栈中,而引用数据类型存的地址指向的数据存在堆中。
接下来我们步入正题:深拷贝,浅拷贝
深拷贝浅拷贝都是对于引用数据类型来说的,基本数据类型没有深拷贝浅拷贝之分。
何为拷贝?拷贝就是复制粘贴。何为深拷贝,浅拷贝?我们先看一个例子:
var arr1=[0,1,2,3]
var arr2=arr1;
将数组arr1的值赋给arr2,如果我们执行语句arr2[0]=66;我们就会发现arr1[0]和arr2[0]的值都会变成66。这就是一个很典型的浅拷贝。
var num1=1;
var num2=num1;
若执行语句num2=200;那么num1的值还是1,num2的值为200,互不影响。
所以何为深拷贝浅拷贝?
1.根本的区别:浅拷贝就是简单的把指向别人的值的一个地址给复制过来,拷贝的深度不够 。深拷贝就是实实在在的把别人的值给复制过来。
2.直观的区别:浅拷贝就是双方不是独立的,还会互相影响;深拷贝是不会影响到彼此,是独立的个体。
所以我们说基本数据类型没有深拷贝浅拷贝之分,都是深拷贝,即互不影响。
现在请记住这一句话(非常重要):
如果我们直接用=用A变量对B变量进行赋值(B=A),我们实际上是将A中直接存的东西(值或地址)赋给了B。
浅拷贝深入讲解:
例:
//定义一个对象
var test={
tname:"oliver",
tage:18,
tcolor:['red','blue','yellow']
}
内存图:
解释一下:因为test是对象,所以他是引用数据类型,所以他在栈区存的是指向内容的地址,而他里面的内容(tname,tage,tcolor)则存在堆区。
对于堆区里面的tcolor是数组(特殊的对象),所以也是引用数据类型,所以存的是地址,指向数组中的内容(也存在堆中)
进行一次浅拷贝:
var test2={};
for(let i in test){
test2[i]=test[i];
}
test2.tcolor[0]="RED";
console.log(test);
console.log(test2);
运行结果:
我们发现test和test2里的tcolor[0]都发生了改变.
内存图:
解释一下:test2中依次直接复制了test中的值:test[0]的oliver,test[1]的18,test[2]的地址,所以test2[2]指向了和test[2]中同样的地址
深拷贝深入讲解:
一.一般的深拷贝
还用上面的例子:要想实现深拷贝,见以下代码:
var test2={};
for(let i in test){
if(typeof test[i]=='object'){//若test[i]为对象类型(数组)即引用数据类型,则进行深拷贝
test2[i]=[];
for(let j in test[i]){
test2[i][j]=test[i][j];
}
}
else{
test2[i]=test[i];
}
}
test2.tcolor[0]="RED";
console.log(test);
console.log(test2);
运行结果:
我们发现只有test2中的tcolor[1]发生了变化
内存图:
解释一下:对于数组tcolor,tcolor直接存储的是地址,但是tcolor[i]直接存储的是具体的数值而非地址,所以在代码运行的时候test2[tcolor].[0],test2[tcolor].[0],test2[tcolor].[0]依次被test[tcolor].[i]赋值。
二.对象嵌套时的深拷贝
接下来我们加大一下难度:如果对象里是的属性也是对象,那我们该怎么办?
举个例子吧:
//定义一个对象
var test={
tname:"oliver",
tage:18,
tcat:{
cname:"fatty",
cage:3,
cfood:{
morning:"fish",
noon:"potato"
}
}
}
我们发现test对象里面套了两层对象(tcat,cfood)
针对这种层层嵌套的问题,我们只能递归去解决,代码如下:
function deepCopy(obj){
let newObj={}
for(let i in obj){
if(typeof obj[i]=='object'){ //若obj[i]为object(即引用数据类型),进行递归
newObj[i]=deepCopy(obj[i])
}else{
newObj[i]=obj[i]
}
}
return newObj;
}
var test2=deepCopy(test);
test2.tcat.cname="FATTY";
console.log(test);
console.log(test2);
运行结果:
我们将test2中的tcat的cname修改成了FATTY,只有test2中的值发生了改变,test未改变,深拷贝成功!
码字不易,希望大家多多鼓励!