拷貝即復制。
本文只討論js中復雜數據類型的復制問題(Object,Array等),不討論基本數據類型(null,undefined,string,number和boolean),這些類型的值本身就存儲在棧內存中(string類型的實際值還是存儲在堆內存中的,但是js把string當做基本類型來處理 ),不存在引用值的情況。
淺復制和深復制都可在已有對象的基礎上再生一份,但對象的實例存儲在堆內存中然后通過一個引用值去操作對象,由此復制的時候就存在兩種情況了:復制引用和復制實例,這也是淺復制和深復制的區別所在。
- 淺復制:淺復制是復制引用,復制后的引用都是指向同一個對象的實例,彼此之間的操作會互相影響。
- 深復制:深復制不是簡單的復制引用,而是在堆中重新分配內存,并且把源對象實例的所有屬性都進行新建復制,以保證深復制的對象的引用不包含任何原有對象或對象上的任何對象,復制后的對象與原來的對象是完全隔離的。
淺復制:
淺復制就是簡單的引用復制,示例如下:
var src = {
name:"src";
}
var target = src; //復制一份src對象的引用
target.name = "target"; //改變復制后對象的name值
console.log(src.name); //輸出target,即原對象也發生了改變,證明復制的是指向堆內存內對象位置的引用
Array的slice()和concat()方法都會返回一個新的數組實例,這與直接引用復制不同,但數組中的對象元素(Object、Array等)仍只是復制了引用,并不是真正的深復制。
var array = [1, [1, 2, 3], { name: "array" }];
var array_concat = array.concat();//copy array返回新數組
var array_slice = array.slice(0);//copy array返回新數組
array_concat[0] = 3//改變基本類型值不會相互影響
array_concat[1][0] = 5; //改變array_concat中數組元素的值
array_slice[0] = 4//改變基本類型值不會相互影響
array_slice[2].name = "array_slice"; //改變引用類型值會相互影響
console.log(array[1]); //[5,2,3]//改變引用類型值會相互影響
console.log(array_slice[1]); //[5,2,3]//改變引用類型值會相互影響
console.log(array[2].name); //array_slice
console.log(array_concat[2].name); //array_slice
console.log(array)//[ 1, [ 5, 2, 3 ], { name: 'array_slice' } ]
console.log(array_concat)//[ 3, [ 5, 2, 3 ], { name: 'array_slice' } ]
console.log(array_slice)//[ 4, [ 5, 2, 3 ], { name: 'array_slice' } ]
深復制:
由深復制的定義來看,深復制要求如果源對象存在對象屬性,那么需要進行遞歸復制,從而保證復制的對象與源對象完全隔離。然而還有一種處在淺復制和深復制范圍之間的復制——jQuery的extend方法在deep參數為false時所謂的“淺復制”。這種復制只進行一個層級的復制:即如果源對象中存在對象屬性,那么復制的對象上也會引用相同的對象。這不符合深復制的要求,但又比簡單的復制引用的復制程度有了加深。
- 遞歸實現
function deepCopy(oldObj, newObj) {
var newObj = newObj || {}
for (var i in oldObj) {
if (oldObj[i] instanceof Object) {
if (oldObj[i].constructor === Array) {
newObj[i] = []
} else {
newObj[i] = {}
}
deepCopy(oldObj[i], newObj[i])
} else {
newObj[i] = oldObj[i]
}
}
return newObj
}
var obj1 = {
country: 'China',
city: ['Beijing,Shanghai,Nanjing'],
age: 16,
friends: {
name: 'dot',
sex: 'female',
age: null
}
}
var obj2 = {
name: 'dolby',
fav: 'food'
}
var obj3 = null
console.log(deepCopy(obj1, obj2))
//{ name: 'dolby',
// fav: 'food',
// country: 'China',
// city: [ 'Beijing,Shanghai,Nanjing' ],
// age: 16,
// friends: { name: 'dot', sex: 'female', age: null } }
console.log(deepCopy(obj1, obj3))
//{ country: 'China',
// city: [ 'Beijing,Shanghai,Nanjing' ],
// age: 16,
// friends: { name: 'dot', sex: 'female', age: null } }
- JSON對象的parse()和stringify()方法
stringify()方法將JS對象序列化成JSON字符串,parse()方法將JSON字符串反序列化成JS對象,借助這兩個方法可以實現對象的深復制。
var obj1 = {
country: 'China',
city: ['Beijing', 'Shanghai', 'Nanjing'],
age: 16,
friends: {
name: 'dot',
sex: 'female',
age: null
}
}
var deepCopyObj = JSON.parse(JSON.stringify(obj1))
console.log(deepCopyObj)
//{ country: 'China',
// city: [ 'Beijing', 'Shanghai', 'Nanjing' ],
// age: 16,
// friends: { name: 'dot', sex: 'female', age: null } }
deepCopyObj.age = 0
deepCopyObj.city[1] = 'tianjin'
console.log(deepCopyObj)
//{ country: 'China',
// city: [ 'Beijing', 'tianjin', 'Nanjing' ],
// age: 0,
// friends: { name: 'dot', sex: 'female', age: null }
console.log(obj1)
//{ country: 'China',
// city: [ 'Beijing', 'Shanghai', 'Nanjing' ],
// age: 16,
// friends: { name: 'dot', sex: 'female', age: null } }
從代碼輸出看出,復制后的deepCopyObj與obj1完全隔離,二者不會相互影響。
這個方法可滿足基本的深復制需求,而且能夠處理JSON格式能表示的所有數據類型(即 Number, String, Boolean, Array, 扁平對象),但對于正則表達式類型、函數類型等無法進行深復制(而且會直接丟失相應的值),同時如果對象中存在循環引用的情況也無法正確處理。
- jQuery庫中的extend復制方法
在 jQuery 中可通過添加一個參數來實現遞歸extend。調用$.extend(true, {}, ...)就可以實現深復制。jQuery 無法正確深復制 JSON 對象以外的對象.
var x = {
a: 1,
b: { f: { g: 1 } },
c: [ 1, 2, 3 ]
};
var y = $.extend({}, x), //淺復制
z = $.extend(true, {}, x); //深復制
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)。
var $ = require("jquery"),
_ = require("lodash");
var arr = new Int16Array(5),
obj = { a: arr },
obj2;
arr[0] = 5;
arr[1] = 6;
// 1. jQuery
obj2 = $.extend(true, {}, obj);
console.log(obj2.a); // [5, 6, 0, 0, 0]
Object.prototype.toString.call(obj2); // [object Int16Array]
obj2.a[0] = 100;
console.log(obj); // [100, 6, 0, 0, 0]
//此處jQuery不能正確處理Int16Array的深復制!!!
// 2. lodash
obj2 = _.cloneDeep(obj);
console.log(obj2.a); // [5, 6, 0, 0, 0]
Object.prototype.toString.call(arr2); // [object Int16Array]
obj2.a[0] = 100;
console.log(obj); // [5, 6, 0, 0, 0]
注意:如果對象比較大,層級比較多,深復制會帶來性能上的問題,在遇到需要采用深復制的場景時可以考慮看看有沒有其他替代的方案,在實際應用場景中也是淺復制更為常用。
參考資料: