在我不長(zhǎng)的開(kāi)發(fā)經(jīng)歷中毫痕,常常在js中遇到數(shù)組或?qū)ο罂截惖那闆r数尿,總是不能寫對(duì)赦肃。 于是決定好好梳理一遍相關(guān)的知識(shí)适室。
預(yù)備知識(shí): 引用類型
ECMAScript 變量可能包含兩種不同數(shù)據(jù)類型的值: 基本類型值和引用類型值 勿负。
當(dāng)從一個(gè)變量向另一個(gè)變量復(fù)制引用類型的值時(shí)馏艾,實(shí)際復(fù)制的是指針,指向存儲(chǔ)在堆中的同一個(gè)對(duì)象奴愉。因此改變其中一個(gè)變量琅摩,就會(huì)影響另一個(gè)變量。
——《JavaScript高級(jí)程序設(shè)計(jì)(第三版)》
數(shù)組Array和對(duì)象Object同屬于引用類型锭硼,因此在拷貝時(shí)需要額外的工作房资,而不能簡(jiǎn)單的按照 c=a , a=b, b=c的方式來(lái)賦值。
數(shù)組拷貝
數(shù)組的淺拷貝就是直接賦值
var a = [1,2,3];
var b = a; // b = [1,2,3]
b[0] = 10; // b = [10,2,3], a = [10,2,3]
引用類型傳遞的是地址檀头,因此修改數(shù)組b后數(shù)組a也會(huì)跟著改變轰异。
-
改進(jìn)辦法1 Array.splice( ) 方法
數(shù)組類型的splice( )方法本來(lái)時(shí)用于切割數(shù)組的,接收兩個(gè)參數(shù)暑始,起點(diǎn)與終點(diǎn)索引值搭独,將這個(gè)區(qū)間內(nèi)的數(shù)組元素<b>淺拷貝</b>到一個(gè)新數(shù)組并返回,原數(shù)組保持不變蒋荚。 若不傳遞任何參數(shù)給splice( )方法戳稽,則會(huì)默認(rèn)<b>淺拷貝</b>數(shù)組中的每一個(gè)元素到一個(gè)新數(shù)組并返回。由于是復(fù)制的數(shù)組元素而非數(shù)組變量本身期升,因此傳遞的是數(shù)組中每一個(gè)元素的值或地址惊奇。
var a = [1,2,3]; var b = a.splic(); //b = [1,2,3] b[0] = 10; //b = [10,2,3], a = [1,2,3]
這個(gè)辦法具有局限性,因?yàn)槠浣K究是淺拷貝播赁,如果數(shù)組中的元素含有引用類型颂郎,那么改變新數(shù)組中它的值仍會(huì)反應(yīng)到原數(shù)組中:
var obj = { prop: 1}; var a = [obj,1,2,3]; // a = [{prop:1},1,2,3] var b = a.splice(); // b = [{prop:1},1,2,3] b[0].prop = 20; b[1] = 10; // b = [{prop:20},10,2,3] // a = [{prop:20},1,2,3]
另外,當(dāng)被拷貝數(shù)組中包含數(shù)組元素時(shí)(類似二維數(shù)組)容为,使用該方法復(fù)制將返回空數(shù)組乓序。
?
-
改進(jìn)辦法2 Array . concat( ) 方法
數(shù)組類型的concat( ) 方法主要用于連接多個(gè)數(shù)組返回一個(gè)新數(shù)組,若不帶參數(shù)則返回一個(gè)原數(shù)組的拷貝坎背,遺憾的是這個(gè)拷貝仍為淺拷貝替劈。與splice( ) 方法表現(xiàn)稍不同的是,若被復(fù)制數(shù)組中包含數(shù)組元素得滤,它仍將正常工作陨献,返回淺拷貝。
var a = [1,2,3]; var b = [4,5]; var c = [a,b]; //c = [[1,2,3],[4,5]]; var d = c.concat(); //d = [[1,2,3],[4,5]]; var e = c.splice(); //e = []; d[1][1] = 10; //d = [[1,2,3],[4,10]]; //c = [[1,2,3],[4,10]]
對(duì)象拷貝
直接賦值仍然是淺拷貝懂更,要做到對(duì)象的深拷貝眨业,需要將屬性一一拷貝到新對(duì)象上:
var a = {prop1:1, prop2:2, prop3:3};
//聲明一個(gè)函數(shù)執(zhí)行深拷貝
function deepCopy(obj){ //接收待拷貝的對(duì)象最為參數(shù)
var copy = {}; //聲明新副本對(duì)象
for(prop in obj){ // for in語(yǔ)句遍歷對(duì)象屬性
copy[prop] = obj[prop] // for in 語(yǔ)句中遍歷結(jié)果為屬性的字符串名急膀,因此需以[]方式訪問(wèn)
}
return copy; //返回新對(duì)象
}
var b = deepCopy(a); // b = {prop1:1,prop2:2,prop3:3}
b.prop1 = 10; // b = {prop1:10,prop2:2,prop3:3}
// a = {prop1:1, prop2:2,prop3:3}
上述這總解決辦法只能拷貝所有屬性均為基本類型值的對(duì)象,如果拷貝的對(duì)象其屬性中包含了Object或Array等引用類型龄捡,仍然會(huì)出現(xiàn)指向同一個(gè)地址的情況(雖然對(duì)象本身沒(méi)有指向同一個(gè)地址了卓嫂,但是這些引用類型屬性仍然指向了同一個(gè)地址)∑钢常考慮到對(duì)象屬性層層嵌套的情況并不少見(jiàn)晨雳,因此deepCopy( )函數(shù)仍然需要優(yōu)化:
function deepCopy(obj) {
let copy = {};
for (prop in obj) {
if (obj[prop] instanceof Object) { //先判斷該屬性是否也是一個(gè)Object對(duì)象
copy[prop] = deepCopy(obj[prop]); //若為對(duì)象則對(duì)該屬性再遞歸調(diào)用deepCopy()賦值給新對(duì)象的該屬性
} else {
copy[prop] = obj[prop] //若該屬性不為Object類型,則直接賦值即可
}
}
return copy; //返回拷貝出的新對(duì)象
}
到這里就斤,上述這些方法函數(shù)已經(jīng)完全能夠解決我遇到的所有情景了悍募。但其實(shí)這個(gè)問(wèn)題還可以推得很深推得很細(xì),下面就來(lái)做點(diǎn)"無(wú)用功", 再往前邁進(jìn)一步洋机。
混合拷貝
如前所述,數(shù)組的拷貝方法在數(shù)組元素為Object或者Array類型時(shí)仍然有無(wú)法做到副本與原變量獨(dú)立洋魂。 再加上JavaScript “獨(dú)特”的語(yǔ)言特性绷旗,數(shù)組與對(duì)象可以做到 “你中有我,我中有你” 的混合狀態(tài)副砍,因此可以再次改進(jìn)deepCopy( ) 衔肢,讓它能實(shí)現(xiàn)混合情景下的深拷貝。
function deepCopy(origin){
let copy = null; //聲明副本
if(origin.constructor === Array){ //確定副本的類型
copy = [];
}else if(origin.constructor === Object){
copy = {};
}
for(i in origin){ //遍歷原變量的枚舉屬性
if(origin[i] instanceof Object){ //Array和Object類型其原型鏈上均含Object構(gòu)造函數(shù)
copy[i] = deepCopy(origin[i])
}else{
copy[i] = origin[i];
}
}
return copy;
}
現(xiàn)在豁翎,deepCopy( ) 可以輕松應(yīng)對(duì)任何包含 數(shù)組或者對(duì)象的深拷貝了角骤。
雖然 MDN不推薦用for in遍歷數(shù)組,因?yàn)槠浞祷氐氖撬饕龜?shù)字的字符串值心剥,并且在不同的ECMA-262標(biāo)準(zhǔn)中返回的屬性或索引值順序是不同的邦尊,不一定按照對(duì)象構(gòu)造時(shí)的屬性順序或數(shù)組索引值的大小順序。但是這里拷貝數(shù)組則不用擔(dān)心优烧,雖然不按順序返回索引值蝉揍,但最終還是遍歷了整個(gè)數(shù)組。 這樣對(duì)象分支和數(shù)組分支就合并在了同一個(gè)for in語(yǔ)句中畦娄,代碼變得更為簡(jiǎn)潔又沾。
參考網(wǎng)站: MDN-JavaScript