天天看點

前端JS實作深度克隆和淺度克隆 對象或數組複制克隆 javascript拷貝

作者:小焱2018
前端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] = 5
console.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)   // true



obj.name = 'www'

console.log(cloneObj)   // { name: 'www', age: 18 }
           

我們可以發現,我們修改了obj變量的屬性值的時候,cloneObj的屬性值也跟着發生了變化。原因是他們雖然是兩個變量,但是引用的變量是同一個變量。看下圖分析:

前端JS實作深度克隆和淺度克隆 對象或數組複制克隆 javascript拷貝

深度克隆(深拷貝)

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)   // --> false



var obj = { name: 'ccc', age: 18 }

var cloneObj = deepClone(obj)

console.log(cloneObj)   // --> { name: 'ccc', age: 18 }

console.log(obj === cloneObj)   // false

obj.name = 'www'

console.log(obj)    // --> { name: 'www', age: 18 }

console.log(cloneObj)   // --> { name: 'ccc', age: 18 }
           

obj和cloneObj分别指向自己所存的變量位址,互不影響,代碼注釋挺詳細了,看下圖:

前端JS實作深度克隆和淺度克隆 對象或數組複制克隆 javascript拷貝

注意:上圖深度克隆代碼隻供參考了解,還有很多細節沒有考慮,比如數組和對象的嵌套拷貝等等,具體使用請檢視Lodash中的cloneDeep()方法。

Json.parse(Json.Stringfy())(深拷貝)

let arr3=JSON.parse(JSON.stringify(arr));           

//先把原數組轉換為json字元串,變為基本資料類型,完全生成一份新資料,然後把新資料轉換為js原數組,就利用了這一點,實作了深拷貝