JS深拷貝的4種實(shí)現(xiàn)方法
淺拷貝與深拷貝
淺拷貝是創(chuàng)建一個(gè)新對象,這個(gè)對象有著原始對象屬性值的拷貝。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的是內(nèi)存地址 。
如果不進(jìn)行深拷貝,其中一個(gè)對象改變了對象的值,就會影響到另一個(gè)對象的值。 深拷貝是將一個(gè)對象從內(nèi)存中完整的拷貝一份出來,從堆內(nèi)存中開辟一個(gè)新的區(qū)域存放新對象,且修改新對象不會影響原對象。
1、JSON.parse(JSON.stringify(obj))序列化和反序列
先將需要拷貝的對象進(jìn)行JSON字符串化,然后再pase解析出來,賦給另一個(gè)變量,實(shí)現(xiàn)深拷貝。
let a = {a:1,b:2} let b = JSON.parse(JSON.stringify(a)) a.a = 11
1.1 JSON.parse(JSON.stringify(obj))深淺拷貝的缺陷
let a = { name: 'Jack', age: 18, hobbit: ['sing', {type: 'sports', value: 'run'}], score: { math: 'A', }, run: function() {}, walk: undefined, fly: NaN, cy: null, date: new Date() } let b = JSON.parse(JSON.stringify(a))
取不到值為 undefined 的 key;如果對象里有函數(shù),函數(shù)無法被拷貝下來;無法拷貝copyObj對象原型鏈上的屬性和方法;對象轉(zhuǎn)變?yōu)?date 字符串。
2. Object.assign(target, source1, source2)
es6新增的方法,可用于對象合并,將源對象的所有可枚舉屬性,復(fù)制到目標(biāo)對象上。
var data = { a: "123", b: 123, c: true, d: [43, 2], e: undefined, f: null, g: function() { console.log("g"); }, h: new Set([3, 2, null]), i: Symbol("fsd"), k: new Map([ ["name", "張三"], ["title", "Author"] ]) }; var newData = Object.assign({},data) console.log(newData)
可以看到這個(gè)API可以將源對象上的全部數(shù)據(jù)類型屬性值完全復(fù)制到一個(gè)新的對象上,這難道就是我們所尋找的最完美的深拷貝方式了嗎?答案是否,只能說是部分深拷貝,或者說就是淺拷貝,為什么這么說呢,接著往下看。
var test = { name: '張三' } var data = { a: 123, b: test } var newData = Object.assign({},data) console.log(newData) // { a: 123, b: { name: '張三' }} test.age = 18 console.log(newData) // { a: 123, b: { name: '張三', age: 18 }}
結(jié)果很明顯,這種方式的拷貝,如果源目標(biāo)對象中某個(gè)屬性值是對另一個(gè)對象的引用,那么這個(gè)屬性的拷貝仍然是對引用的拷貝。
3、普通遞歸函數(shù)實(shí)現(xiàn)深拷貝
function deepClone(source) { if (typeof source !== 'object' || source == null) { return source; } const target = Array.isArray(source) ? [] : {}; for (const key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { if (typeof source[key] === 'object' && source[key] !== null) { target[key] = deepClone(source[key]); } else { target[key] = source[key]; } } } return target; }
3.1、解決循環(huán)引用和symblo類型
function cloneDeep(source, hash = new WeakMap()) { if (typeof source !== 'object' || source === null) { return source; } if (hash.has(source)) { return hash.get(source); } const target = Array.isArray(source) ? [] : {}; Reflect.ownKeys(source).forEach(key => { const val = source[key]; if (typeof val === 'object' && val != null) { target[key] = cloneDeep(val, hash); } else { target[key] = val; } }) return target; }
4. 迭代遞歸方法(解決閉環(huán)問題)
function deepCopy(data, hash = new WeakMap()) { if(typeof data !== 'object' || data === null){ throw new TypeError('傳入?yún)?shù)不是對象') } // 判斷傳入的待拷貝對象的引用是否存在于hash中 if(hash.has(data)) { return hash.get(data) } let newData = {}; const dataKeys = Object.keys(data); dataKeys.forEach(value => { const currentDataValue = data[value]; // 基本數(shù)據(jù)類型的值和函數(shù)直接賦值拷貝 if (typeof currentDataValue !== "object" || currentDataValue === null) { newData[value] = currentDataValue; } else if (Array.isArray(currentDataValue)) { // 實(shí)現(xiàn)數(shù)組的深拷貝 newData[value] = [...currentDataValue]; } else if (currentDataValue instanceof Set) { // 實(shí)現(xiàn)set數(shù)據(jù)的深拷貝 newData[value] = new Set([...currentDataValue]); } else if (currentDataValue instanceof Map) { // 實(shí)現(xiàn)map數(shù)據(jù)的深拷貝 newData[value] = new Map([...currentDataValue]); } else { // 將這個(gè)待拷貝對象的引用存于hash中 hash.set(data,data) // 普通對象則遞歸賦值 newData[value] = deepCopy(currentDataValue, hash); } }); return newData; }
比之前的1.0版本多了個(gè)存儲對象的容器WeakMap,思路就是,初次調(diào)用deepCopy時(shí),參數(shù)會創(chuàng)建一個(gè)WeakMap結(jié)構(gòu)的對象,這種數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)之一是,存儲鍵值對中的健必須是對象類型。
- 首次調(diào)用時(shí),weakMap為空,不會走上面那個(gè)if(hash.has())語句,如果待拷貝對象中有屬性也為對象時(shí),則將該待拷貝對象存入weakMap中,此時(shí)的健值和健名都是對該待拷貝對象的引用
- 然后遞歸調(diào)用該函數(shù)
- 再次進(jìn)入該函數(shù),傳入了上一個(gè)待拷貝對象的對象屬性的引用和存儲了上一個(gè)待拷貝對象引用的weakMap,因?yàn)槿绻茄h(huán)引用產(chǎn)生的閉環(huán),那么這兩個(gè)引用是指向相同的對象的,因此會進(jìn)入if(hash.has())語句內(nèi),然后return,退出函數(shù),所以不會一直遞歸進(jìn)棧,以此防止棧溢出。
總結(jié)
上述的幾種方式不管優(yōu)缺點(diǎn)如何,共同點(diǎn)是只能拷貝對象的可枚舉屬性,對于不可枚舉或者原型上的屬性,卻不能拷貝,但對于基本的使用來說,已經(jīng)足夠了。
到此這篇關(guān)于JS深拷貝的4種實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)JS深拷貝內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
WdatePicker.js時(shí)間日期插件的使用方法
本篇文章主要介紹了WdatePicker.js時(shí)間日期插件的使用方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07js實(shí)現(xiàn)使用鼠標(biāo)拖拽切換圖片的方法
這篇文章主要介紹了js實(shí)現(xiàn)使用鼠標(biāo)拖拽切換圖片的方法,涉及javascript操作圖片實(shí)現(xiàn)輪播效果的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-05-05JS中BOM相關(guān)知識點(diǎn)總結(jié)(必看篇)
下面小編就為大家?guī)硪黄狫S中BOM相關(guān)知識點(diǎn)總結(jié)(必看篇)。小編覺得挺不錯的,希望對大家有所幫助。一起跟隨小編過來看看吧,祝大家游戲愉快哦2016-11-11微信小程序獲取手機(jī)系統(tǒng)信息的方法【附源碼下載】
這篇文章主要介紹了微信小程序獲取手機(jī)系統(tǒng)信息的方法,涉及微信小程序wx.getSystemInfo函數(shù)的簡單使用技巧,并附帶源碼供讀者下載參考,需要的朋友可以參考下2017-12-12淺析JS中對函數(shù)function的理解(基礎(chǔ)篇)
我們知道,在js中,函數(shù)實(shí)際上是一個(gè)對象,每個(gè)函數(shù)都是Function類型的實(shí)例,并且都與其他引用類型一樣具有屬性和方法。下面給大家談下對JS中函數(shù)function的理解,一起看看吧2016-10-10JavaScript實(shí)現(xiàn)Tab欄切換特效
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)Tab欄切換特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06