首先,值的拷貝,通常有三種方式,由於基本型別與引用型別在記憶體中儲存位置和儲存方式的不同,導致了以下三種概念的衍生:
- = 賦值:多個指標指向的是同一個堆中的地址,所以相互有影響;
- 淺拷貝:在堆中重新建立記憶體,拷貝前後基本資料型別不受影響,但只拷貝一層,無法拷貝子物件;所以改變淺拷貝得到的物件中的引用型別時,原始資料會受到影響;例如陣列的concat和slice方法;
- 深拷貝:對子物件也可以拷貝,拷貝前後兩個物件互不影響,是對物件以及物件的所有子物件進行拷貝;思路就是遞迴呼叫淺拷貝的邏輯,把所有屬於物件的屬性型別都遍歷賦給另一個物件即可。完全的拷貝一個物件,即使巢狀了物件,兩者也相互分離,修改一個物件的屬性,也不會影響另一個。
看下陣列結構的拷貝:
var new_arr = JSON.parse( JSON.stringify(arr) );複製程式碼
但此法無法拷貝函式。concat、slice、JSON.stringify 都算是技巧類,可以根據實際情況適當使用。
初步實現一個淺拷貝:
在看開源專案的過程中,經常會看到類似如下的原始碼。for...in
迴圈物件的所有列舉屬性,然後再使用hasOwnProperty()
方法來忽略繼承屬性。
const shallowClone = (obj) => {
if (typeof obj !== 'object') return
let newObj = obj instanceof Array ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}複製程式碼
初步實現一個深拷貝:
const deepClone = (obj) => {
if (typeof obj !== 'object') return
let newObj = obj instanceof Array ? [] : {}
for (let key in obj) {
if (typeof obj[key] === 'object') {
newObj[key] = deepClone(obj[key])
} else {
newObj[key] = obj[key]
}
}
return newObj
}複製程式碼
深拷貝會完全的克隆一個新物件,但因為使用遞迴,效能會不如淺拷貝,在開發中,還是要根據實際情況進行選擇。
第三方庫的實現
Underscore _.clone()
實際上是一種淺複製 (shallow-copy),所有巢狀的物件和陣列都是直接複製引用而並沒有進行深複製。原始碼:
// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};複製程式碼
jQuery $.extend()
var x = {
a: 1,
b: { f: { g: 1 } },
c: [ 1, 2, 3 ]
};
var y = $.extend({}, x), //shallow copy
z = $.extend(true, {}, x); //deep copy
y.b.f === x.b.f // true
z.b.f === x.b.f // false複製程式碼
lodash _.clone() / _.cloneDeep()
在lodash中關於複製的方法有兩個,分別是_.clone()
和_.cloneDeep()
。其中_.clone(obj, true)
等價於_.cloneDeep(obj)
。
jQuery 無法正確深複製 JSON 物件以外的物件,而 lodash 花了大量的程式碼來實現 ES6 引入的大量新的標準物件。lodash 針對存在環的物件的處理也是非常出色的。因此相較而言,lodash 在深複製上的行為反饋比前兩個庫好很多。
參考源:
https://juejin.im/post/59ac1c4ef265da248e75892b
https://segmentfault.com/a/1190000002801042
https://github.com/mqyqingfeng/Blog/issues/32