深拷貝械巡、淺拷貝是前端面試的高頻題目乳怎,要想知道它們的區(qū)別我們需要先搞懂它們的定義。
淺拷貝:
創(chuàng)建一個(gè)新對(duì)象察滑,這個(gè)對(duì)象有著原始對(duì)象屬性值的一份精確拷貝打厘。如果屬性是基本類型,拷貝的就是基本類型的值贺辰,如果屬性是引用類型户盯,拷貝的就是內(nèi)存地址 ,所以如果其中一個(gè)對(duì)象改變了這個(gè)地址饲化,就會(huì)影響到另一個(gè)對(duì)象莽鸭。
深拷貝:
將一個(gè)對(duì)象從內(nèi)存中完整的拷貝一份出來(lái),從堆內(nèi)存中開辟一個(gè)新的區(qū)域存放新對(duì)象,且修改新對(duì)象不會(huì)影響原對(duì)象
淺拷貝很容易理解,可以深入研究一下深拷貝吃靠。
深拷貝
乞丐版
在不使用第三方庫(kù)的情況下硫眨,我們想要深拷貝一個(gè)對(duì)象,用的最多的就是下面這個(gè)方法巢块。
JSON.parse(JSON.stringify());
復(fù)制代碼這種寫法非常簡(jiǎn)單礁阁,而且可以應(yīng)對(duì)大部分的應(yīng)用場(chǎng)景,但是它還是有很大缺陷的族奢,比如拷貝其他引用類型姥闭、拷貝函數(shù)、循環(huán)引用等情況歹鱼。
顯然泣栈,面試時(shí)你只說(shuō)出這樣的方法是一定不會(huì)合格的。
接下來(lái)弥姻,我們一起來(lái)手動(dòng)實(shí)現(xiàn)一個(gè)深拷貝方法南片。
基礎(chǔ)版
如果是淺拷貝的話,我們可以很容易寫出下面的代碼:
function clone(target) {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = target[key];
}
return cloneTarget;
};
創(chuàng)建一個(gè)新的對(duì)象庭敦,遍歷需要克隆的對(duì)象疼进,將需要克隆對(duì)象的屬性依次添加到新對(duì)象上,返回新對(duì)象秧廉。
如果是深拷貝的話伞广,考慮到我們要拷貝的對(duì)象是不知道有多少層深度的拣帽,我們可以用遞歸來(lái)解決問題,稍微改寫上面的代碼:
- 如果是原始類型嚼锄,無(wú)需繼續(xù)拷貝减拭,直接返回
- 如果是引用類型,創(chuàng)建一個(gè)新的對(duì)象区丑,遍歷需要克隆的對(duì)象拧粪,將需要克隆對(duì)象的屬性執(zhí)行深拷貝后依次添加到新對(duì)象上。
很容易理解沧侥,如果有更深層次的對(duì)象可以繼續(xù)遞歸直到屬性為原始類型可霎,這樣我們就完成了一個(gè)最簡(jiǎn)單的深拷貝:
function clone(target) {
if (typeof target === 'object') {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
這是一個(gè)最基礎(chǔ)版本的深拷貝,這段代碼可以讓你向面試官展示你可以用遞歸解決問題宴杀,但是顯然癣朗,他還有非常多的缺陷,比如旺罢,還沒有考慮數(shù)組旷余。
考慮數(shù)組
在上面的版本中,我們的初始化結(jié)果只考慮了普通的object
扁达,下面我們只需要把初始化代碼稍微一變荣暮,就可以兼容數(shù)組了:
module.exports = function clone(target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
循環(huán)引用
對(duì)象存在循環(huán)引用的情況,即對(duì)象的屬性間接或直接的引用了自身的情況:
解決循環(huán)引用問題罩驻,我們可以額外開辟一個(gè)存儲(chǔ)空間穗酥,來(lái)存儲(chǔ)當(dāng)前對(duì)象和拷貝對(duì)象的對(duì)應(yīng)關(guān)系,當(dāng)需要拷貝當(dāng)前對(duì)象時(shí)惠遏,先去存儲(chǔ)空間中找砾跃,有沒有拷貝過(guò)這個(gè)對(duì)象,如果有的話直接返回节吮,如果沒有的話繼續(xù)拷貝抽高,這樣就巧妙化解的循環(huán)引用的問題。
這個(gè)存儲(chǔ)空間透绩,需要可以存儲(chǔ)key-value
形式的數(shù)據(jù)翘骂,且key
可以是一個(gè)引用類型,我們可以選擇Map
這種數(shù)據(jù)結(jié)構(gòu):
- 檢查
map
中有無(wú)克隆過(guò)的對(duì)象 - 有 - 直接返回
- 沒有 - 將當(dāng)前對(duì)象作為
key
帚豪,克隆對(duì)象作為value
進(jìn)行存儲(chǔ) - 繼續(xù)克隆
function clone(target, map = new Map()) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
for (const key in target) {
cloneTarget[key] = clone(target[key], map);
}
return cloneTarget;
} else {
return target;
}
};