JavaScript 的 深拷貝和淺拷貝
深拷貝和淺拷貝都是針對的引用型別,
JS中的變數型別分為值型別(基本型別)和引用型別;
對值型別進行復制操作會對值進行一份拷貝,而對引用型別賦值,則會對地址進行拷貝,最終兩個變數指向同一份資料
一、先來看看JS中的資料型別
let x = 1; //number型別 let x = 0.1; //number型別,JS不區分整數值和浮點數值 let x = "hello world"; //由雙引號內文字構成字串 let x = 'javascript'; //單引號內文字同樣可以構成字串 let x = true; // boolean 布林型別 let x = null; let x = undefined; //null和undefined很相似,是特殊的型別
JS 中資料分為兩種型別:
- 原始資料型別
- number
- string
- boolean
- null
- undefined
- 物件資料型別
- array 陣列 特殊物件型別
- function 函式 特殊物件型別
- object 物件
還有 undefined 和 null,此處暫不討論
object物件需要注意的點:
- 物件是可變的,即值是可以修改的
- 物件的比較並非值得比較
比如:var a = [], b = [];
a == b; //false,只有在引用相同(指向的地址相同)時,兩個只才會相等
由此可以延伸出深拷貝和淺拷貝 的問題。
=========================================================================
我們的困惑:
1. 看著相等,卻又不等
2. 想要不等,卻又相等
那麼造成這樣問題的原因在哪呢?
> 物件的引用
> 引用只會對地址進行賦值,所以
1. 不同的變數 a 和 b,他們的地址不同,即使資料相同,本身也不會相等,這是造成困惑一的原因;
2. 而變數 aa 和 bb 指向同一地址,當該地址的資料改變時,所有使用該地址的變數全部改變(同一資料),這是造成困惑二的原因
達不到我們想要的效果,怎麼辦呢?
二、引用(物件)資料型別的賦值和比較問題
解決辦法:笨辦法,也是唯一的方式,既然物件資料型別 是由基本資料型別組成的,而基本資料型別可以正常賦值、比較,那我們就把物件型別變成一個個的基本型別進行操作
方法一: 遍歷物件中的內容,一個一個的進行賦值,這樣只進行一層拷貝的方式了,就是淺拷貝
// 淺拷貝方法 function shallowClone(source) { let target = {}; for(leti in source) { if (source.hasOwnProperty(i)) { target[i] = source[i]; } } return target; }
方法二: 相對於一層拷貝的淺拷貝,無線層次的拷貝叫做 深拷貝
// 簡單深拷貝 function clone(source) { let target = {}; for(let i in source) { if (source.hasOwnProperty(i)) { if (typeof source[i] === 'object') { target[i] = clone(source[i]); // 判斷仍是物件,就進行遞迴 } else { target[i] = source[i]; } } } return target; }
上面 clone 方法 和 shallowClone 方法的 區別就是 多了 遞迴
但是仍然有些問題需要注意:
- 引數需要檢驗
- 判斷是否物件的邏輯不夠嚴謹
- 需要考慮陣列的情況
暫不細說,判斷物件可以用此方法:
// 更嚴謹的判斷物件的方法 function isObject(x) { return Object.prototype.toString.call(x) === '[object Object]'; }
當然我們也可以參考其他方法或使用外掛
比如: 簡單粗暴的JSON.parse(JSON.stringify(oldObj))
比如:ES6的assign方法(淺拷貝)
比如: 通過immutableJS實現深拷貝
三、最後
無論 淺拷貝,還是深拷貝 都會帶來效能問題(平白的需要遍歷,只是重新賦值)
所以我們物件最好寫的淺一點,精簡一點。。。
詳細可以看這篇:https://yanhaijing.com/javascript/2018/10/10/clone-deep/