JS深拷貝的實現過程和程式碼解讀
在程式碼編寫中,我們經常用到複製/拷貝等操作。JS中存在兩種變數型別,分別為值型別和引用型別。
值型別的變數進行拷貝很容易,因為它們的值直接存在記憶體棧中。但是對於引用型別的變數來說,它們的記憶體棧中儲存的是所佔用的資料堆的地址,因此這樣 let a = { age: 20} let b = a
簡單拷貝,只是將a的地址拷貝給了b,a,b指向了同一個資料堆。這樣的拷貝,當b對age進行修改,對a的資料也進行了破壞。
接下來給出兩個關於值型別和引用型別的變數拷貝的程式碼,分析其原理:
// 值型別 let a = 100 let b = a a = 200 console.log(b) // 100 // 引用型別 let a = { age: 20} let b = a b.age = 21 console.log(a.age) // 21
對於值型別的資料,其儲存情況如下:
考慮到效能問題,值型別資料的儲存和複製都比較快速,不耗效能,因此它被儲存到棧裡,key下的value直接儲存值。
對於上面給出的程式碼,新建a,並賦值,其儲存結果如第一個框所示;新建b,並將a賦值給b,其儲存結果如第二個框所示;所以再a變化時,b不受影響,如第三個框所示。
對於引用型別的資料,其儲存情況如下:
在棧中,value內只儲存key所在的記憶體地址,通過記憶體地址到堆裡進行檢視具體值。
因此,當新建一個引用型別a,再新建一個引用型別b,並將a賦值給b,那b下儲存的只是a的記憶體地址。當b對age進行修改時,堆內的值被修改,也就是a的age也被修改,因為a、b指向同一個記憶體地址。
所以我們提出了深拷貝。深拷貝主要針對引用型別的變數在進行拷貝時出現只拷貝記憶體地址而不復制具體值的情況所提出的解決辦法。
先給出深拷貝的程式碼:
/** * @param {Object} 要拷貝的物件 */ function deepClone(obj = {}) { //針對的是物件和陣列 //如果obj 不是 物件或陣列 或obj傳入為空 //typeof可能判斷是否是引用型別,而無法進一步判斷是哪種引用型別 if(typeof obj != 'object' || obj == null) { return obj } //初始化返回結果 let result // instanceof 可以判斷 陣列 型別 if (obj instanceof Array) { result = [] } else { result = {} } for (let key in obj) { if (obj.hasOwnProperty(key)) { //保證key不是原型的屬性 //遞迴 result[key] = deepClone(obj[key]) } } //返回結果 return result }
上面的程式碼需要注意一下3點:
- 注意判斷值型別和引用型別,指對引用型別進行深拷貝
- 注意判斷是陣列還是物件,影響返回結果的初始化
- 遞迴
給出如下的程式碼來演示淺拷貝和深拷貝的區別,和深拷貝的作用:
const obj1 = {
age: 20,
name: 'xxx',
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
const obj2 = obj1
obj2.address.city = 'shanghai'
console.log(obj1.address.city)
const obj3 = deepClone(obj1)
obj3.address.city = 'shenzhen'
console.log(obj1.address.city)
執行,可以在控制器內看到如下結果:
我們建立了一個obj1,其中city的值為beijing,將obj1淺拷貝給obj2,並obj2.address.city = 'shanghai'
,列印obj1.address.city
,發現beijing被修改為shanghai;將obj1深拷貝給obj3,並obj3.address.city = 'shenzhen'
,列印obj1.address.city
,發現沒有被修改。這樣我們就完成了深拷貝操作。