在實際項目中翠储,我們往往需要對一個對象進(jìn)行拷貝绘雁,其目的可能是為了拷貝一個對象做其他處理,可能是為了擴(kuò)充一個對象使其擁有另一個對象的屬性援所,也可能是為了其他的目的
但是在 js 中庐舟,對象的拷貝分為深淺兩種,而不僅僅是將這個對象的屬性賦給另一個對象那么簡單住拭。同時對象的深淺拷貝也是很對前端面試中常見的一題挪略,所以下面就來詳細(xì)說說 js 中的 對象深淺拷貝,如果有誤滔岳,還請多多指正杠娱,共同進(jìn)步
值類型與引用類型
首先理解一下兩個概念:值類型和引用類型
- 值類型
簡單來說,值類型就是將一個變量賦值給另一個變量后谱煤,兩個變量完全獨立摊求,改變其中的一個并不會影響另一個
var a = 1;
var b = a; // b = 1
a = 2; // a = 2 b = 1
像上面的例子中,雖然后聲明的變量b賦予了a的值刘离,但是改變a的值室叉,b卻沒有改變
除了數(shù)值類型睹栖,與此類似的 js 中的值類型還有布爾值、字符串茧痕、null野来、undefined等
- 引用類型
引用類型剛好與值類型相反,原始的變量被改變后凿渊,被賦值的變量也會被改變
引用類型會在內(nèi)存中開辟一塊區(qū)域保存它的值梁只,而被賦予了這個值的原始變量本質(zhì)上是將指向了這塊內(nèi)存,而被賦值的另一個變量獲得的也只是這個指向而已
所以一旦內(nèi)存上的值改變埃脏,所有只想這塊內(nèi)存的變量的值都會被改變
var c = [1,2,3];
var d = c; // d = [1,2,3]
c[0] = 0; // c = [0,2,3] d = [0,2,3]
可能有的小伙伴會說搪锣,不對呀,下面這種情況 d 并沒有被改變
var c = [1,2,3];
var d = c; // d = [1,2,3]
c = [4,5,6]; // c = [4,5,6] d = [1,2,3]
其實并沒有不對彩掐,上面例子中的 c[0] 改變的是原內(nèi)存地址中存儲的值构舟,因為c、d指向相同堵幽,所有都被改變狗超;而后一個例子中,為 c 重新賦值朴下,相當(dāng)于是在內(nèi)存中重新開辟了一塊區(qū)域存儲新值努咐,改變了 c 原來的指向,但 d 的指向卻沒有改變殴胧,所以我們看到的值也就沒變
js 中常見引用類型有 數(shù)組渗稍、對象、函數(shù)
了解了值類型和引用類型团滥,下面我們開始進(jìn)入正題
對象的淺拷貝
對象的淺拷貝簡單竿屹,就是將一個變量賦給另一個變量
var obj1 = {
name: 'test name',
age: 18
}
var obj2 = obj1;
上面的例子中 obj2 經(jīng)過淺拷貝擁有了 obj1 的屬性
封裝淺拷貝方法
var easyCopy = function ( extendObj ) {
var newObj = extendObj.constructor === Array ? [] : {};
if (typeof extendObj != 'object') return;
for (var key in extendObj) {
if (extendObj.hasOwnProperty(key)) {
newObj[key] = extendObj[key];
}
}
return newObj
};
var obj2 = {
tall: 1.8,
weight: 75
}
var obj1 = easyCopy( obj2 );
console.log( obj1 );
淺拷貝存在的問題
我們知道引用類型的賦值其實是改變了變量的指向,那么如果在需要拷貝的對象中存在某個屬性的值是引用類型灸姊,如數(shù)組或子對象拱燃,那么淺拷貝后的原對象的屬性獲得的也只是這個指向
所以如果改變被拷貝對象的屬性值,那么原對象的相應(yīng)屬性也會跟著改變
var obj2 = {
names: ['test0', 'test1', 'test3']
}
obj1 = easyCopy( obj2 );
console.log( obj1, obj2 );
obj2.names[1] = 'test0';
console.log( obj1, obj2 );
// 打印結(jié)果為:obj1.name[1] 的值從原來的 'test1' 變成了 'test0'
日常項目中使用比較多的是淺拷貝力惯,但是如果某些情況下使用了淺拷貝碗誉,可能會產(chǎn)生一些極不容易發(fā)現(xiàn)的bug,所以這時候就需要用到深拷貝了
對象的深拷貝
深拷貝其實就是將對象中的數(shù)組夯膀、子對象進(jìn)行深度遞歸遍歷诗充,直到其不是引用類型位置再進(jìn)行復(fù)制,這樣即使改變了其中一個的值诱建,也不會影響到另一個
深拷貝的封裝
var deepCopy = function( extendObj ){
var str, newObj = extendObj.constructor === Array ? [] : {};
if(typeof extendObj !== 'object'){
return;
} else if(window.JSON){
str = JSON.stringify(extendObj);
newObj = JSON.parse(str);
} else {
for(var key in extendObj){
if (!extendObj.hasOwnProperty(key)) return;
newObj[key] = typeof extendObj[key] === 'object' ?
cloneObj(extendObj[key]) : extendObj[key];
}
}
return newObj;
};
var obj2 = {
names: ['test0', 'test1', 'test3']
}
var obj1 = deepCopy( obj2 );
console.log( obj1, obj2 );
obj2.names[1] = 'test0';
console.log( obj1, obj2 );
深拷貝的缺點
雖然深拷貝能夠避免淺拷貝出現(xiàn)的問題,但是卻會帶來性能上的問題碟绑,如果一個對象非常復(fù)雜或數(shù)據(jù)龐大俺猿,所消耗的性能將會是很可觀的
補(bǔ)充
** for … in**
for … in 可以用來遍歷任何一個對象茎匠,它會將該對象上的所有屬性全部遍歷出來,包括原型鏈上的屬性
由于可以遍歷出原型鏈上的屬性押袍,所以需要使用 hasOwnProperty 這個方法來判斷到底是不是這個對象自身的屬性
由于數(shù)組也是對象诵冒,for … in 也可以用來遍歷數(shù)組,但是 for … in 損耗性能較多谊惭,所以如果是遍歷數(shù)組的話最好使用 for 語句
遞歸調(diào)用
一個方法重復(fù)調(diào)用自身的情況叫做遞歸汽馋,但是需要注意的是,一定要有一個條件來結(jié)束遞歸圈盔,否則將會陷入無限的循環(huán)
var index = 1;
function fuckSelf() {
if (index < 100) {
index++;
fuckSelf();
}
}
fuckSelf();