前言
在软件开发过程中,拷贝是一个非常基础的操作。拷贝看似非常简单但其背后的逻辑和原理还是值得我们深度学习,希望看完这篇文章后能让你对拷贝有更深的理解。
拷贝
拷贝通常只针对引用数据类型,因为基本数据类型的拷贝基本就是拷贝值,或者直接覆盖了。其中拷贝又分为浅拷贝和深拷贝,接下来我们好好讲讲
浅拷贝
基于原对象,拷贝得到一个新对象,原对象中内容修改会影响新对象。这是因为不像原始数据类型,引用数据类型在栈中存储的不是其属性和属性值,而是其在堆中的地址值,下面就是他们的区别。
let a=1
let b=a
a=2
console.log(b);//b=1
let a={
age:'20',
}
let b=a
console.log(b)//b{} bage='20'
在图二中在栈中存储的是地址值,再创建一个对象将a赋给b实质上是将a的地址值赋给b,导致a和b都指向堆中的#001,因此b.age也会是20。接下来a中数据变化b的数据也会变化,这就是浅拷贝的一种。
浅拷贝有这几种方法。
1. object.create()
let a={
name:'毛毛',
like:{
n:'running'
}
}
let b=object.create(a)
a.name='付哥'
console.log(b)//{}
console.log(b.name)//付哥
create方法创建了一个对象,并让对象隐式继承了该对象的方法,由于对对象a修改后b也出现了更改,所以该方法是浅拷贝
2.object.assign({},a)
let a={
name:'毛毛'
}
let b={
age:18
}
let c=Object.assign(a,b)//对象的拼接 把b的属性拼接到a上,a和c是同一个对象,c.name='毛毛'
console.log(c)//{name:'毛毛',age:18}
console.log(a)//{name:'毛毛',age:18}
该方法将后者的属性传给前者并返回对象。
let a={
name:'毛毛'
like:{
n:'running'
}
}
let c=Object.assign({},a)
a.like.n='swimming'
console.log(c)//{name:'毛毛',like:{n:'swimming'}}
a的属性改变,c也随之改变,所以是浅拷贝
3.[].concat(arr)
众所周知数组里也可以放对象,现在我们聊聊数组里的浅拷贝
let arr=[1,2,3,{a:10}]
let newarr=[1,2].concat(arr)//数组的拷贝 concat将arr中的元素合并到数组中,返回一个新的数组
arr.[3].a=100
console.log(arr)//[1,2,3,{a:100}]
console.log(newarr);//[1,2,1,2,3,{a:100}]
concat将arr中的元素合并到数组中,返回一个新的数组,这依然是浅拷贝
4.数组解构
let arr=[1,2,3,{a:10}]
let newarr=[...arr]//将1 2 3 {a:10}解构出来 但是是浅拷贝
arr[2]=4;
arr[3].a=100;
console.log(newarr);//[1, 2, 3, {a: 100}]
...arr可以将arr中的所有元素解构出来,放到newarr中,但依然是浅拷贝
5.arr.slice(0)
slice()方法可以截取数组的一段并返回新的数组,遵循左闭右开
例如:
let arr=[1,2,3,{a:10}]
let s=arr.slice(1,3);//[2,3]左闭右开,原数组不变,当arr.slice(0)时,s=arr,返回整个数组
console.log(s,arr);//[2, 3] [1, 2, 3, { a: 10 }]
当slice(0)时,则返回整个数组
let arr=[1,2,3,{a:10}]
let s=arr.slice(0);
arr[2]=4;
arr[3].a=100;
console.log(s);// [1, 2, 3, { a: 100 }]
6.arr.toReversed().reverse()
在数组中存在一个toReversed(),可以将数组里的元素顺序反转,并返回一个数组,在此基础上我们再次将他进行反转,我们就能拷贝出一份新的数组了
let arr=[1,2,3,{a:10}]
let s=arr.toReversed().reverse();
arr[2]=4;
arr[3].a=100;
console.log(s);// [1, 2, 3, { a: 100 }]
自己写一个浅拷贝
--实现原理:
1.借助for in 遍历源对象,将源对象的属性值增加到新对象中
2.因为for in 会遍历到对象隐式具有的属性,通常要使用 obj.hasOwnProperty(key)要判断要拷贝的属性是不是对象显式具有的
let obj={
name:'罗总',
like:{
a:'food'
}
}
function shallow(obj){
let newObj={}
for(let key in obj){//遍历对象中的属性
//去除对象身上的隐式原型属性
if(obj.hasOwnProperty(key)){
newObj[key]=obj[key]
}
}
return newObj;
}
console.log(shallow(obj));//{name: "罗总", like: {…}}
深拷贝
深拷贝:基于原对象,拷贝得到一个新对象,原对象中内容修改不会影响新对象
深拷贝有这些方法:
1.JSON.parse(JSON.stringify(obj))
JSON.stringify(obj)可将对象转换成字符串
JSON.parse(String)将字符串转为对象。这种拷贝方式有一些小小的问题,它不能识别BigInt类型,不能拷贝 undefined,symbol,function类型的值。除此之外可以视为深拷贝
let obj={
name:'wlj',
age:18,
like:{
n:'coding'
},
a:true,
b:undefined,
c:null,
d:Symbol(1),
// e:123n,
f:function(){},
}
let obj2=JSON.parse(JSON.stringify(obj))//
console.log(obj2);//{name: "wlj", age: 18, like: {…}, a: true, c: null,
2.structuredClone()
这个方法相比其他方法比较新,新就意味着一些更新频率慢的浏览器可能读不懂这个方法,所以使用这个方法的时候要注意浏览器版本
const user={
name:{
firstname:'liu',
lastname:'jia'
},
age:18
}
const newuser=structuredClone(user)
user.name.firstname='niuniu'
use.age=20
console.log(newuser);//{name:{firstname:'liu',lastname:'jia'},age:18}
这个方法非常简单,随着浏览器的更新,这个方法用的也越来越多,方便快捷
自己写一个深拷贝
--实现原理:
1.借助for in 遍历源对象,将源对象的属性值增加到新对象中
2.因为for in 会遍历到对象隐式具有的属性,通常要使用 obj.hasOwnProperty(key)要判断要拷贝的属性是不是对象显式具有的
3.如果遍历到的属性值是原始值类型,直接往新对象赋值,如果是引用 型,递归创建新的子对象。
const user={
name:{
firstname:'liu',
lastname:'jia'
},
age:18
}
function deep(obj){
let newObj={};
for(let key in obj){
if(obj.hasOwnProperty(key)){//只拷贝显式具有的属性
if(obj[key] instanceof Object){//obj[key]是不是对象 //typeof(obj[key]==='object'&& obj[key]!==null)
newObj[key]=deep(obj[key]);//递归
}else{
newObj[key]=obj[key];//直接赋值
}
}
}
return newObj;
}
总结
今天我们学习了什么是浅拷贝和深拷贝,他们的根本区别就是原对象发生变化的时候拷贝出来的新对象会不会一起发生变化,从数据结构上来看其本质就是存储的方式不同。
希望这篇文章对你有用。 好好学习拷贝,我们下次再见。