这个面试题属于比较常见的面试题,但是我们在回答过程中但是还是有很多细节我们没有回答出来,那么接我们一起来探讨一下这个问题
为什么会出现深拷贝浅拷贝?
由于 javascript 存储的方式是堆栈存储,在 javascript 中分为两类数据类型,一类是简单数据类型,一类是复杂数据类型
简单数据类型:
- String
- Number
- Boolean
- undefined
- null
- Symbol
简单数据类型的存储方式
// 首先简单数据类型存储来栈中
let a = 1;
let b = a;
b = 2
// 在存储的时候发生了什么事情解析:
- 申明变量 a 并且赋值 1 ,这个时候由于 a 存储的是简单数据类型,所以在栈中开辟空间进行存储
- a 赋值给 b 的过程中,其实是将 a 的值赋值给 b ,a 属于简单数据类,那么,就在栈中新开辟空间进行存储
- 这个时候给 b 重新赋值的时候,a 并不会受到影响,还是原来的值
复杂数据类型:
- Object
- Array
- Function
- Date
- RegExp
- ...
复杂数据类型存储方式
let person1 = {
name:"张三"
}
let person2 = person1
person2.name = "李四"
console.log(person1.name) // 李四解析:
- 申明 person1 对象,但是 person1 的内容存在堆中,栈中存着指向这个堆的地址
- 这个时候在对 person2 进行赋值的时候,赋值的不是内容,而是栈中的地址,这个时候 person1 和 person2 都指向一个内容
- 在对 person2 的值进行更改的是,其实是通过地址找到了内容进行更改地址的内容
- 那么 内容都改变了,这个时候打印 person1 的时候,就会发现值也改变了
- 这种现象就叫做浅拷贝,浅拷贝的危害是会污染到源数据
问题:出现浅拷贝会污染到数据源,在做项目的过程中,经常会出现莫名奇妙的错误,很多情况下就是因为浅拷贝的问题,所以浅拷贝出现,我们需要进行深拷贝,深拷贝说白了,就是在堆中重新开辟空间去存储内容,那么现在有哪些深拷贝的方式呢?
深拷贝的方式有哪些?
1、JSON.parse(JSON.stringify())
let person1 = {
name:"张三"
}
let person2 = JSON.parse(JSON.stringify(person1))
person2.name = "李四"
console.log(person1.name) // 张三看似好像可以实现深拷贝,源数据没有更改,但是 JSON.parse(JSON.stringify())有它自身的问题:
let obj = {
name:'张三',
age:undefined,
fn:function(){
console.log(1)
},
data:new Date(),
reg:/^\d$/,
person:{
name:'王五'
}
}
let obj1 = JSON.parse(JSON.stringify(obj))
obj1.person.name = "李四"
console.log(obj1,obj)
解析:我们发现确实是实现了深拷贝,源数据的 "王五" 并没有更改,但是也暴露了问题
问题:
- 拷贝之后的 data 源数据是对象,但是拷贝之后变成了字符串
- 函数无法拷贝
- 正则无法拷贝
- undefined 无法拷贝
2、Object.assign()
let person1 = {
name:"张三"
}
let person2 = {}
Object.assign(person2,person1)
person2.name = "李四"
console.log(person1.name) // 张三
看似好像可以实现深拷贝,源数据没有更改,但是 Object.assign()也有它的问题:
let obj = {
name:'张三',
age:undefined,
fn:function(){
console.log(1)
},
data:new Date(),
reg:/^\d$/,
perosn:{
name:'王五',
obj:{
age:15
}
}
}
let obj1 = {}
Object.assign(obj1,obj)
obj1.perosn.name = "李四"
console.log(obj1,obj)解析:Object.assign能实现拷贝,但是问题和优势很明显
问题:
- 第一层可以实现深拷贝,但是二层一下的对象无法实现深拷贝,我们看到,在更改person.name 的值的时候,发现源数据也发生的更改
优势:
- 可以实现 undefined 的拷贝
- 可以实现对函数的拷贝
- 可以实现对 Date 对象的拷贝
- 可以实现正则的拷贝
跟Object.assign有着一样的问题的还有扩展运算符
3、扩展运算符
let obj = {
name:'张三',
age:undefined,
fn:function(){
console.log(1)
},
data:new Date(),
reg:/^\d$/,
perosn:{
name:'王五',
obj:{
age:15
}
}
}
let obj1 = {...obj}
obj1.perosn.name = '李四'
console.log(obj1,obj)
解析:它的结果跟 Object.assign 一样
4、MessageChannle 管道
let obj = {
name:'张三',
age:undefined,
// fn:function(){
// console.log(1)
// },
data:new Date(),
reg:/^\d$/,
perosn:{
name:'王五',
obj:{
age:15
}
}
}
// 两个对象 prot1 port2 new MessageChannle()
// 处理异步的问题 promise : 异步中的微任务
// resolve:成功的回调 reject:失败的回调
// port1 ======== port2
function deepCopy(obj){
return new Promise(resolve=>{
let {port1,port2} = new MessageChannel()
port2.onmessage = ev => resolve(ev.data)
port1.postMessage(obj)
})
}
deepCopy(obj).then(res=>{
let obj1 = res
obj1.name = "李四"
obj1.perosn.name = "小五"
console.log(obj1,obj)
}).catch(err=>{
console.log(err)
})
解析:MessageChannle 可以实现深拷贝,并且可以实现其他数据的拷贝
优势:
- 可以实现 undefined 的拷贝
- 可以实现对函数的拷贝
- 可以实现对 Date 对象的拷贝
- 可以实现正则的拷贝
问题:
- 对函数不能实现拷贝,拷贝函数就会报错
5、自己简单实现递归拷贝
function deepCopy(obj){
let newObj = null
if(typeof obj ==='object'&&obj !== null){
newObj = obj instanceof Array?[]:{}
for(var i in obj){
newObj[i] = deepCopy(obj[i])
}
}else{
newObj = obj // 最终会走这一步
}
return newObj
}解释:当然这个拷贝是最简单的,也存在一些问题,但是实现递归最简单的方式就是这个,很多情况下我们为了保证不会出问题,都会用第三方工具库 lodash ,这样写出来才更加保险一点
最后:本人喜欢研究面试题,希望有更多志同道合的朋友一起来交流研究,可以帮助修改简历