在實(shí)際開(kāi)發(fā)當(dāng)中,我們經(jīng)常會(huì)遇到要對(duì)對(duì)象進(jìn)行深拷貝的情況。而且深拷貝這個(gè)問(wèn)題在面試過(guò)程中也經(jīng)常會(huì)遇到,下面就對(duì)本人在學(xué)習(xí)過(guò)程中的收獲,做以簡(jiǎn)單的總結(jié)。
什么是淺拷貝,什么是深拷貝?
什么是淺拷貝
關(guān)于淺拷貝的概念,我在網(wǎng)上看到一種說(shuō)法,直接上代碼。
var person = {name: "Jason", age: 18, car: {brand: "Ferrari", type: "430"}};
var person1 = person; //他們認(rèn)為這是淺拷貝
但是我個(gè)人認(rèn)為,上面這個(gè)根本不涉及拷貝,只是一個(gè)簡(jiǎn)單的引用賦值。以我的理解,淺拷貝應(yīng)該是不考慮對(duì)象的引用類(lèi)型的屬性,只對(duì)當(dāng)前對(duì)象的所有成員進(jìn)行拷貝,代碼如下:
function copy(obj){
var objCopy = {};
for(var key in obj){
objCopy[key] = obj[key];
}
return objCopy;
}
var person = {name: "Jason", age: 18, car: {brand: "Ferrari", type: "430"}};
var personCopy = copy(person);
上面這段代碼中,person對(duì)象擁有兩個(gè)基本類(lèi)型的屬性name和age,一個(gè)引用類(lèi)型的屬性car,當(dāng)使用如上方法進(jìn)行拷貝的時(shí)候,name和age屬性會(huì)被正常的拷貝,但是car屬性,只會(huì)進(jìn)行引用的拷貝,這樣會(huì)導(dǎo)致拷貝出來(lái)的對(duì)象personCopy和person會(huì)共用一個(gè)car對(duì)象。這樣就是所謂的淺拷貝。
什么是深拷貝
深拷貝的就是在拷貝的時(shí)候,需要將當(dāng)前要拷貝的對(duì)象內(nèi)的所有引用類(lèi)型的屬性進(jìn)行完整的拷貝,也就是說(shuō)拷貝出來(lái)的對(duì)象和原對(duì)象之間沒(méi)有任何數(shù)據(jù)是共享的,所有的東西都是自己獨(dú)占的一份。
如何實(shí)現(xiàn)深拷貝
實(shí)現(xiàn)深拷貝需要考慮的問(wèn)題
實(shí)現(xiàn)深拷貝需要考慮如下幾個(gè)因素:
·傳入的對(duì)象是使用對(duì)象字面量{}創(chuàng)建的對(duì)象還是由構(gòu)造函數(shù)生成的對(duì)象
·如果對(duì)象是由構(gòu)造函數(shù)創(chuàng)建出來(lái)的,那么是否要拷貝原型鏈上的屬性
·如果要拷貝原型鏈上的屬性,那么如果原型鏈上存在多個(gè)同名的屬性,保留哪個(gè)
·處理循環(huán)引用的問(wèn)題
第三方庫(kù)實(shí)現(xiàn)深拷貝
jQuery的$.extend()
我們可以通過(guò)$.extend()方法來(lái)完成深復(fù)制。值得慶幸的是,我們?cè)趈Query中可以通過(guò)添加一個(gè)參數(shù)來(lái)實(shí)現(xiàn)遞歸extend。調(diào)用$.extend(true, {}, ...)就可以實(shí)現(xiàn)深復(fù)制,參考下面的例子:
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
但是jQuery的這個(gè)`$.extend()`方法,有弊端,什么弊端呢?我們看下面的例子:
var objA = {};
var objB = {};
objA.b = objB;
objB.a = objA;
$.extend(true,{},a);
//這個(gè)時(shí)候就出現(xiàn)異常了
//Uncaught RangeError: Maximum call stack size exceeded(…)
也就是說(shuō),jQuery中的`$.extend()`并沒(méi)有處理循環(huán)引用的問(wèn)題。
使用JSON對(duì)象實(shí)現(xiàn)深拷貝
然而使用這種方法會(huì)有一些隱藏的坑,它能正確處理的對(duì)象只有 Number, String, Boolean, Array, 扁平對(duì)象,即那些能夠被 json 直接表示的數(shù)據(jù)結(jié)構(gòu)。
自己造輪子
下面我們給出一個(gè)簡(jiǎn)單的解決方案,當(dāng)然這個(gè)方案是參考別人的方式來(lái)實(shí)現(xiàn)的。希望對(duì)大家有用。
var clone = (function() {
//這個(gè)方法用來(lái)獲取對(duì)象的類(lèi)型 返回值為字符串類(lèi)型 "Object RegExp Date Array..."
var classof = function(o) {
if (o === null) {
return "null";
}
if (o === undefined) {
return "undefined";
}
// 這里的Object.prototype.toString很可能用的就是Object.prototype.constructor.name
// 這里使用Object.prototype.toString來(lái)生成類(lèi)型字符串
var className = Object.prototype.toString.call(o).slice(8, -1);
return className;
};
//這里這個(gè)變量我們用來(lái)存儲(chǔ)已經(jīng)保存過(guò)的屬性,目的在于處理循環(huán)引用的問(wèn)題
var references = null;
//遇到不同類(lèi)型的對(duì)象的處理方式
var handlers = {
//正則表達(dá)式的處理
'RegExp': function(reg) {
var flags = '';
flags += reg.global ? 'g' : '';
flags += reg.multiline ? 'm' : '';
flags += reg.ignoreCase ? 'i' : '';
return new RegExp(reg.source, flags);
},
//時(shí)間對(duì)象處理
'Date': function(date) {
return new Date(+date);
},
//數(shù)組處理 第二個(gè)參數(shù)為是否做淺拷貝
'Array': function(arr, shallow) {
var newArr = [],
i;
for (i = 0; i < arr.length; i++) {
if (shallow) {
newArr[i] = arr[i];
} else {
//這里我們通過(guò)reference數(shù)組來(lái)處理循環(huán)引用問(wèn)題
if (references.indexOf(arr[i]) !== -1) {
continue;
}
var handler = handlers[classof(arr[i])];
if (handler) {
references.push(arr[i]);
newArr[i] = handler(arr[i], false);
} else {
newArr[i] = arr[i];
}
}
}
return newArr;
},
//正常對(duì)象的處理 第二個(gè)參數(shù)為是否做淺拷貝
'Object': function(obj, shallow) {
var newObj = {}, prop, handler;
for (prop in obj) {
//關(guān)于原型中屬性的處理太過(guò)復(fù)雜,我們這里暫時(shí)不做處理
//所以只對(duì)對(duì)象本身的屬性做拷貝
if (obj.hasOwnProperty(prop)) {
if (shallow) {
newObj[prop] = obj[prop];
} else {
//這里還是處理循環(huán)引用的問(wèn)題
if (references.indexOf(obj[prop]) !== -1) {
continue;
}
handler = handlers[classof(obj[prop])];
//如果沒(méi)有對(duì)應(yīng)的處理方式,那么就直接復(fù)制
if (handler) {
references.push(obj[prop]);
newObj[prop] = handler(obj[prop], false);
} else {
newObj[prop] = obj[prop];
}
}
}
}
return newObj;
}
};
return function(obj, shallow) {
//首先重置我們用來(lái)處理循環(huán)引用的這個(gè)變量
references = [];
//我們默認(rèn)處理為淺拷貝
shallow = shallow === undefined ? true : false;
var handler = handlers[classof(obj)];
return handler ? handler(obj, shallow) : obj;
};
}());
(function() {
//下面是一些測(cè)試代碼
var date = new Date();
var reg = /hello word/gi;
var obj = {
prop: 'this ia a string',
arr: [1, 2, 3],
o: {
wow: 'aha'
}
};
var refer1 = {
arr: [1, 2, 3]
};
var refer2 = {
refer: refer1
};
refer1.refer = refer2;
var cloneDate = clone(date, false);
var cloneReg = clone(reg, false);
var cloneObj = clone(obj, false);
alert((date !== cloneDate) && (date.valueOf() === cloneDate.valueOf()));
alert((cloneReg !== reg) && (reg.toString() === cloneReg.toString()));
alert((obj !== cloneObj) && (obj.arr !== cloneObj.arr) && (obj.o !== cloneObj.o) && (JSON.stringify(obj) === JSON.stringify(cloneObj)));
clone(refer2, false);
alert("I'm not dead yet!");
// Output:
// true
// true
// true
// I'm not dead yet!
}());
猜你喜歡:如何在JavaScript中獲取當(dāng)前日期?
JavaScript中如何搜索數(shù)組元素?
傳智播客web前端培訓(xùn)課程