深拷貝和淺拷貝
這兩個概念是在項目中比較常見的,在不少時候,都會遇到拷貝的問題,咱們老是須要將一個對象賦值到另外一個對象上,但可能會在改變新賦值對象的時候,忽略掉我是否以後還須要用到原來的對象,那麼就會出現當改變新賦值對象的某一個屬性時,也同時改變了原對象,此時咱們就須要用到拷貝這個概念了。
深拷貝和淺拷貝的區別
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.拓展運算符(...)
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)
參考文章: