前言
前拷貝和深拷貝都是對於JS中的引用類型而言的,淺拷貝就只是複製對象的引用,如果拷貝後的對象發生變化,原對象也會發生變化。只有深拷貝纔是真正地對對象的拷貝。
首先,需要知道,JavaScript中的數據類型分爲基本數據類型和引用數據類型,對於基本數據類型而言,沒有深淺拷貝的區別,我們所說的深拷貝都是對於引用數據類型而言的。
淺拷貝
const obj = { a: 'a', b: 'b', c: [1, 2, 3], d: { dd: 'dd' } }
const array = [1, 2, 3, 4, 5]
const CloneObj = obj
const CloneArray = array
CloneObj.a = 'c'
CloneArray.push(6)
console.log(CloneArray, CloneObj)
// [1, 2, 3, 4, 5, 6]
console.log(CloneObj)
// a: "c"
// b: "b"
// c: (3) [1, 2, 3]
// d: {dd: "dd"}
可以清楚地看到當CloneObj和CloneArray發生變化時,obj和array也發生了變化。
深拷貝
深拷貝就是對目標完全的拷貝,只要進行了深拷貝,他們就不會再互相影響。
目前實現深度拷貝的方法主要有兩種:
1. 利用JSON對象中的parse和stringify
2.利用遞歸來實現每一層重新創建對象並賦值
首先看第一種方式
// 使用JSON來實現深度克隆
const obj1 = { a: 'a', b: 'b', c: [1, 2, 3], d: { dd: 'dd' } }
const CloneObj1 = JSON.parse(JSON.stringify(obj1))
console.log(obj1 === CloneObj1) // false
CloneObj1.a = { aa: 'aa' }
console.log(obj1) //a: "a", b: "b",c: [1, 2, 3],d: {dd: "dd"}
const array1 = [1, 2, 3, 4, 5]
const CloneArray1 = JSON.parse(JSON.stringify(array1))
console.log(array1 === CloneArray1) // false
CloneArray1.push(6)
console.log(array1) // [1, 2, 3, 4, 5]
以上代碼確實實現了深度拷貝,但是這種方法只能使用一些簡單的情況,比如如果對象中的一個屬性值是一個函數,那麼使用這個方法會導致屬性丟失。
因爲undefined、function、symbol會在轉換過程中被忽略,所以當一個對象中有函數時,就不能用這個方法進行深拷貝。
第二種方式:遞歸
function DeepClone(source) {
// 判斷目標是數組還是對象
const targetObj = source.constructor === Array ? [] : {}
for (let key in source) {
if (source.hasOwnProperty(key)) {
// 如果是對象就遞歸
if (source[key] && typeof source[key] === 'object') {
targetObj[key] = source[key].constructor === Array ? [] : {}
targetObj[key] = DeepClone(source[key])
} else {
targetObj[key] = source[key]
}
}
}
return targetObj
}
首先判斷目標的類型,但是要怎麼判斷呢??? 我們可以根據constructor屬性來判斷,因爲實例對象的constructor屬性指向它的構造函數,之前我們提到過,只有引用類型的數據纔有淺拷貝深拷貝之分,所以接下來就是要判斷是不是引用類型,如果是就繼續向下進行遞歸,如果不是則直接賦值,這樣就實現了深度拷貝。
那麼我們來測試一下:
var a = {
msg: '213123',
data: '2018141142',
getName: function() {
return 'Bob'
}
}
var b = DeepClone(a)
b.data = '0'
console.log(a) // {msg: "213123", data: "2018141142", getName: ƒ}
可以,沒有問題!
補充:JavaScript中拷貝的方法
我們知道在JavaScript
中,數組有兩個方法 concat
和 slice
是可以實現對原數組的拷貝的,這兩個方法都不會修改原數組,而是返回一個修改後的新數組。
同時,ES6 中 引入了 Object.assgn
方法和 ...
展開運算符也能實現對對象的拷貝。
經過我自己的測試,發現他們都是首層淺拷貝。
總結:
1. 賦值運算符=實現的是淺拷貝,只拷貝對象的引用值
2. JavaScript中數組和對象自帶的拷貝方法都是首層淺拷貝
3. JSON實現的是深拷貝,但是對目標對象有要求
4. 如果想實現真正意義上的深拷貝,採用上面的遞歸方式