深拷貝VS淺拷貝javascript
本文主要對深拷貝&淺拷貝的解釋及實現作一下簡單記錄。原文連接,歡迎star。html
之因此會有深拷貝與淺拷貝之分,是由於不一樣數據類型的數據在內存中的存儲區域不同。java
堆和棧是計算機中劃分出來用來存儲的區域,其中堆(heap)則是動態分配的內存,大小不定也不會自動釋放;而棧(stack)爲自動分配的內存空間,它由系統自動釋放。存放在棧內存中的簡單數據段,數據大小肯定,內存空間大小能夠分配,是直接按值存放的,因此能夠直接訪問。git
衆所周知,JavaScript中的數據分爲(基本類型和引用類型)。五種基本類型(boolean
,number
,undefined
,null
,string
,)的數據的原始值是大小肯定不可變的,因此放在棧內存中;而引用類型(object)是放在堆內存中的,對應賦值的變量只是一個存放在棧內存中的指針,也就是一個指向引用類型存放在堆內存中的地址。github
引用類型是引用的比較,例如數組
// a與b是兩個引用類型,但其指針指向的是兩個不一樣的引用 let a = [1,2,3]; let b = [1,2,3]; a===b; //false // 引用類型的賦值操做,b的指針也是指向與a一樣的引用 let a = [1,2,3]; let b = a; a===b; //true
而基本類型間的比較是值的比較,例如函數
let a = 1; let b = 1; a===b; //true let a = 1; let b = a; a===b; //true
在對基本類型進行賦值操做的時候實際是傳值,即在棧內存中新開一個區域給新的變量,而後再把值賦給它。因此基本類型賦值的變量是兩個互相獨立變量。post
let a = 10; let b = a; b++; console.log(a); //10 console.log(b); //11
而引用類型的賦值操做是傳址,只是在棧內存中新增了一個變量,同時賦值給這個變量的只是保存在堆內存中對象的地址,也就是指針的指向。所以這兩個變量的指針指向同一個地址,相互操做也就會有影響。this
let a = {}; let b = a; a.name = 'slevin'; console.log(b.name); //'slevin' console.log(a.name); //'slevin' console.log(a===b); //true
須要注意的是,引用類型的賦值並不算是淺拷貝,由於賦值操做只至關因而引用,兩個變量仍是指向同一對象,任何一方改變了都會影響到另外一方;但淺拷貝出來的變量與源變量已經不一樣,在不包含子對象的狀況下(此狀況即爲深拷貝),一方改變不會影響到另外一方。以下賦值操做:spa
let a = {}; let b = a; b.name = 'slevin'; console.log(a.name); //'slevin';對b操做影響到了a console.log(a===b); //true;由於二者指向同一個對象
自定義實現方法:
// 淺拷貝的方法 function shallowCopy(srcObj) { let copy = {}; for (let key in srcObj) { //只拷貝自身的屬性,__proto__上繼承來的屬性不作拷貝,也可去掉這個限制 if (srcObj.hasOwnProperty(key)) { copy[key] = srcObj[key]; } } return copy; } let obj = { name: 'slevin', age: 18, language: { 'english': 'good', 'mandarin': 'wonderful', } } let copy = shallowCopy(obj); copy.age = 28; console.log(obj.age); // 18; 並無改變源對象 console.log(obj === copy); //false;二者指向不是同一個對象
利用Object.assign(target,...sources)
可將一個或多個源對象上可枚舉屬性的值複製到目標對象,並返回目標對象。但注意,只能作一層屬性的淺拷貝。
let obj = { name: 'slevin', age: 18, language: { english: 'good', mandarin: 'wonderful', test: [1, 2, 3] }, fn:function(){ console.log('this:',this.name); } } let copy = Object.assign({},obj); //不會改變源對象 copy.age = 22; //會改變源對象 copy.language.english = 'bad'; console.log('copy===obj:',copy===obj);//false console.log('copy:',copy); console.log('obj:',obj);
對於數組來講,也可使用Array.prototype.slice()
方法和Array.prototype.concact()
方法
let obj = [1,2,['a','b','c']] let copy = obj.slice(); // 不會改變源對象 copy[0]=111; // 會改變源對象 copy[2][0]='aaa'; console.log('copy===obj:',copy===obj);//false console.log('copy:',copy); console.log('obj:',obj);
簡單來講,深拷貝就是把對象以及對象的子對象進行拷貝。由於淺拷貝只複製了一層對象的屬性,而若是對象的數值也爲引用類型,那麼拷貝過來依然是個引用地址,在拷貝對象上對子對象的值進行操做會改變原始數據中的值。
例如上面obj
淺拷貝獲得copy對象
,若是在copy
對象上改變子對象copy.language
上的屬性值,會影響到源對象obj
:
copy.language.english = 'bad'; copy.language.mandarin = 'bad'; console.log(obj.language); // {'english': 'bad','mandarin': 'bad',}
那麼如何深拷貝呢?思路就是遞歸調用剛剛的淺拷貝,遍歷全部值爲引用類型的屬性,而後依次賦值給另一個對象便可。
方法一,自定義實現:
/**將源對象source深度合併到目標對象target上
*/
function deepCopy (source,target={},deep=true) { for (let key in source){ // 深拷貝,並且只拷貝自身屬性.(默認傳入的source爲對象) if (deep && source.hasOwnProperty(key)){ if (typeof(source[key])==='object'){ // // source[key] 是對象,而 target[key] 不是對象, 則 target[key] = {} 初始化一下,不然遞歸會出錯的 // if (!(target[key] instanceof Object) && (source[key] instanceof Object)){ // target[key] = {}; // } // // source[key] 是數組,而 target[key] 不是數組,則 target[key] = [] 初始化一下,不然遞歸會出錯的 // if (!Array.isArray(target[key]) && Array.isArray(source[key])) { // target[key] = []; // } // 上面的代碼能夠簡化爲如下: target[key] = Array.isArray(source[key]) ? [] : {}; // 遞歸執行拷貝 deepCopy(source[key],target[key],true); } else { target[key] = source[key]; } } } return target; } let obj = { name: 'slevin', age: 18, language: { english: 'good', mandarin: 'wonderful', test:[1,2,3] } } let copy = deepCopy(obj); copy.language.test[0] = 111; console.log('copy:',copy); //111 改變目標對象的子對象屬性值 console.log('obj:',obj); //1 對應源對象上沒有改變
利用JSON.parse(JSON.stringify(copyObj))
方法
let obj = { name: 'slevin', age: 18, language: { english: 'good', mandarin: 'wonderful', test: [1, 2, 3] } } let copy = JSON.parse(JSON.stringify(obj)) copy.language.test[0]=111; console.log('copy===obj:',copy===obj);//false console.log('copy.language.test[0]:',copy.language.test[0]);//111 console.log('obj.language.test[0]:',obj.language.test[0]);//1
但要注意,此方法有兩個缺點:
let obj = { name: 'slevin', age: 18, language: { english: 'good', mandarin: 'wonderful', test: [1, 2, 3] }, fn:function(){ console.log('this:',this.name); } } let copy = JSON.parse(JSON.stringify(obj)); console.log('copy===obj:',copy===obj);//false console.log('obj.fn:',obj.fn); //fn(){}... console.log('copy.fn:',copy.fn); //undefined
最後,再補一張引用類型的賦值操做、淺拷貝深拷貝對比圖,加深印象