1. 程式人生 > 程式設計 >JavaScript深拷貝的一些踩坑記錄

JavaScript深拷貝的一些踩坑記錄

前言

之前去一家公司面試的時候,面試官問了我一個問題,說:"如何才能深拷貝一個物件"。當時我心裡有些竊喜,這麼簡單的問題還用想嗎?於是脫口而出:"平時常用的有兩種辦法,第一種用jsON.parse(JSON.stringify(obj)),第二種可以使用for...in加遞迴完成"。面試官聽了以後點了點頭覺得挺滿意的。http://www.cppcns.com
當時我也並沒有太過在乎這個問題,直到前段時間又想起這個問題,發現上面說的兩種方法都是有Bug的。

提出問題

那麼上面所說的Bug是什麼呢?

特殊物件拷貝

首先讓我們試想有這麼一個物件,在不考慮普通型別的情況下,它有如下成員:

const obj = {
 arr: [111,222],obj: {key: '物件'},a: () => {console.log('函式')},date: new Date(),reg: /正則/ig
}

然後我們用上面兩種方式分別拷貝一次

JSON法

JSON.parse(JSON.stringify(obj))

輸出結果:

JavaScript深拷貝的一些踩坑記錄

可以從中看出,obj中的普通物件和陣列都能拷貝,然而date物件成了字串,函式直接就不見了,正則成了一個空物件。
再來看看for...in加遞迴的方法

遞迴

function isObj(obj) {
 return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}
function deepCopy(obj) {
 let tempObj = Array.isArray(obj) ? [] : {}
 for(let key in obj) {
 tempObj[key] = isObj(obj[key]) ? deepCopy(obj[key]) : obj[key]
 }
 return tempObj
}

結果:

JavaScript深拷貝的一些踩坑記錄

結論

通過上面的測試可知,這兩個方法都無法拷貝函式,date,reg型別的物件;

什麼是環?

環就是物件迴圈引用,導致自己成為一個閉環,例如下面這個物件:

var a = {}

a.a = a

JavaScript深拷貝的一些踩坑記錄

使用上面兩個方法拷貝一下會直接報錯

JavaScript深拷貝的一些踩坑記錄

解決方案

可以使用一個WeakMap結構儲存已經被拷貝的物件,每一次進行拷貝的時候就先向WeakMap查詢該物件是否已經被拷貝,如果已經被拷貝則取出該物件並返回,將deepCopy函式改造成如下

function deepCopy(obj,hash = new WeakMap()) {
 if(hash.has(obj)) return hash.get(obj)
 let cloneObj = Array.isArray(obj) ? [] : {}
 hash.set(obj,cloneObj)
 for (let key in obj) {
 cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key],hash) : obj[key];
 }
 return cloneObj
}

拷貝環結果:

JavaScript深拷貝的一些踩坑記錄

特殊物件的拷貝

這個問題的解決比較麻煩,因為需要特別對待的物件種類實在太多,於是我參考了MDN上的結構化拷貝,然後結合解決環的方案:

// 只解決date,reg型別,程式設計客棧其他的可以自己新增

function deepCopy(obj,hash = new WeakMap()) {
 let cloneObj
 let Constructor = obj.constructor
 switch(Constructor){
 case RegExp:
 cloneObj = new CfHNnQYonstructor(obj)
 break
 case Date:
 cloneObj = new Constructor(obj.getTime())
 break
 default:
 if(hash.has(obj)) return hash.get(程式設計客棧obj)
 cloneObj = new Constructor()
 hash.set(obj,cloneObj)
 }
 for (let key in obj) {
 cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key],hash) : obj[key];
 }
 return cloneObj
}

拷貝結果:

JavaScript深拷貝的一些踩坑記錄

完整版可以檢視lodash深拷貝

  • 函式的拷貝

但是MDN上的結構化拷貝依舊沒有解決函式的拷貝

JavaScript深拷貝的一些踩坑記錄

目前為止,我只想到使用eval的方法對函式進行拷貝,但是這種方法只能對箭頭函式生效,如果是fun(){}這種形式的則會出錯

拷貝函式增加函式型別

JavaScript深拷貝的一些踩坑記錄

拷貝結果

JavaScript深拷貝的一些踩坑記錄

出錯型別

JavaScript深拷貝的一些踩坑記錄

後記

javascript的深拷貝還不止上面所說的這些坑,還存在的問題有如何拷貝原型鏈上的屬性?如何拷貝不可列舉屬性?程式設計客棧 如何拷貝Error物件等等的坑,在這裡就不一一贅述了。

不過在日常過程中還是建議使用JSON方法,這個方法已經覆蓋了絕大部分的業務需求,所以不需要把簡單的事情複雜化,不過面試中如果遇到面試官鑽牛角尖對這個問題的解答絕對可以秀他一臉了。

到此這篇關於javaScript深拷貝的一些踩坑記錄的文章就介紹到這了,更多相關JavaScript深拷貝內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!