我们今天来简单了解一下JavaScript中的深拷贝和浅拷贝。在提到深拷贝和浅拷贝时不得不提的就是值类型和引用类型。
我们就先来了解一下值类型和引用类型吧!
一、值类型和引用类型
「1.值类型」
JavaScript的简单数据类型(number,string,boolean ,null,nudefined)又称为基本数据类型。
包装类型:又称为原始值包装类型
number---Number
var k = 10 //k是值类型(基本类型)number
console.log(k)//10
var a = new Number(10)//k是number类型的对象
console.log(a)//number:10
string------String
boolean-----Boolean
var t = true //t是值类型(基本类型)boolean
console.log(t)//true
var t = new Boolean(true)//t是Boolean类型的对象
console.log(t)//boolean:true
「2.引用类型」
复杂的数据类型(对象)
特点:变量中保存的是对象的一个引用地址。
var obj = //obj存放的是下面对象在内存中的地址
{
name:'aa',
sex:'男',
tel:'123445',
age:13
}
var fp = obj//将obj中的地址赋给fp
1.一个对象只能被一个变量引用,若这个变量又被重新赋值,则该对象变成没有任何引用的对象,此时JS的垃圾回收机制会自动释放该对象所占的内存空间
2.在函数的参数传递过程中
- 实参向形参传递的是值(值传递):形参变了不会影响实参
- 实参向形参传递的是引用(引用传递):本质传递的是对象的地址,形参变了实参也会变
3.如果在函数的参数中修改对象的属性或方法,则在函数外面通过引用这个对象的变量访问时的结果也是修改后的
function change(obj){
obj.name = '小红'//在函数内修改了对象的属性
}
var stu = {name:'小明',age:18}
change(stu)
console.log(stu.name)//输出结果:小红
「3.值类型和引用类型的区别」
1.值类型和引用类型在内存存储的区别
以上函数testf在调用时,
- 定义局部变量 age,由于age是局部变量,所以在栈中申请内存空间,起名为age,又由于给age赋的值24是基本类型,所以,值直接存储在栈中。
- 定义局部变量arr,由于arr是局部变量,所以在栈中申请空间,但是arr的内存中存储的是什么?由于给arr赋的值不是基本类型,而是引用类型(new出来的),所以,先在堆中申请空间存放数据 12,23,34,。再把堆区的地址赋给arr。
2.值类型和引用类型在赋值时的区别
- 值类型
- 引用类型
了解了值类型和引用类,接下来我们就一起来讨论一下深拷贝和浅拷贝。
二、深拷贝和浅拷贝
因为值类型赋值的时,赋值的时数据,所以不存在深拷贝和浅拷贝的问题。
「1.浅拷贝」
var ninja = {
name : '旗木卡卡西',
address : '火影村',
swage : '2000',
friends : new Array('宇智波带土', '野原琳', '迈特凯') //这是引用类型
}
var ninja2 = {};
for(let key in ninja){
ninja2[key] = ninja[key];
}
ninja2.friends[0] ="火影";
console.log(ninja2);
console.log(ninja);
在控制台中打印的结果(ninja和ninja2的friends[0]都变成了“火影”):
内存:
1.原始的对象ninja:
var ninja = {
name : '旗木卡卡西',
address : '火影村',
swage : '2000',
friends : new Array('宇智波带土', '野原琳', '迈特凯') //这是引用类型
}
2.浅拷贝的代码
var ninja2 = {};
for(let key in ninja){
ninja2[key] = ninja[key];
}
「2.深拷贝(初级)」
var ninja = {
name : '旗木卡卡西',
address : '火影村',
swage : '2000',
friends : new Array('宇智波带土', '野原琳', '迈特凯') //这是引用类型
}
var ninja2 = {};
for(let key in ninja){
if(typeof ninja[key]=='object'){
ninja2[key]=[];//因为,我上面写的是数组,所以,暂时赋值一个空数组.
for(let i in ninja[key]){
ninja2[key][i] = ninja[key][i]
}
}else{
ninja2[key] = ninja[key];
}
}
ninja2.friends[0] ="火影";
console.log(ninja2);
console.log(ninja);
在控制台中打印的结果(只有ninja2的friends[0]变成了“火影”)
内存
1.原始的对象ninja:
var ninja = {
name : '旗木卡卡西',
address : '火影村',
swage : '2000',
friends : new Array('宇智波带土', '野原琳', '迈特凯') //这是引用类型
}
2.深拷贝代码
var ninja2 = {};
for(let key in ninja){
if(typeof ninja[key]=='object'){
ninja2[key]=[];//因为,我上面写的是数组,所以,暂时赋值一个空数组.
for(let i in ninja[key]){
ninja2[key][i] = ninja[key][i]
}
}else{
ninja2[key] = ninja[key];
}
}
「3.深拷贝(终级)」
1.如果对象的属性都是对象(引用类型),属性的属性也是引用类型,即嵌套多层,我们可以使用递归的方式来进行深拷贝。
var p = {
id:'007',
name:'白敬亭',
wife:{
id:'008',
name:'白敬亭的妻子',
address:{
city:'北京',
area:'怀柔区'
}
}
}
//写函数
function copyObj(obj){
let newObj={};
for(let key in obj){
if(typeof obj[key] =='object'){//如:key是wife,引用类型,那就递归
newObj[key] = copyObj(obj[key])
}else{//基本类型,直接赋值
newObj[key] = obj[key];
}
}
return newObj;
}
let pNew = copyObj(p);
pNew.wife.name='Miraitowa';
pNew.wife.address.city = '西安';
console.log(pNew);
console.log(p);
2.如果属性是数组等非键值对的对象,那我们可以给数组增加一个自我复制的函数。
function copyself(arr){
let arrNew = new Array();
for(let i in arr){
arrNew[i] = arr[i]
}
return arrNew;
}
var ninja = {
name : '旗木卡卡西',
address : '火影村',
swage : '2000',
friends : new Array('宇智波带土', '野原琳', '迈特凯') //这是引用类型
}
function copyObj(obj){
let newObj={};
for(let key in obj){
if(typeof obj[key] =='object'){
newObj[key] = copyself(obj[key]);
}else{//基本类型,直接赋值
newObj[key] = obj[key];
}
}
return newObj;
}
var ninjaNew = copyObj(ninja);
ninjaNew.friends[0] = "火影";
console.log(ninjaNew);
console.log(ninja);
「4.总结」
简单来说,浅拷贝就是只对指针进行拷贝,拷贝后两个指针指向同一个内存空间,用任意一个保存指针的变量对对象进行修改,两个指针所指向的对象都会被修改。
深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。用其中一个保存指针的变量对对象进行修改,这个指针所指向的对象都会被修改,另一个指针所指的对象不会被修改。
参考文献:https://blog.csdn.net/jiang7701037/article/details/98738487