JS深拷貝的4種實(shí)現(xiàn)方法
淺拷貝與深拷貝
淺拷貝是創(chuàng)建一個新對象,這個對象有著原始對象屬性值的拷貝。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的是內(nèi)存地址 。
如果不進(jìn)行深拷貝,其中一個對象改變了對象的值,就會影響到另一個對象的值。 深拷貝是將一個對象從內(nèi)存中完整的拷貝一份出來,從堆內(nèi)存中開辟一個新的區(qū)域存放新對象,且修改新對象不會影響原對象。
1、JSON.parse(JSON.stringify(obj))序列化和反序列
先將需要拷貝的對象進(jìn)行JSON字符串化,然后再pase解析出來,賦給另一個變量,實(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) 
可以看到這個API可以將源對象上的全部數(shù)據(jù)類型屬性值完全復(fù)制到一個新的對象上,這難道就是我們所尋找的最完美的深拷貝方式了嗎?答案是否,只能說是部分深拷貝,或者說就是淺拷貝,為什么這么說呢,接著往下看。
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)對象中某個屬性值是對另一個對象的引用,那么這個屬性的拷貝仍然是對引用的拷貝。
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 {
// 將這個待拷貝對象的引用存于hash中
hash.set(data,data)
// 普通對象則遞歸賦值
newData[value] = deepCopy(currentDataValue, hash);
}
});
return newData;
}比之前的1.0版本多了個存儲對象的容器WeakMap,思路就是,初次調(diào)用deepCopy時,參數(shù)會創(chuàng)建一個WeakMap結(jié)構(gòu)的對象,這種數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)之一是,存儲鍵值對中的健必須是對象類型。
- 首次調(diào)用時,weakMap為空,不會走上面那個if(hash.has())語句,如果待拷貝對象中有屬性也為對象時,則將該待拷貝對象存入weakMap中,此時的健值和健名都是對該待拷貝對象的引用
- 然后遞歸調(diào)用該函數(shù)
- 再次進(jìn)入該函數(shù),傳入了上一個待拷貝對象的對象屬性的引用和存儲了上一個待拷貝對象引用的weakMap,因?yàn)槿绻茄h(huán)引用產(chǎn)生的閉環(huán),那么這兩個引用是指向相同的對象的,因此會進(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)文章
js實(shí)現(xiàn)使用鼠標(biāo)拖拽切換圖片的方法
這篇文章主要介紹了js實(shí)現(xiàn)使用鼠標(biāo)拖拽切換圖片的方法,涉及javascript操作圖片實(shí)現(xiàn)輪播效果的相關(guān)技巧,非常具有實(shí)用價值,需要的朋友可以參考下2015-05-05
JS中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í)際上是一個對象,每個函數(shù)都是Function類型的實(shí)例,并且都與其他引用類型一樣具有屬性和方法。下面給大家談下對JS中函數(shù)function的理解,一起看看吧2016-10-10
JavaScript實(shí)現(xiàn)Tab欄切換特效
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)Tab欄切換特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-06-06

