JavaScript中的淺拷貝與深拷貝
前言
文章開始之前,讓我們先思考一下這幾個問題:
- 為什麼會有淺拷貝與深拷貝
- 什麼是淺拷貝與深拷貝
- 如何實現淺拷貝與深拷貝
好了,問題出來了,那麼下面就讓我們帶著這幾個問題去探究一下吧!
如果文章中有出現紕漏、錯誤之處,還請看到的小夥伴多多指教,先行謝過
以下↓
資料型別
在開始瞭解淺拷貝
與深拷貝
之前,讓我們先來回顧一下JavaScript
的資料型別(可以參考這裡JavaScript中的資料型別
)
在JavaScript
中,我們將資料分為基本資料型別(原始值)
與引用型別
- 基本資料型別的值是按值訪問的,基本型別的值是不可變的
- 引用型別的值是按引用訪問的,引用型別的值是動態可變的
由於資料型別的訪問方式不同,它們的比較方式也是不一樣的
var a = 100; var b = 100; a === b // true var c = {a: 1, b: 2}; var d = {a: 1, b: 2}; c == d // false 兩個不同的物件 複製程式碼
- 基本資料型別的比較是值得比較
- 引用型別的比較是引用地址的比較
鑑於以上資料型別的特點,我們可以初步想到:
所謂淺拷貝
與深拷貝
可能就是對於值的拷貝和引用的拷貝
(簡單資料型別都是對值的拷貝,不進行區分)
一般來說,我們所涉及的拷貝物件,也都是針對引用型別的。由於引用型別屬性層級可能也會有多層,這樣也就引出了我們所要去了解的淺拷貝
與深拷貝
淺拷貝
顧名思義,所謂淺拷貝就是對物件進行淺層次的複製,只複製一層物件的屬性,並不包括物件裡面的引用型別資料
想象一下,如果讓你自己去實現這個功能,又會有怎麼的思路呢
首先,我們需要知道被拷貝物件有哪些屬性吧,然後還需要知道這些屬性都對應了那些值或者地址的引用吧。那麼,答案已經呼之欲出了,是的,迴圈
var person = { name: 'tt', age: 18, friends: ['oo', 'cc', 'yy'] } function shallowCopy(source) { if (!source || typeof source !== 'object') { throw new Error('error'); } var targetObj = source.constructor === Array ? [] : {}; for (var keys in source) { if (source.hasOwnProperty(keys)) { targetObj[keys] = source[keys]; } } return targetObj; } var p1 = shallowCopy(person); console.log(p1) 複製程式碼
在上面的程式碼中,我們建立了一個shallowCopy
函式,它接收一個引數也就是被拷貝的物件。
- 首先建立了一個物件
-
然後
for...in
迴圈傳進去的物件,為了避免迴圈到原型上面會被遍歷到的屬性,使用hasOwnProperty
限制迴圈只在物件自身,將被拷貝物件的每一個屬性和值新增到建立的物件當中 - 最後返回這個物件
通過測試,我們拿到了和person
物件幾乎一致的物件p1
。看到這裡,你是不是會想那這個結果和var p1 = person
這樣的賦值操作又有什麼區別呢?
我們再來測試一波
var p2 = person; // 這個時候我們修改person物件的資料 person.name = 'tadpole'; person.age = 19; person.friends.push('tt') p2.name // tadpole p2.age // 19 p2.friends // ["oo", "cc", "yy", "tt"] p1.name // tt p1.age // 18 p1.friends // ["oo", "cc", "yy", "tt"] 複製程式碼
上面我們建立了一個新的變數p2
,將person
賦值給p2
,然後比較兩個變數
-- | 和原資料是否指向同一物件 | 第一層資料為基本資料型別 | 原資料中包含子物件 |
---|---|---|---|
賦值 | 是 | 改變會使原資料一同改變 | 改變會使原資料一同改變 |
淺拷貝 | 否 | 改變不會使原資料一同改變 | 改變會使原資料一同改變 |
深拷貝
瞭解完淺拷貝,相信小夥伴們對於深拷貝也應該瞭然於胸了
淺拷貝由於只是複製一層物件的屬性,當遇到有子物件的情況時,子物件就會互相影響。所以,深拷貝是對物件以及物件的所有子物件進行拷貝
實現方式就是遞迴呼叫淺拷貝
function deepCopy(source){ if(!source || typeof source !== 'object'){ throw new Error('error'); } var targetObj = source.constructor === Array ? [] : {}; for(var keys in source){ if(source.hasOwnProperty(keys)){ if(source[keys] && typeof source[keys] === 'object'){ targetObj[keys] = source[keys].constructor === Array ? [] : {}; targetObj[keys] = deepCopy(source[keys]); }else{ targetObj[keys] = source[keys]; } } } return targetObj; } var obj1 = { arr: [1, 2, 3], key: { id: 22 }, func: function() { console.log(123) } } var obj2 = deepCopy(obj1); obj1.arr.push(4); obj1.arr // [1, 2, 3, 4] obj2.arr // [1, 2, 3] obj1.key === obj2.key // false obj1.func === obj2.func // true 複製程式碼
對於深拷貝的物件,改變源物件不會對得到的物件有影響。只是在拷貝的過程中源物件的方法丟失了,這是因為在序列化JavaScript
物件時,所有函式和原型成員會被有意忽略
還有一種實現深拷貝的方式是利用JSON
物件中的parse
和stringify
,JOSN
物件中的stringify
可以把一個js
物件序列化為一個JSON
字串,parse
可以把JSON
字串反序列化為一個js
物件,通過這兩個方法,也可以實現物件的深複製
// 利用JSON序列化實現一個深拷貝 function deepCopy(source){ return JSON.parse(JSON.stringify(source)); } var o1 = { arr: [1, 2, 3], obj: { key: 'value' }, func: function(){ return 1; } }; var o2 = deepCopy(o1); console.log(o2); // => {arr: [1,2,3], obj: {key: 'value'}} 複製程式碼
實現拷貝的其他方式
淺拷貝
Array.prototype.slice() Array.prototype.concat() Object.assign ...
深拷貝
很多框架或者庫都提供了深拷貝的方式,比如jQuery
、lodash
函式庫等等,基本實現方式也就和我們前面介紹的大同小異
後記
根據需求的不同,比如有時候我們需要一個全新的物件,在修改它的時候不去影響到源物件,那麼這個時候我們就可能需要深拷貝;反之,淺拷貝就能實現我們的需求
只是,我們需要注意到一點,那就是因為實現深拷貝使用遞迴的方式,就增加了效能的消耗
相信在不斷使用的過程中,你一定會對它越來越熟悉
最後,推薦一波前端學習歷程,不定期分享一些前端問題和有意思的東西歡迎star
關注傳送門