在JavaScript中朴上,拷貝對象或數(shù)組時(shí),我們需要理解“深拷貝”和“淺拷貝”的區(qū)別色查。這兩種拷貝方式在處理嵌套對象或數(shù)組時(shí)表現(xiàn)不同剪勿。
淺拷貝
淺拷貝是指創(chuàng)建一個新的對象或數(shù)組,其元素是對原對象或數(shù)組中元素的引用耻姥。如果原對象或數(shù)組中的元素是基本類型(如數(shù)字销钝、字符串、布爾值)琐簇,那么這些元素會被復(fù)制到新對象或數(shù)組中蒸健。但如果元素是對象或數(shù)組,那么新對象或數(shù)組中的對應(yīng)元素將是對原對象中對應(yīng)元素的引用婉商,而不是一個新的副本似忧。
應(yīng)用場景:
- 當(dāng)你只需要復(fù)制對象或數(shù)組的第一層結(jié)構(gòu),并且不關(guān)心嵌套對象或數(shù)組的獨(dú)立性時(shí)丈秩。
- 在性能敏感的場景中盯捌,淺拷貝比深拷貝更快,因?yàn)樗恍枰f歸地復(fù)制所有嵌套元素蘑秽。
實(shí)例代碼:
// 淺拷貝對象
const originalObject = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, originalObject);
originalObject.b.c = 3;
console.log(shallowCopy.b.c); // 輸出 3饺著,因?yàn)閎屬性仍然引用原對象中的嵌套對象
// 淺拷貝數(shù)組
const originalArray = [1, 2, { a: 3 }];
const shallowCopyArray = originalArray.slice();
originalArray[2].a = 4;
console.log(shallowCopyArray[2].a); // 輸出 4,因?yàn)閿?shù)組中的對象仍然是原對象中的引用
-
對象擴(kuò)展運(yùn)算符(...):
對象擴(kuò)展運(yùn)算符可以用于將一個對象的所有可枚舉屬性復(fù)制到另一個新對象中肠牲。
const original = { a: 1, b: 2 };
const copy = { ...original };
copy.a = 3;
console.log(original.a); // 輸出 1瓶籽,原對象未受影響
-
Object.assign() 方法:
Object.assign 方法可以將一個或多個源對象的所有可枚舉屬性復(fù)制到目標(biāo)對象中,并返回目標(biāo)對象埂材。
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original);
copy.a = 3;
console.log(original.a); // 輸出 1塑顺,原對象未受影響
-
數(shù)組方法 slice() 和 concat():
對于數(shù)組,可以使用 slice() 方法或 concat() 方法來創(chuàng)建淺拷貝俏险。
slice() 方法返回一個新的數(shù)組對象严拒,這一新的數(shù)組對象是一個從原數(shù)組中淺拷貝出來的部分,原數(shù)組不會被修改竖独。
const originalArray = [1, 2, 3];
const copyArray = originalArray.slice();
copyArray[0] = 4;
console.log(originalArray[0]); // 輸出 1裤唠,原數(shù)組未受影響
concat() 方法也可以用于淺拷貝數(shù)組,它返回一個新數(shù)組莹痢,該數(shù)組是原數(shù)組與傳入的數(shù)組/值連接后的一個新數(shù)組种蘸。如果只傳入一個空數(shù)組作為參數(shù)墓赴,就相當(dāng)于創(chuàng)建了一個原數(shù)組的淺拷貝。
const originalArray = [1, 2, 3];
const copyArray = originalArray.concat();
copyArray[0] = 4;
console.log(originalArray[0]); // 輸出 1航瞭,原數(shù)組未受影響
-
Array.from() 方法:
Array.from() 方法可以從類似數(shù)組或可迭代對象中創(chuàng)建一個新的诫硕、淺拷貝的數(shù)組實(shí)例。
const originalArray = [1, 2, 3];
const copyArray = Array.from(originalArray);
copyArray[0] = 4;
console.log(originalArray[0]); // 輸出 1刊侯,原數(shù)組未受影響
-
直接賦值(對于非對象和數(shù)組的基本類型):
如果值是非對象(例如數(shù)字章办、字符串、布爾值)滨彻,則直接賦值就會創(chuàng)建一個新的副本藕届,因?yàn)榛绢愋褪前粗祩鬟f的。
let original = 42;
let copy = original;
copy = 43;
console.log(original); // 輸出 42亭饵,原值未受影響
但是休偶,如果值是一個對象或數(shù)組,直接賦值將不會創(chuàng)建副本辜羊,而是創(chuàng)建一個指向原對象或數(shù)組的引用踏兜。
深拷貝
深拷貝是指創(chuàng)建一個新的對象或數(shù)組,并且遞歸地復(fù)制所有嵌套的對象或數(shù)組只冻,使得新對象或數(shù)組與原對象或數(shù)組完全獨(dú)立庇麦。深拷貝會占用更多的內(nèi)存计技,并且執(zhí)行速度較慢喜德,因?yàn)樗枰f歸地處理所有嵌套元素。
應(yīng)用場景:
- 當(dāng)你需要復(fù)制一個對象或數(shù)組垮媒,并且希望新對象或數(shù)組與原對象或數(shù)組完全獨(dú)立時(shí)舍悯。
- 在處理包含復(fù)雜嵌套結(jié)構(gòu)的數(shù)據(jù)時(shí),如配置對象睡雇、狀態(tài)樹等萌衬。
實(shí)例代碼:
使用JSON.parse和JSON.stringify進(jìn)行深拷貝(這種方法有局限性,不能處理函數(shù)它抱、undefined秕豫、NaN、循環(huán)引用等):
const originalObject = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(originalObject));
originalObject.b.c = 3;
console.log(deepCopy.b.c); // 輸出 2观蓄,因?yàn)樯羁截悇?chuàng)建了一個新的嵌套對象
// 注意:這種方法不適用于數(shù)組中包含函數(shù)或其他特殊值的情況
深拷貝與 JSON.stringify
當(dāng)你使用 JSON.parse(JSON.stringify(obj)) 作為深拷貝的一種手段時(shí)混移,你需要注意上述行為。特別是侮穿,如果你的對象中包含函數(shù)歌径、undefined、Symbol亲茅、循環(huán)引用回铛、Infinity狗准、NaN、Date 或 RegExp茵肃,那么這種方法可能不會按預(yù)期工作腔长。
示例
const original = {
a: 1,
b: undefined,
c: function() {},
d: Symbol('d'),
e: Infinity,
f: NaN,
g: new Date(),
h: /hello/,
i: { j: 1, k: { l: 2 } }
};
const copy = JSON.parse(JSON.stringify(original));
console.log(copy);
// 輸出:
// {
// a: 1,
// i: { j: 1, k: { l: 2 } },
// g: "2023-04-01T12:00:00.000Z" (日期會被轉(zhuǎn)換為 ISO 格式字符串)
// }
// 注意:b, c, d, e, f, h 屬性都丟失了或者被轉(zhuǎn)換了
在這個例子中,你可以看到 b(undefined)免姿、c(函數(shù))饼酿、d(Symbol)、e(Infinity)胚膊、f(NaN)故俐、h(RegExp)屬性都沒有被復(fù)制到新對象中,而 g(Date 對象)被轉(zhuǎn)換為了一個字符串紊婉。只有 a 和 i(嵌套對象)被正確地復(fù)制了药版。
使用遞歸函數(shù)進(jìn)行深拷貝(更通用,但也需要小心處理循環(huán)引用):
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}
const copy = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key]);
}
}
return copy;
}
const originalObject = { a: 1, b: { c: 2 } };
const deepCopy = deepClone(originalObject);
originalObject.b.c = 3;
console.log(deepCopy.b.c); // 輸出 2喻犁,因?yàn)樯羁截悇?chuàng)建了一個新的嵌套對象
在實(shí)際應(yīng)用中槽片,選擇淺拷貝還是深拷貝取決于你的具體需求。如果你只需要復(fù)制對象或數(shù)組的第一層結(jié)構(gòu)肢础,并且不關(guān)心嵌套元素的獨(dú)立性还栓,那么淺拷貝就足夠了。如果你需要完全獨(dú)立的副本传轰,包括所有嵌套的對象和數(shù)組剩盒,那么你應(yīng)該使用深拷貝。