前端JS實現深度克隆和淺度克隆 物件或陣列複製克隆 javascript複製
在聊JavaScript(以下簡稱js)深度克隆之前,我們先來了解一下js中物件的組成。
在 js 一切例項皆是物件,具體分為
原始型別
和
合成型別
:
原始型別
物件指的是 Undefined 、 Null 、Boolean 、Number 和 String ,按值傳遞。
合成型別
物件指的是 array 、 object 以及 function ,按地址傳遞,傳遞的時候是記憶體中的地址。
克隆或者複製分為2種:
淺度克隆
、
深度克隆
。
淺度克隆
:基本型別為值傳遞,物件仍為引用傳遞。對複製後的資料進行修改會影響原資料的一種複製方式。
深度克隆
:所有元素或屬性均完全克隆,並於原引用型別完全獨立,即,在後面修改物件的屬性的時候,原物件不會被修改。對複製後的資料進行修改是不會影響原資料的一種複製方式。
js中的深淺複製一直是一個熱門的話題,簡單來說,複製就是透過一些方法產生與被複製資料(幾乎)完全一樣的資料。在js中,複製可以分為深複製(深度克隆)和淺複製兩種。
按照一般的理解來說透過複製得到的資料無論如何被修改應該也不會影響原資料呀,比如我們日常生活中使用的複製貼上,一般情況下,我們沒見過誰修改一個複製得到的word文件導致原文件也發生了修改呀!這就得從js的記憶體空間說起。
js記憶體空間的補充:
js中的記憶體空間由常量池、棧和堆組成,其中堆用來存放引用型別的資料,像陣列、物件這些都屬於引用型別的資料。一個物件在記憶體空間中的存放形式為:將物件的名稱和引用存放在棧空間,該引用指向堆空間中的該物件,陣列同理。
而我們都知道透過賦值運算等方法產生一個複製物件,拿到的其實還是原物件,因為我們複製的僅僅是該物件的引用,修改新的物件,原物件的內容也會發送改變,因為原物件和新物件的引用一樣,所以深淺複製問題就誕生了。
淺克隆(淺複製)
在資料型別為引用型別的時候,當你給這個變數賦值,其實是引用這個變數在記憶體中的地址。如下:
1。
直接賦值
在資料型別為引用型別的時候,當你給這個變數賦值,其實是引用這個變數在記憶體中的地址。如下:
var obj = {name: ‘ccc’, age: 18} // 定義一個變數為物件,引用型別var cloneObj = obj // 建立一個新變數,並賦值console。log(cloneObj) // {name: ‘ccc’, age: 18} console。log(cloneObj === obj) // true
2。
Object.assgin():
const arr1 = [1, 2, 3]const arr2 = Object。assign(arr1)arr2[0] = 5console。log(arr1) // [5, 2, 3]console。log(arr2) // [5, 2, 3]
3、Array.prototype.concat()(淺複製)
let arr=[1,2,3,4,{username:“kebi”,age:18}]; let arr1=arr。concat() // concat是連線陣列,如果不傳參,則表示複製原陣列的資料到目標陣列中
4、Array.prototype.slice()(淺複製)
let arr2=arr。slice(); //slice是擷取陣列或者字串,不傳參表示預設全部
淺克隆帶來的問題:
複製的資料裡不能有函式,處理不了,淺複製,複製的是引用,修改複製以後的資料會影響原資料,深複製(深度克隆),複製時生成新資料,修改不會影響原資料
var obj = {name: ‘ccc’, age: 18} // 定義一個變數為物件,引用型別var cloneObj = obj // 建立一個新變數,並賦值console。log(cloneObj) // {name: ‘ccc’, age: 18} console。log(cloneObj === obj) // trueobj。name = ‘www’console。log(cloneObj) // { name: ‘www’, age: 18 }
我們可以發現,我們修改了obj變數的屬性值的時候,cloneObj的屬性值也跟著發生了變化。原因是他們雖然是兩個變數,但是引用的變數是同一個變數。看下圖分析:
深度克隆(深複製)
1.
判斷被複製物件的型別
2.
根據型別生成空物件或空陣列,其他基本資料型別直接返回即可
3.
呼叫for in方法對被複製物件(陣列)進行遍歷,往新物件(陣列)中新增資料,如果是基本資料型別,則直接將其新增到新陣列(物件)中,否則深度克隆該資料,這樣子進行
遞迴
即可。
深度克隆,就是解決淺度克隆帶來的問題的。直接上程式碼:
function deepClone(o) { // 判斷如果不是引用型別,直接返回資料即可 if (typeof o === ‘string’ || typeof o === ‘number’ || typeof o === ‘boolean’ || typeof o === ‘undefined’) { return o } else if (Array。isArray(o)) { // 如果是陣列,則定義一個新陣列,完成複製後返回 // 注意,這裡判斷陣列不能用typeof,因為typeof Array 返回的是object console。log(typeof []) // ——> object var _arr = [] o。forEach(item => { _arr。push(item) }) return _arr } else if (typeof o === ‘object’) { var _o = {} for (let key in o) { _o[key] = deepClone(o[key]) } return _o }}var arr = [1, 2, 3, 5]var cloneArr = deepClone(arr)console。log(cloneArr) // ——> [ 1, 2, 3, 5 ]console。log(arr === cloneArr) // ——> falsevar obj = { name: ‘ccc’, age: 18 }var cloneObj = deepClone(obj)console。log(cloneObj) // ——> { name: ‘ccc’, age: 18 }console。log(obj === cloneObj) // falseobj。name = ‘www’console。log(obj) // ——> { name: ‘www’, age: 18 }console。log(cloneObj) // ——> { name: ‘ccc’, age: 18 }
obj和cloneObj分別指向自己所存的變數地址,互不影響,程式碼註釋挺詳細了,看下圖:
注意:上圖深度克隆程式碼只供參考瞭解,還有很多細節沒有考慮,比如陣列和物件的巢狀複製等等,具體使用請檢視Lodash中的cloneDeep()方法。
Json.parse(Json.Stringfy())(深複製)
let arr3=JSON。parse(JSON。stringify(arr));
//先把原陣列轉換為json字串,變為基本資料型別,完全生成一份新資料,然後把新資料轉換為js原陣列,就利用了這一點,實現了深複製