javascript的深拷貝和淺拷貝
阿新 • • 發佈:2020-06-29
這個面試題屬於比較常見的面試題,但是我們在回答過程中但是還是有很多細節我們沒有回答出來,那麼接我們一起來探討一下這個問題
為什麼會出現深拷貝淺拷貝?
由於 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:'張三',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:'張三',fn:function(){
console.log(1)
},perosn:{
name:'王五',obj:{
age:15
}
}
}
let obj1 = {...obj}
obj1.perosn.name = '李四'
console.log(obj1,obj)
複製程式碼
解析:它的結果跟 Object.assign 一樣
4、MessageChannle 管道
let obj = {
name:'張三',// fn:function(){
// console.log(1)
// },
data:new Date(),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 ,這樣寫出來才更加保險一點
最後:本人喜歡研究面試題,希望有更多志同道合的朋友一起來交流研究,可以幫助修改簡歷