摘要:在以上討論和研究結束后,同學向我推薦了一個庫,測試了一下該庫存在方法,實現深拷貝更為完整和精致,前文問題均沒有在該方法內被發現,在這里提一波。
如果本文對您有任何幫助或者您有任何想要提出的意見或問題,請在本文下方回復,誠摯歡迎各位參與討論,望各位不吝指教。
原載自己的小博客 JavaScript對象拷貝遇到的坑和解決方法 | 手柄君的小閣,所以無恥地算原創吧
近期參與某集訓,JavaScript,遇到一對象拷貝問題,得到需求:
給一個對象,請編寫一個函數,使其可以拷貝一個對象,返回這個拷貝得到的新對象:
舉例如下:
function clone(obj){ //DO SOMETHING return newObject; //返回拷貝得到的新對象 }
首先想到解法如下:
> ES6解構賦值(淺拷貝):function clone(obj){ return {...obj}; }
得到新對象為原始對象淺拷貝,即屬性Key一致,值如果是數或者字符串則值傳遞,否則為地址傳遞,即Value引用和源對象一致,可根據下方運行測試:
var a = {a:1, b:2, c:3, d:[0, 1, 2]} var b = clone(a); console.log(b.d[1]); //1 b.d[1] = 2; console.log(b.d[1]); //2 console.log(a.d[1]); //2
對復制后的對象中包含的數組或者對象進行編輯,影響了源對象,這顯然不是我們想要的結果,但是在對象內不包含數組或對象時,該方法不失為一個快速創建對象拷貝的實用方法。
在ES6中,Object提供了一個 assign() 方法,也可以實現相同效果
function clone(obj){ return Object.assign({},obj); }
運行效果和前一種方式基本一致,根據MDN描述,Object.assign() 方法用于將所有可枚舉屬性的值從一個或多個源對象復制到目標對象,允許至少兩個參數,第一個參數為拷貝的目標對象,在方法執行結束后會被返回,其余參數將作為拷貝來源。
前面兩種方法均為淺拷貝,那么對于對象內包含對象或數組的對象,我們該怎樣拷貝呢?
我們的老師提供了一種方法如下,缺陷稍后再談
function clone(obj) { var newobj = obj.constructor === Array ? [] : {}; if (typeof obj !== "object") { return obj; } else { for (var i in obj) { newobj[i] = typeof obj[i] === "object" ? clone(obj[i]) : obj[i]; } } return newobj; }
同樣使用前文中的測試數據:
var a = {a:1, b:2, c:3, d:[0, 1, 2]} var b = clone(a); console.log(b.d[1]); //1 b.d[1] = 2; console.log(b.d[1]); //2 console.log(a.d[1]); //1
可見該方法可以正確地對對象進行深拷貝,并根據參數類型為數組或對象進行進行判斷并分別處理,但是該方法有一定缺陷:
1,在存在Symbol類型屬性key時,無法正確拷貝,可以嘗試以下測試數據:
var sym = Symbol(); var a = {a:1, b:2, c:3, d:[0, 1, 2], [sym]:"symValue"} var b = clone(a); b.d[1] = 2; console.log(b.d[1]); //2 console.log(a.d[1]); //1 console.log(a[sym]); //"symValue" console.log(b[sym]); //undefined
可以發現拷貝得到的對象b,不存在Symbol類型對象為屬性名的屬性。
那么可以發現,問題主要出在For...in遍歷屬性無法獲得Symbol類型Key導致,那么有什么方法可以遍歷到這些呢?
在ES6中Reflect包含的靜態方法ownKeys() 可以獲取到這些key,根據MDN描述,這個方法獲取到的返回值等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。
那么使用ES6解構賦值和Reflect.ownKeys() 組合使用,改寫上文函數,得到:
> ES6解構賦值 & Reflect.ownKeys() 遍歷并遞歸(深拷貝):function clone(obj) { var newobj = obj.constructor === Array ? [...obj] : {...obj}; if (typeof obj !== "object") { return obj; } else { Reflect.ownKeys(newobj).forEach(i => { newobj[i] = typeof obj[i] === "object" ? clone(obj[i]) : obj[i]; }); } return newobj; }
運行相同的測試語句:
var sym = Symbol(); var a = {a:1, b:2, c:3, d:[0, 1, 2], [sym]:"symValue"} var b = clone(a); b.d[1] = 2; console.log(b.d[1]); //2 console.log(a.d[1]); //1 console.log(a[sym]); //"symValue" console.log(b[sym]); //"symValue" b[sym] = "newValue"; console.log(a[sym]); //"symValue" console.log(b[sym]); //"newValue"
可以發現Symbol類型的key也被正確拷貝并賦值了,但是該方法依然有一定問題,如下:
2,在對象內部存在環時,堆棧溢出,嘗試運行以下測試語句:
var a = { info: "a", arr: [0, 1, 2] }; var b = { data: a, info: "b", arr: [3, 4, 5] }; a.data = b; var c = clone(a); //Error: Maximum call stack size exceeded. 報錯:堆棧溢出
解決這個的方法稍后再講,但目前來看已有的兩種深拷貝方法足夠平時使用,接下來正好提一下,ES5.1中包含的JSON對象,使用該對象亦可對對象進行深拷貝,會遇到的問題和第一種深拷貝方式一樣,無法記錄Symbol為屬性名的屬性,另外只能包含能用JSON字符串表示的數據類型,實現代碼如下:
> JSON對象轉義(深拷貝):function clone(obj) { return JSON.parse(JSON.stringify(obj); }
JSON.stringify() 首先將對象序列化為字符串,再由JSON.parse() 反序列化為對象,形成新的對象。
回到前面提到的問題2,如果對象內包含環,怎么辦,我的實現思路為使用兩個對象作為類似HashMap,記錄源對象的結構,并在每層遍歷前檢查對象是否已經被拷貝過,如果是則重新指向到拷貝好的對象,防止無限遞歸。實現代碼如下(配有注釋):
:
/** * 深拷貝(包括Symbol) * @param {Object} obj */ function clone(obj) { const map = {}; //空對象,記錄源對象 const mapCopy = {}; //空對象,記錄拷貝對象 /** * 在theThis對象中,查找e對象的key,如果找不到,返回false * @param {Object} e 要查找的對象 * @param {Object} theThis 在該對象內查找 * @returns {symbol | boolean} */ function indexOfFun(e, theThis) { let re = false; for (const key of Reflect.ownKeys(theThis)) { if (e === theThis[key]) { re = key; break; } } return re; } /** * 在Map對象中,查找e對象的key * @param {Object} e */ const indexOfMap = e => indexOfFun(e, map); /** * 在Map中記錄obj對象內所有對象的地址 * @param {Object} obj 要被記錄的對象 */ function bindMap(obj) { map[Symbol()] = obj; Reflect.ownKeys(obj).forEach(key => { //當屬性類型為Object且還沒被記錄過 if (typeof obj[key] === "object" && !indexOfMap(obj[key])) { bindMap(obj[key]); //記錄這個對象 } }); } bindMap(obj); /** * 拷貝對象 * @param {Object} obj 要被拷貝的對象 */ function copyObj(obj) { let re;//用作返回 if (Array.isArray(obj)) { re = [...obj]; //當obj為數組 } else { re = { ...obj }; //當obj為對象 } mapCopy[indexOfMap(obj)] = re; //記錄新對象的地址 Reflect.ownKeys(re).forEach(key => { //遍歷新對象屬性 if (typeof re[key] === "object") { //當屬性類型為Object if (mapCopy[indexOfMap(re[key])]) { //當屬性已經被拷貝過 re[key] = mapCopy[indexOfMap(re[key])]; //修改屬性指向到先前拷貝好的對象 } else {//當屬性還沒有被拷貝 re[key] = copyObj(re[key]); //拷貝這個對象,并將屬性指向新對象 } } }); return re; //返回拷貝的新對象 } return copyObj(obj); //執行拷貝并返回 }
運行前面的測試語句:
var a = { info: "a", arr: [0, 1, 2] }; var b = { data: a, info: "b", arr: [3, 4, 5] }; a.data = b; var c = clone(a); c.info = "c"; c.data.info = "d"; console.log(a.info); //"a" console.log(a.data.info); //"b" console.log(c.info); //"c" console.log(c.data.info); //"d"
得到該函數可以正確地拷貝帶環對象。
在以上討論和研究結束后,同學向我推薦了一個庫 lodash,測試了一下該庫存在 _.cloneDeep() 方法,實現深拷貝更為完整和精致,前文問題均沒有在該方法內被發現,在這里提一波。
如果本文對您有任何幫助或者您有任何想要提出的意見或問題,請在本文下方回復,誠摯歡迎各位參與討論,望各位不吝指教。
本文原載于https://www.bysb.net/3113.html
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/107470.html
摘要:引用數據類型是存放在堆內存中的,變量實際上是一個存放在棧內存的指針,這個指針指向堆內存中的地址。棧和堆的區別其實淺拷貝和深拷貝的主要區別就是數據在內存中的存儲類型不同。這里,對存在子對象的對象進行拷貝的時候,就是深拷貝了。 數據類型 在開始拷貝之前,我們從JavaScript的數據類型和內存存放地址講起。數據類型分為基本數據類型 和引用數據類型 基本數據類型主要包括undefin...
摘要:相信人很多學習的過程中都踩了深拷貝和淺拷貝的坑,深拷貝和淺拷貝的區別我就不再贅述了,今天我來寫一下我自己實現深拷貝的各種方法。中的深拷貝也是用類似方法實現。 相信人很多學習js的過程中都踩了深拷貝和淺拷貝的坑,深拷貝和淺拷貝的區別我就不再贅述了,今天我來寫一下我自己實現深拷貝的各種方法。 比較簡單的拷貝方式可以借用瀏覽器的Json對象去實現,先把對象轉化為json字符串,在解析回對...
摘要:兩者享有相同的引用。深拷貝這個問題通常可以通過來解決。深淺拷貝也可以使用的方法,注意使用合并返回值 前言 最近寫代碼經常用到深淺拷貝,從一開始的悶頭使用漸漸想要深究其理,這篇文章記錄一下我的認為,有所不足,恭請指正 我們可以先看看一個常遇到的一個小問題 let a = { age:1 } let b = a a.age = 2 console.log(b.age) //2 ...
摘要:深拷貝與淺拷貝的出現,就與這兩個數據類型有關。這時,就需要用淺拷貝來實現了。數據一但過多,就會有遞歸爆棧的風險。這個方法是在解決遞歸爆棧問題的基礎上,加以改進解決循環引用的問題。但如果你并不想保持引用,那就改用用于解決遞歸爆棧即可。 前言 這是前端面試題系列的第 9 篇,你可能錯過了前面的篇章,可以在這里找到: 數組去重(10 種濃縮版) JavaScript 中的事件機制(從原生到...
摘要:所以,深拷貝是對對象以及對象的所有子對象進行拷貝實現方式就是遞歸調用淺拷貝對于深拷貝的對象,改變源對象不會對得到的對象有影響。 為什么會有淺拷貝與深拷貝什么是淺拷貝與深拷貝如何實現淺拷貝與深拷貝好了,問題出來了,那么下面就讓我們帶著這幾個問題去探究一下吧! 如果文章中有出現紕漏、錯誤之處,還請看到的小伙伴多多指教,先行謝過 以下↓ 數據類型在開始了解 淺拷貝 與 深拷貝 之前,讓我們先...
閱讀 871·2023-04-26 00:11
閱讀 2668·2021-11-04 16:13
閱讀 2118·2021-09-09 09:33
閱讀 1485·2021-08-20 09:35
閱讀 3842·2021-08-09 13:42
閱讀 3617·2019-08-30 15:55
閱讀 1079·2019-08-30 15:55
閱讀 2232·2019-08-30 13:55