拷貝即復(fù)制。
本文只討論js中復(fù)雜數(shù)據(jù)類型的復(fù)制問題(Object,Array等),不討論基本數(shù)據(jù)類型(null,undefined,string,number和boolean)萍鲸,這些類型的值本身就存儲在棧內(nèi)存中(string類型的實(shí)際值還是存儲在堆內(nèi)存中的,但是js把string當(dāng)做基本類型來處理 )炮障,不存在引用值的情況槐壳。
淺復(fù)制和深復(fù)制都可在已有對象的基礎(chǔ)上再生一份,但對象的實(shí)例存儲在堆內(nèi)存中然后通過一個引用值去操作對象仔役,由此復(fù)制的時候就存在兩種情況了:復(fù)制引用和復(fù)制實(shí)例掷伙,這也是淺復(fù)制和深復(fù)制的區(qū)別所在。
- 淺復(fù)制:淺復(fù)制是復(fù)制引用又兵,復(fù)制后的引用都是指向同一個對象的實(shí)例任柜,彼此之間的操作會互相影響。
- 深復(fù)制:深復(fù)制不是簡單的復(fù)制引用沛厨,而是在堆中重新分配內(nèi)存宙地,并且把源對象實(shí)例的所有屬性都進(jìn)行新建復(fù)制,以保證深復(fù)制的對象的引用不包含任何原有對象或?qū)ο笊系娜魏螌ο竽嫫ぃ瑥?fù)制后的對象與原來的對象是完全隔離的宅粥。
淺復(fù)制:
淺復(fù)制就是簡單的引用復(fù)制,示例如下:
var src = {
name:"src";
}
var target = src; //復(fù)制一份src對象的引用
target.name = "target"; //改變復(fù)制后對象的name值
console.log(src.name); //輸出target页屠,即原對象也發(fā)生了改變粹胯,證明復(fù)制的是指向堆內(nèi)存內(nèi)對象位置的引用
Array的slice()和concat()方法都會返回一個新的數(shù)組實(shí)例蓖柔,這與直接引用復(fù)制不同,但數(shù)組中的對象元素(Object风纠、Array等)仍只是復(fù)制了引用况鸣,并不是真正的深復(fù)制。
var array = [1, [1, 2, 3], { name: "array" }];
var array_concat = array.concat();//copy array返回新數(shù)組
var array_slice = array.slice(0);//copy array返回新數(shù)組
array_concat[0] = 3//改變基本類型值不會相互影響
array_concat[1][0] = 5; //改變array_concat中數(shù)組元素的值
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' } ]
深復(fù)制:
由深復(fù)制的定義來看竹观,深復(fù)制要求如果源對象存在對象屬性镐捧,那么需要進(jìn)行遞歸復(fù)制,從而保證復(fù)制的對象與源對象完全隔離臭增。然而還有一種處在淺復(fù)制和深復(fù)制范圍之間的復(fù)制——jQuery的extend方法在deep參數(shù)為false時所謂的“淺復(fù)制”懂酱。這種復(fù)制只進(jìn)行一個層級的復(fù)制:即如果源對象中存在對象屬性,那么復(fù)制的對象上也會引用相同的對象誊抛。這不符合深復(fù)制的要求列牺,但又比簡單的復(fù)制引用的復(fù)制程度有了加深。
- 遞歸實(shí)現(xiàn)
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對象瞎领,借助這兩個方法可以實(shí)現(xiàn)對象的深復(fù)制。
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 } }
從代碼輸出看出随夸,復(fù)制后的deepCopyObj與obj1完全隔離九默,二者不會相互影響。
這個方法可滿足基本的深復(fù)制需求宾毒,而且能夠處理JSON格式能表示的所有數(shù)據(jù)類型(即 Number, String, Boolean, Array, 扁平對象)驼修,但對于正則表達(dá)式類型、函數(shù)類型等無法進(jìn)行深復(fù)制(而且會直接丟失相應(yīng)的值)诈铛,同時如果對象中存在循環(huán)引用的情況也無法正確處理乙各。
- jQuery庫中的extend復(fù)制方法
在 jQuery 中可通過添加一個參數(shù)來實(shí)現(xiàn)遞歸extend。調(diào)用$.extend(true, {}, ...)就可以實(shí)現(xiàn)深復(fù)制幢竹。jQuery 無法正確深復(fù)制 JSON 對象以外的對象.
var x = {
a: 1,
b: { f: { g: 1 } },
c: [ 1, 2, 3 ]
};
var y = $.extend({}, x), //淺復(fù)制
z = $.extend(true, {}, x); //深復(fù)制
y.b.f === x.b.f // true
z.b.f === x.b.f // false
- lodash庫的.clone() 或 .cloneDeep()方法——擁抱未來的庫
lodash中關(guān)于復(fù)制的方法有兩個觅丰,分別是.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的深復(fù)制!M善蟆咬荷!
// 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]
注意:如果對象比較大,層級比較多轻掩,深復(fù)制會帶來性能上的問題幸乒,在遇到需要采用深復(fù)制的場景時可以考慮看看有沒有其他替代的方案,在實(shí)際應(yīng)用場景中也是淺復(fù)制更為常用唇牧。
參考資料: