前言
说到拷贝大家会想到啥?复制粘贴?没错,就是简单的复制粘贴!
那你知道JavaScript的拷贝有多"讲究"嘛?你知道什么是深拷贝和浅拷贝嘛?
如果面试官让你手写浅拷贝和深拷贝的方法怎么办?
今天,就带大家来了解一下JavaScript中的浅拷贝和深拷贝!
收藏===学会!大家可以手动学会一下哦~🥺
正文
说到拷贝,大家一定一定很很熟悉吧!不就是ctrl + c
再ctrl + v
嘛?
我们今天来讲一点不一样的!
在JavaScript中,对象的复制是一项常见的操作。然而,由于JavaScript中的对象是引用类型,开发者必须小心处理对象的复制,以避免在程序中出现意外的副作用。为了实现对象的复制,我们通常使用浅拷贝和深拷贝两种不同的技术。它们分别指的是对对象进行复制时,是否仅仅复制了对象的引用,还是创建了一个全新的对象。
什么是浅拷贝?
浅拷贝就是指当我们在复制对象时,仅仅是复制了对象的表面结构,而没有复制对象内部的嵌套对象!换句话说,浅拷贝创建的新对象,无时不刻不在受到原对象的影响,也就是说这个新对象的某些属性仍然是原对象的引用!
我们来看个案例:
js
let obj ={
age:18
}
//可以说obj2浅拷贝obj
let obj2 = obj
obj.age = 20
console.log(obj2.age);//输出:20
在这个案例当中,我们可以知道,对于对象这种引用类型,我们通过变量间的赋值,其实是一种浅拷贝的方式,为什么这么说呢?
我们都知道,对象是引用类型,也就是说,我们的obj
和obj2
两个变量其实存的是堆当中数据的地址!我们通过赋值操作,其实就是把obj
的地址赋给obj2
,我们通过obj
去修改这个地址所在的位置中存储的属性,也就导致obj2
读取这个地址中的属性也是被修改后的值,这样的一种拷贝方式其实就是浅拷贝!
浅拷贝是一种对象复制的操作,它创建了一个新对象,并将原始对象的属性值复制到新对象中。然而,与深拷贝不同,浅拷贝只复制基本数据类型的属性值,而对于对象类型的属性,它们仍然是原始对象中相同对象的引用。换句话说,浅拷贝创建了一个新对象,但对于原始对象中的对象属性,新对象中的属性仍然指向原始对象中相应的属性。
我们再来看看浅拷贝的常见案例:
js
// //常见的浅拷贝
let a = {name:'小黑'}
let b = Object.create(a)//让b对象继承于a
a.name = '小白'
console.log(b.name)
这个案例,我们利用Object
对象自带的方法create
让对象b
继承对象a
,可以看到,我们通过修改a
对象的属性name
,再通过b
对象去读取name
属性,这个属性也被修改了,也就是说对象a
和对象b
其实指向的是同一个属性!
那我们想个办法!怎么样才能让对象实现不是浅拷贝呢? 大家会不会想到开辟一段新的地址?
让我们来试试!我们利用Object
自带的方法assign()
来将两个对象合并成一个对象,也就是开辟了一个新的引用地址,存储新的对象。
js
//浅拷贝
let a = {name:'小黑', like:{
n:'coding'
}}
//Object.assign()将两个对象合并成一个对象
let b = Object.assign({},a)
a.name = '小白'
// b.name不受影响
a.like.n = 'running'
// like也是一个对象,存的也是一个引用地址
console.log(b);//输出:{ name: '小黑', like: { n: 'running' } }
可以看到,我们通过这样设定的新对象b
,是一个新的引用地址中存储的对象!
我们通过修改原对象a
的name
属性,发现b
对象中的属性并不受影响!
但是,我们若是修改b
对象中的嵌套对象中的属性,则会影响b
对象中的嵌套的like
对象的属性!
这是因为,再b
对象当中like
对象存储的也是引用地址,在对象a
中,存储的是引用地址
所以,在b
对象当中,存储的也是引用地址!因此,a
对象和b
对象中的like
对象指向的是同一个地址,仍然是浅拷贝!
我们再看看数组的浅拷贝!
js
//浅拷贝
let arr = [1,2,3,{a:10}]
let newArr = [].concat(arr)//concat会返回一个新的数组
arr[3].a=100
console.log(newArr);//输出:[ 1, 2, 3, { a: 100 } ]
在我们这个案例当中,我们定义了一个arr
数组,利用数组方法concat
生成一个新的数组newArr
,也就是引用一段新的地址,开辟一段新的空间存储新的数组!
但是,newArr
中有引用的数据,比如{a:10}
还是存储的是引用对象的地址!所以,我们还是能通过arr
修改这个对象的属性a
来影响我们新生成的数组newArr
同理,我们通过其他方法生成的新数组也是一样的效果!
例如splice
方法
js
let arr = [1,2,3,{a:10}]
//splice影响原数组 slice不影响原数组
let newArr = arr.slice(0)
arr[3].a = 100
console.log(newArr);//输出:[ 1, 2, 3, { a: 100 } ]
解构方法:
js
let arr = [ 1,2,3,{a:10}]
let newArr = [...arr]
arr[3].a = 100
console.log(newArr);//输出:[ 1, 2, 3, { a: 100 } ]
浅拷贝的实现原理
现在,我们来介绍一下浅拷贝的实现原理!
我们直接先给大家展示代码吧!
js
//浅拷贝的实现原理
let obj = {
name:'小黑',
age:18,
like:{
type:'coding'
}
}
let arr = ['a',{n:1},1,undefined,null]
function shalldowCopy(obj){
if(typeof obj !== 'object' ||obj ==null) return //只拷贝引用类型!
let objcopy = obj instanceof Array ? []:{}
for(let key in obj)
{
if(obj.hasOwnProperty(key))
{
//不能写.key 写.会直接变为字符串,我们要读的是变量
objcopy[key] = obj[key]
}
}
return objcopy
}
let newObj = shalldowCopy(obj);
obj.like.type = 'swimming'//key为like,读到的就是引用地址!
console.log(newObj);//输出:{ name: '小黑', age: 18, like: { type: 'swimming' } }
当然,这个代码还是一个盖板的代码,还有很多种条件,大家可以自己去优化一下!
我们来为大家介绍一下原理!
-
我们定义了一个要进行浅拷贝操作的对象
obj
和数组arr
-
我们又定义了一个
shalldowCopy
方法,我们通过这个方法来实现浅拷贝的原理! -
在方法体当中,我们首先通过判断
obj
是否是引用类型!我们只需要拷贝引用类型~ -
再定义一个变量
objcopy
通过一个三元运算符来通过obj
的类型来给这个变量赋值为数组或者对象! -
我们再通过
for...in
循环遍历obj
中的元素或者属性!为什么我们这里不用
for ...of
遍历呢?这是因为for...of
:用于遍历可迭代对象(iterable objects),例如数组、字符串、Map、Set等。它遍历的是对象的值。for...in
:用于遍历对象的可枚举属性,包括继承的属性。它遍历的是对象的键。对象不具有迭代属性,所以我们选择使用
for ...in
方法 -
通过
hasOwnPropery(key)
方法判断对象中是否具有这个属性,然后把这个obj
的属性赋值给objcopy
hasOwnProperty()
是 JavaScript 中 Object 对象的一个方法。该方法用于检查一个对象是否包含指定的属性(键),并返回一个布尔值,指示属性是否为对象的直接属性而非继承的属性。 -
最后返回
objcopy
,就实现了一个浅拷贝的原理!
什么是深拷贝?
深拷贝是指创建一个新对象,同时递归地复制原始对象及其所有嵌套的对象,确保新对象和原对象的每个属性都是相互独立的,没有共享引用。它不仅复制对象本身,还会递归地复制对象内部的所有嵌套对象。
例如:
js
let a = 1
let b = a
a = 2
console.log(b);
//b是原始数据类型,存在调用栈当中,会直接取到a的值存到b中
对于原始数据的赋值就是一种深拷贝!我们修改a
的值不会影响到b
的值!
在JavaScript中,对象赋值默认是浅拷贝,这意味着当我们将一个对象赋值给另一个变量时,实际上是传递了对象的引用,而不是对象的副本。这样的赋值会导致两个变量指向相同的内存地址,修改其中一个变量会影响到另一个变量。深拷贝的作用在于创建对象的完全独立副本,避免对象之间的关联。
深拷贝的实现
JSON.stringify
和JSON.parse
利用JSON.stringify
和JSON.parse
是一种简单的深拷贝方法。这种方法的优势是简单易懂,但它有一些限制,比如不能处理包含函数、循环引用等情况的对象。
我们看看这个案例:
js
let obj = {
name:'小黑',
age:18,
like:{
type:'coding'
}
}
//深拷贝的方法
//JSON.stringify(obj)把一个对象变为字符串, JSON.parse把一个字符串变为对象
console.log(JSON.stringify(obj));//输出:{"name":"小黑","age":18,"like":{"type":"coding"}}
console.log(JSON.parse(JSON.stringify(obj)));//输出:{ name: '小黑', age: 18, like: { type: 'coding' } }
let newObj = JSON.parse(JSON.stringify(obj))
obj.like.type = 'eating'
console.log(newObj);//输出:{ name: '小黑', age: 18, like: { type: 'coding' } }
可以看到,我们通过利用JSON.stringify
和JSON.parse
就可以简单地实现一个深拷贝的方法!这是官方提供的方法!
但是这个方法存在诸多限制!这个方法拷贝不了函数体,拷贝不了undefined,拷贝不了BigInt,拷贝不了Symbol,BigInt都处理不了!
当然,我们也可以自己手写一个递归方法实现深拷贝的方法!
手写深拷贝
js
// 面试题 手动实现一个深拷贝的方法
let obj = {
name:'小黑',
age:18,
like:{
type:'coding'
}
}
var copyobj = deepCopy(obj)
obj.type = 'running'
console.log(copyobj);//输出:{ name: '小黑', age: 18, like: { type: 'coding' } }
//换一个地址
//这个如果对象存在循环引用就会爆栈
function deepCopy(obj) {
let objCopy = {}
for(let key in obj){
if(obj.hasOwnProperty(key)){
if(obj[key] instanceof Object)//obj[key] i是引用类型
{
objCopy[key] = deepCopy(obj[key])
}else
{
objCopy[key] = obj[key]
}
}
}
return objCopy
}
//Jquery库,这个库很经典
//https://www.underscorejs.cn/ 封装了好多方法
当然,这也是一个面试题哦~
我们接下来为大家来分析一下这个是如何实现的!
- 同样的,我定义了一个
obj
对象,这是一个嵌套的对象,其中嵌套了一个like
对象! - 我们定义了一个函数
deepCopy
函数,用于实现我们深拷贝的逻辑! - 首先,我们定义了一个
objCopy
为空对象 - 通过
for ...in
方法,对传过来的对象进行遍历! - 然后,我们通过一个
hasOwnProperty()
方法,获取对象当中的key
值,键名, - 判断这个
key
值所存储的值是否是引用类型 - 如果是,则进行函数自调用,在同样的逻辑下去拷贝对象体内的引用类型。
- 如果是原始类型,则直接复制就好了!
这样我们就实现了一个深拷贝的方法!主要的思想是什么呢?
就是在我们的方法当中,每次我们都开辟一个新的对象,使用一个新的地址,然后去遍历我们传过来的对象,如果是引用类型,我们就再调用我们自己的方法,用同一个逻辑,每次有引用类型,都去开辟新的引用地址,用新的内存去存储,直到对象当中没有引用类型!
注意!!我们手写的方法应对不了循环引用(这个我们再这里就不多阐述)
如果是原始类型,我们可以直接进行拷贝,因为原始类型的拷贝都是值拷贝,其实差不多就是深拷贝!、
这样我们深拷贝的逻辑就实现了!
其实还有一种方法就是大家可以导入Jquery库,这个库中封装了很多方法!大家感兴趣可以自行去了解一下哦!
总结
拷贝
通常只针对于引用类型!针对原始类型毫无意义
a = b 谈不上拷贝!
浅拷贝
新的对象受老的对象的影响
- 常见的浅拷贝方法:
- object.create(x)
- object.assign({},x)
- concat
- slice
- 数组解构
- arr.toReversed().reverse()
深拷贝
有一个偏门方法可以处理
常见的深拷贝方法: JSON.parse(JSON.stringify(obj))
缺点
- 无法拷贝undefined,function,Symbol,BigInt这几种类型的数据
- 无法处理循环引用!
引用类型的赋值是浅拷贝,原始类型是深拷贝,聊拷贝忽视原始类型 拷贝只针对引用类型
好了!我们有关JavaScript中的浅拷贝和深拷贝就学习到这里啦!
大家有任何想法和意见欢迎评论留言!点个赞鼓励支持一下吧!🌹🌹🌹🤩