深拷貝和淺拷貝
這兩個概念是在項目中比較常見的,在很多時候,都會遇到拷貝的問題,我們總是需要將一個對象賦值到另一個對象上,但可能會在改變新賦值對象的時候,忽略掉我是否之后還需要用到原來的對象,那么就會出現當改變新賦值對象的某一個屬性時,也同時改變了原對象,此時我們就需要用到拷貝這個概念了。
深拷貝和淺拷貝的區別
1.淺拷貝: 將原對象或原數組的引用直接賦給新對象,新數組,新對象/數組只是原對象的一個引用
2.深拷貝: 創建一個新的對象和數組,將原對象的各項屬性的“值”(數組的所有元素)拷貝過來,是“值”而不是“引用”
在這里,我們需要注意一點,那就是"引用"和"值"是什么,在學c的時候,涉及到了一個指針的概念,指針(Pointer)是編程語言中的一個對象,利用地址,它的值直接指向(points to)存在電腦存儲器中另一個地方的值。由於通過地址能找到所需的變量單元,可以說,地址指向該變量單元。所以,這里的值就是對象的值,而引用就是指向存儲這個值的地址。
那么這里我覺得稍微需要說一下堆棧和指針的關系了
當變量復制引用類型值的時候,同樣和基本類型值一樣會將變量的值復制到新變量上,不同的是對於變量的值,它是一個指針,指向存儲在堆內存中的對象(JS規定放在堆內存中的對象無法直接訪問,必須要訪問這個對象在堆內存中的地址,然后再按照這個地址去獲得這個對象中的值,所以引用類型的值是按引用訪問)
圖文說明:
1.基本類型---存儲

2.基本類型---復制

3.引用類型---存儲

如上圖,引用類型在棧里面存儲的是地址指針
4.引用類型---賦值

5.引用類型---拷貝
接下來說一下淺拷貝和深拷貝的方法(注意需要把拷貝和賦值區分開來,網上很多文章中都把賦值和淺拷貝混為一談,那么這里理解起來是會自我矛盾的)
淺拷貝
概念:創建一個新對象,這個對象有着原始對象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的就是內存地址 ,所以如果其中一個對象改變了這個地址,就會影響到另一個對象。
思路:淺拷貝可以想做把引用類型的第一子級當作是每一個數據類型來賦值。例如:
let obj = {
a: 1,
b: {
c: 2,
}
}
那么obj的淺拷貝就可以看作是a賦值,b賦值。結合上圖,那么當target淺拷貝obj時,那么target中的a會在棧內存中重新開辟空間存儲值,由於b是引用類型,那么b還是存儲地址,指向obj的b的值。
方法:
1.Object.assign()
定義:用於將所有可枚舉屬性的值從一個或多個源對象復制到目標對象。它將返回目標對象。
用法: Object.assign(target, ...sourceObj)

注意:
不會拷貝對象繼承的屬性、
不可枚舉的屬性、
屬性的數據屬性/訪問器屬性、
可以拷貝Symbol類型
2.拓展運算符(...)
用法:...object

3.Array.prototype.slice和Array.prototype.concat
用法:arrayObject.slice(start,end)、arrayObject.concat(arrayX,...,arrayX)
arrayObject.slice(start,end)

arrayObject.concat(arrayX,...,arrayX)

4.遍歷
function deepClone(source) {
if (!source || typeof source !== 'object') {
return source;
}
const targetObj = source.constructor === Array ? [] : {};
for (const keys in source) {
if (source.hasOwnProperty(keys)) {
targetObj[keys] = source[keys];
}
}
return targetObj;
}
深拷貝:
概念:將一個對象從內存中完整的拷貝一份出來,從堆內存中開辟一個新的區域存放新對象,且修改新對象不會影響原對象
思路:通過上述淺拷貝的示例,可以看出,這些方法都只實現了對對象的第一層元素的拷貝,但是之后層的元素還是共享的,那么我們還是結合淺拷貝的思路,參考基本類型的賦值過程,來解決這個問題。
方法:
1.遞歸遍歷
在淺拷貝的遍歷方法上多加一層判斷,進而對所有層級的元素進行遍歷賦值
function deepClone(source) {
if (!source || typeof source !== 'object') {
return source;
}
const targetObj = source.constructor === Array ? [] : {};
for (const keys in source) {
if (source.hasOwnProperty(keys)) {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
} else {
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
2.JSON.parse(JSON.stringify(XXXX))
JSON.stringify方法是把對象轉成字符串,返回新生成的字符串,這一步可以看作是,把目標對象轉化成基礎類型后,重新開辟一個新的區域存放轉化后的字符串,然后在通過JSON.parse轉成JSON格式,在賦給新的對象,這樣操作新的對象就和目標對象毫無關系了

注意:
1.拷貝的對象的值中如果有函數,undefined,symbol類型,不會轉換,而是丟棄
2.無法拷貝不可枚舉的屬性,無法拷貝對象的原型鏈
3.拷貝Date引用類型會變成字符串
4.拷貝RegExp引用類型會變成空對象
5.對象中含有NaN、Infinity和-Infinity,則序列化的結果會變成null
6.無法拷貝對象的循環應用(即obj[key] = obj)
參考文章: