寫在前面
深拷貝應該是前端面試中經常被問到的問題之一,搞定它可以讓我們在面試中如魚得水。那麼什麼是深拷貝呢?它和淺拷貝有什麼區別呢?如何實現一個深拷貝?相信看完這篇文章你就能回答上面的問題了。
一、簡單解釋
我們都知道js的數據類型包括兩種:基本數據類型和引用數據類型。我們今天所說的深拷貝和淺拷貝都只針對引用數據類型,淺拷貝只複製指向某個對象的指針,而不復制對象本身,新舊對象還是共享同一塊內存;但深拷貝會創造一個一模一樣的對象,新對象跟原對象不共享內存,修改新對象不會改到原對象。紙上得來終覺淺,先來一個例子,藉助例子大家會看的更明白。
var obj = {
name:'zhang',
say:function(){
console.log('hi');
},
a:undefined,
b:{c:'hello'}
}
二、淺拷貝
淺拷貝常用方法:
//方法一:
var newObj = {...obj}
//方法二:
var newObj = Object.assign({},obj)
接下來我們看看淺拷貝有什麼特性:
var obj2 = {...obj};
obj.b.c = 'world'
console.log(obj.b.c); //world
console.log(obj2.b.c); //world
console.log(obj === obj2); //true
從代碼的運行結果我們可以得知,拷貝後的新對象和舊對象還是共享同一塊內存空間。當我們修改新對象的某個屬性時,舊對象的那個屬性也被修改了,顯然這不是我們想要的結果。
三、深拷貝
上面淺拷貝的結果並不是我們想要的,那麼深拷貝會是什麼結果呢?深拷貝有多種方法,在這裏我們只講兩種:
3.1利用JSON.stringfy()和JSON.parse()實現深拷貝
var obj2 = JSON.parse(JSON.stringify(obj))
obj2.b.c = 'world'
console.log(obj.b.c); //hello
console.log(obj2.b.c); //world
console.log(obj === obj2); //false
同樣的代碼,和上面的淺拷貝運行結果完全不一樣。看到這裏是不是覺得深拷貝很簡單啊,一行代碼就實現了。事實並沒有那麼簡單,我們再打印一下拷貝的obj2對象
是的,你沒有看錯,深拷貝後的obj2對象竟然沒有了屬性a和say方法,這有點坑啊!注意: 使用這種方法拷貝對象時,當對象的屬性值是undefined、function、symbol類型時,在轉換過程中會被忽略。而且這種方法也無法實現對RegExp等特殊對象的克隆。所以這種方法還是要謹慎使用。
3.2構造深拷貝函數
function clone(obj){
var cloneObj = Array.isArray(obj) ? [] : {};
if(obj && typeof obj === 'object'){
for(key in obj){
if(obj.hasOwnProperty(key)){
//如果對象的屬性還是對象 則遞歸調用
if(obj[key] && typeof obj[key] === 'object'){
cloneObj[key] = clone(obj[key])
}else{
cloneObj[key] = obj[key]
}
}
}
}
return cloneObj;
}
代碼應該很好理解,關鍵的一點就是判斷對象的屬性,如果還是對象就遞歸賦值。同樣我們測試一下:
var obj2 = clone(obj);
obj2.b.c = 'world'
console.log(obj.b.c);
console.log(obj2.b.c);
console.log(obj === obj2);
console.log(obj);
console.log(obj2);
運行結果:
ok,結果沒毛病!
後話
看到這裏大家都能夠回答我們在文章開頭提出的那三個問題了吧。其實深拷貝還有其他方法,比如jquery的extend方法、lodash函數庫的cloneDeep方法等,不過這些方法很少用到,有興趣的同學可以瞭解一下。