在工作中樱调,有時候想操作一個對象a但是又不想改變對象a的數(shù)據(jù)祸挪,于是會使對象b=對象a屡穗,然后去操作對象b澳化。等操作完以后發(fā)現(xiàn)欠啤,咦郊艘,怎么完操作的是對象b僚祷,沒動對象a尺锚,但是對象a的數(shù)據(jù)也被改了酷师?讶凉??
為什么要這樣山孔?對象a做錯了什么要跟對象b一起承受同樣的蹂躪懂讯?—— 這還得從數(shù)據(jù)類型說起了。
為什么想起寫這個台颠,因為 我又踩了這個坑 理清基礎(chǔ)知識很重要褐望。
js數(shù)據(jù)類型
js有6中基本數(shù)據(jù)類型:String,Number串前,Boolean瘫里,Null,undefined和es6新增的symbol荡碾,一種引用數(shù)據(jù)類型:Object谨读。
需要注意的是,new出來的布爾坛吁、數(shù)字劳殖、字符串也屬于引用類型铐尚。
- 示例1:
let num = 1;
let numNew = new Number(1);
console.log(typeof num); // number
console.log(typeof numNew); // object
let flag = true;
let flagNew = new Boolean(false);
console.log(typeof flag); // boolean
console.log(typeof flagNew); // object
let str = 'a';
let strNew = new String('b');
console.log(typeof str); // string
console.log(typeof strNew); // object
那么,基本關(guān)系搞清楚了哆姻,我們來看一下下面的例子:
- 示例2:
let strA = 'a';
let strB = strA;
strB = 'b';
console.log(strA); // a
console.log(strB); // b
let objA = {'a':1,'b':2};
let objB = objA;
objB.a = 3;
console.log(objA); // {'a':3,'b':2}
console.log(objB); // {'a':3,'b':2}
為什么更改了strB之后strA沒變而更改了objB之后objA卻變了呢宣增?這就是傳值與傳址的區(qū)別。
因為在js中 基本類型按值引用矛缨,引用類型按地址引用爹脾。
什么是“按地址引用”?
上面的例子中箕昭,objA是指向內(nèi)存中{'a':1,'b':2}這個數(shù)據(jù)的指針灵妨,而此時let objB = objA
,objB也是指向{'a':1,'b':2}這個數(shù)據(jù)(相當于新建了一個不同名的“指針”)盟广,objA和objB指向內(nèi)存中的同一個數(shù)據(jù),所以此時objB.a = 3
改變的是objB指向的內(nèi)存數(shù)據(jù)的值瓮钥,而同時objA也指向此數(shù)據(jù)筋量,故而打印objA與打印objB的結(jié)果是一樣的。
注:內(nèi)存又涉及堆和棧碉熄,本文暫時不詳述桨武,感興趣的胖友可以自行了解一下
那么再來看一下,下面例子中應(yīng)該打印什么呢锈津?
- 示例3:
let objA = {'a':1,'b':2};
let objB = {'a':1,'b':2};
objB.a = 3;
console.log(objA); // 呀酸?
console.log(objB); // ?
盡管objA和objB指向的數(shù)據(jù)一樣琼梆,但是在內(nèi)存中性誉,這是兩個不同的數(shù)據(jù),所以改變objB的時候不會改變objA茎杂,objA和objB的打印為:
console.log(objA); // {'a':1,'b':2}
console.log(objB); // {'a':3,'b':2}
深拷貝和淺拷貝
對引用數(shù)據(jù)類型错览,直接用等號=
賦值,實際上為復(fù)制指向某個對象的指針煌往。正如文章開頭提到的例子和示例2一樣倾哺,此時改變對象b,那對象a也發(fā)生了改變刽脖,因為他們是兩個不同名的指針指向內(nèi)存中的同一數(shù)據(jù)羞海。
而淺拷貝,是拷貝引用數(shù)據(jù)的第一層曲管,后面深層次的引用類型仍然是拷貝引用却邓,比如使用Object.assign
、slice
院水、Object.create
申尤、concat
和擴展運算符等癌幕,實現(xiàn)的都是淺拷貝。
那么昧穿,怎么做到改變對象b的數(shù)據(jù)而對象a的數(shù)據(jù)不變呢勺远?
實際工作中總不能像示例3數(shù)據(jù)一開始就是固定的,所以此時时鸵,你需要進行深拷貝胶逢。將對象a的每個基本類型的數(shù)據(jù)都遍歷一遍,依次的賦值給對象b的對應(yīng)字段饰潜。避免產(chǎn)生因為地址引用帶來的問題初坠。
手動實現(xiàn)深拷貝:
這個函數(shù)只能基本滿足生產(chǎn)需求,如果參數(shù)是Dom對象彭雾、new Object ()的話,就會產(chǎn)生功能性錯誤
function deepCopy(obj1) {
// 創(chuàng)建對應(yīng)的空對象或空數(shù)組
let obj2 = Array.isArray(obj1) ? [] : {};
if (obj1 && typeof obj1 === "object") {
for (let i in obj1) {
// 判斷是否是自身的屬性:因為for-in會遍歷原型鏈上的屬性碟刺,
// 所以需要判斷屬性是否在原型鏈上,不是原型鏈才拷貝
if (obj1.hasOwnProperty(i)) {
// 如果子屬性為引用數(shù)據(jù)類型薯酝,遞歸復(fù)制
if (obj1[i] && typeof obj1[i] === "object") {
obj2[i] = deepCopy(obj1[i]);
} else {
// 如果是基本數(shù)據(jù)類型半沽,直接賦值
obj2[i] = obj1[i];
}
}
}
}
return obj2;
}
生產(chǎn)環(huán)境中的話還是推薦用lodash的_.cloneDeep
,目前對深拷貝各種數(shù)據(jù)支持得最好的了吴菠。
參考文章:
1者填、誰說前端就不需要學習數(shù)據(jù)結(jié)構(gòu)了?來我們淺談一下js的數(shù)據(jù)結(jié)構(gòu)
2做葵、js 值引用和地址引用