1. 程式人生 > >js的淺拷貝和深拷貝

js的淺拷貝和深拷貝

一.深拷貝、淺拷貝是什麼:

1.討論JS物件深拷貝、淺拷貝的前提:

(1)只有物件裡巢狀物件的情況下,才會根據需求討論,我們要深拷貝還是淺拷貝

(2)比如下面這種物件

var obj1 = {
	 name: 'ziwei',
	   arr : [1,2,3]
}

(3)如果是類似這樣{name: ‘ziwei’},沒有巢狀物件的物件的話,就沒必要區分深淺拷貝了。只有在有巢狀的物件時,深拷貝和淺拷貝才有區別

2.淺拷貝是什麼樣子的

呼叫shallowCopy()後,obj2拷貝obj1所有的屬性。但是obj2.arr和obj1.arr是不同的引用,指向同一個記憶體空間

 var obj2 = shallowCopy( obj1 , {}) 
 console.log( obj1 !== obj2 )                   // true    無論哪種拷貝,obj1和obj2一定都是2個不同的物件(記憶體空間不同) 
 console.log( obj2.arr === obj1.arr )            // true   他們2個物件裡arr的引用,指向【相同的】記憶體空間

所以, 2個obj經過拷貝後,雖然他們屬性相同,也的確是不同的物件,但他們內部的obj都是指向同一個記憶體空間,這種我們叫淺拷貝

3.深拷貝是什麼樣子的

呼叫deepCopy()後,obj2拷貝obj1所有的屬性,而且obj2.arr和obj1.arr是指向不同的記憶體空間,

2個obj2除了拷貝了一樣的屬性,沒有任何其他關聯

 var obj2 = deepCopy( obj1 , {}) 
 console.log( obj1 !== obj2 )                   // true    無論哪種拷貝,obj1和obj2一定都是2個不同的物件(記憶體空間不同) 
 console.log( obj2.arr === obj1.arr )            // false   他們2個物件裡arr的引用,指向【不同的】記憶體空間

所以, 2個obj經過拷貝後,除了拷貝下來相同的屬性之外,沒有任何其他關聯的2個物件,這種我們叫深拷貝

二.深拷貝在業務裡的最常見的應用場景

1.例子:

業務需求是 : 一個表格展示商品各種資訊,點選【同意】時,是可以彈出對話方塊調整商品數量的。

這種業務需求下,我們就會用到物件的深拷貝。因為【商品表格】的屬性和【調整商品表格】的屬性幾乎一樣,我們需要拷貝。

下面的虛擬碼和圖片就是展示使用淺拷貝存在的問題

圖片描述

這樣得到的adjustTableArr和tableArr裡,內部物件都是相同的,所以就出現了圖中紅線標註的情況,

當我們修改【調整商品表格】裡的商品數量時,【商品表格】也跟著改變了,這並不是我們想要的

// 表格物件的資料結構
var tableArr = [
    {goods_name : '長袖腰背夾' , code : 'M216C239E0864' , num : '2'},
    {goods_name : '長袖腰背夾' , code : 'M216C240B0170' , num : '3'},
    {goods_name : '短塑褲' , code : 'M216D241C04106' , num : '3'},
]   
var adjustTableArr = []                  // 調整表格用的陣列
for (var key in tableArr) {               // 淺拷貝
	    adjustTableArr[key] = tableArr[key]
}

而實際上,我們希望這2個表格裡的資料完全獨立,互不干擾,只有在確認調整之後才重新整理商品數量。

2.這種情況下我們就可以使用前面說的深拷貝的一行黑科技

var adjustTableArr = JSON.parse(JSON.stringify(tableArr))

還記得它的缺陷嗎? 物件裡的函式無法被拷貝,原型鏈裡的屬性無法被拷貝。這裡就對業務沒有影響,可以很方便的深拷貝。

三.深拷貝和淺拷貝的實現方式

其實JQ裡已經有$.extend()函式,實現就是深拷貝和淺拷貝的功能。有興趣的小夥伴也可以看看原始碼。

淺拷貝
淺拷貝比較簡單,就是用for in 迴圈賦值

function shallowCopy(source, target = {}) {
    var key;
    for (key in source) {
        if (source.hasOwnProperty(key)) {        // 意思就是__proto__上面的屬性,我不拷貝
            target[key] = source[key];
        }
    }
    return target;
}

深拷貝的實現
深拷貝,就是遍歷那個被拷貝的物件
判斷物件裡每一項的資料型別
如果不是物件型別,就直接賦值,如果是物件型別,就再次呼叫deepCopy,遞迴的去賦值。
function deepCopy(source, target = {}) {
var key;
for (key in source) {
if (source.hasOwnProperty(key)) { // 意思就是__proto__上面的屬性,我不拷貝
if (typeof(source[key]) === “object”) { // 如果這一項是object型別,就遞迴呼叫deepCopy
target[key] = Array.isArray(source[key]) ? [] : {};
deepCopy(source[key], target[key]);
} else { // 如果不是object型別,就直接賦值拷貝
target[key] = source[key];
}
}
}
return target;
}
以上的無論深、淺拷貝,都用了source.hasOwnProperty(key),意思是判斷這一項是否是其自有屬性,是的話才拷貝,不是就不拷貝。

也就是說__proto__上面的屬性,我不拷貝。這個其實你可以根據業務需求,來決定加上和這個條件

(JQ的$.extend()是會連__proto__上的屬性也拷貝下來的,但是是直接拷貝到物件上,而不是放到之前的__proto__上)

4.總結與建議
雖然大家可能經常用框架提供的api來實現深拷貝。

這篇文章分享的目的,更多還是希望用一篇文章整理清楚深淺拷貝的含義、遞迴實現思路,以及小夥伴們如果使用了JSON.parse()這種黑科技,一定要清楚這樣寫的優缺點。

5.修正
上面的deepCopy方法有漏洞,沒有考慮source一開始就是陣列的情況

下面是一個修改後版本

function deepCopy( source ) {
let target = Array.isArray( source ) ? [] : {}
for ( var k in source ) {
if ( typeof source[ k ] === ‘object’ ) {
target[ k ] = deepCopy( source[ k ] )
} else {
target[ k ] = source[ k ]
}
}
return target
}