引用類型
之所以會(huì)出現(xiàn)深淺拷貝的問(wèn)題,實(shí)質(zhì)上是由于JS對(duì)基本類型和引用類型的處理不同?;绢愋椭傅氖呛?jiǎn)單的數(shù)據(jù)段,而引用類型指的是一個(gè)對(duì)象,而JS不允許我們直接操作內(nèi)存中的地址,也就是不能操作對(duì)象的內(nèi)存空間,所以,我們對(duì)對(duì)象的操作都只是在操作它的引用而已。
在復(fù)制時(shí)也是一樣,如果我們復(fù)制一個(gè)基本類型的值時(shí),會(huì)創(chuàng)建一個(gè)新值,并把它保存在新的變量的位置上。而如果我們復(fù)制一個(gè)引用類型時(shí),同樣會(huì)把變量中的值復(fù)制一份放到新的變量空間里,但此時(shí)復(fù)制的東西并不是對(duì)象本身,而是指向該對(duì)象的指針。所以我們復(fù)制引用類型后,兩個(gè)變量其實(shí)指向同一個(gè)對(duì)象,改變其中一個(gè)對(duì)象,會(huì)影響到另外一個(gè)。
var num = 10;
var obj = {
name: 'Nicholas'
}
var num2 = num;
var obj2 = obj;
obj.name = 'Lee';
obj2.name; // 'Lee'
淺拷貝
1.Object.assign()
其中第一個(gè)參數(shù)是我們最終復(fù)制的目標(biāo)對(duì)象,后面的所有參數(shù)是我們的即將復(fù)制的源對(duì)象,支持對(duì)象或數(shù)組,一般調(diào)用的方式為
var newObj = Object.assign({}, originObj);
這樣我們就得到了一個(gè)新的淺拷貝對(duì)象。另外[].slice()方法可以視為數(shù)組對(duì)象的淺拷貝。
2. 自定義淺拷貝
如果我們要復(fù)制對(duì)象的所有屬性都不是引用類型時(shí),就可以使用淺拷貝,實(shí)現(xiàn)方式就是遍歷并復(fù)制,最后返回新的對(duì)象。
function shallowCopy(obj) {
var copy = {};
// 只復(fù)制可遍歷的屬性
for (key in obj) {
// 只復(fù)制本身?yè)碛械膶傩? if (obj.hasOwnProperty(key)) {
copy[key] = obj[key];
}
}
return copy;
}
如上面所說(shuō),我們使用淺拷貝會(huì)復(fù)制所有引用對(duì)象的指針,而不是具體的值,所以使用時(shí)一定要明確自己的需求,同時(shí),淺拷貝的實(shí)現(xiàn)也是最簡(jiǎn)單的。
深拷貝
如果我們需要復(fù)制一個(gè)擁有所有屬性和方法的新對(duì)象,就要用到深拷貝,JS并沒(méi)有內(nèi)置深拷貝方法,主要是因?yàn)椋?/p>
- 深拷貝怎么定義?我們?cè)趺刺幚碓??怎么區(qū)分可拷貝的對(duì)象?原生DOM/BOM對(duì)象怎么拷貝?函數(shù)是新建還是引用?這些edge case太多導(dǎo)致我們無(wú)法統(tǒng)一概念,造出大家都滿意的深拷貝方法來(lái)。
- 內(nèi)部循環(huán)引用怎么處理,是不是保存每個(gè)遍歷過(guò)的對(duì)象列表,每次進(jìn)行對(duì)比,然后再造一個(gè)循環(huán)引用來(lái)?這樣帶來(lái)的性能消耗可以接受嗎。
var obj1 = {
age: 20,
name: 'xxx',
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
var obj2 = deepClone(obj1)
obj2.address.city = '上海'
console.log(obj2)
console.log(obj1)
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
return obj
}
let result;
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result
}
