Javascript 淺拷貝與深拷貝

在瞭解JS的淺拷貝與深拷貝以前,咱們須要先知道什麼是值傳遞與引用傳遞。程序員

在JS中,基本類型值的拷貝是按值傳遞的,而引用類型值的拷貝則是按引用傳遞的。經過值傳遞的變量間不會有任何牽連,互相獨立;可是引用傳遞拷貝的變量間則會相互影響,修改其中任何一方所引用的對象的值都會在另外一方中體現,之因此會有這樣的表現和JS的內存機制有關。數組

JS的內存也分爲堆和棧,可是注意這裏的堆棧和數據結構的堆棧是不一樣的概念。數據結構

棧:由系統自動分配,自動回收,效率高,但容量小函數

堆:由程序員手動分配內存,而且手動銷燬(高級語言如JS中有垃圾自動回收機制),效率不如棧,但容量大spa

JS定義的基本類型值會被存放在棧內存中,JS能夠直接訪問棧內存,因此訪問存放基本類型值的變量能夠直接訪問到基本類型值。而引用類型由於其大小不固定,系統會爲引用類型分配堆內存存放,而只將指向該堆內存空間的指針(引用)存放在棧中,JS不予許直接訪問存放引用類型值的堆內存,只能經過訪問引用間接的訪問引用類型值。這樣一來,咱們訪問引用類型值時,實質上只是在訪問它的引用,而後再按照這個引用地址去堆中找到它的實際內容。指針

所以進行拷貝的時候,對於基本類型值變量,系統會爲新變量單獨開闢一個新的棧內存空間,並將源變量保存的基本類型值拷貝一份保存到裏面。而對於引用類型值,新變量拷貝獲得的只是引用對象的引用而已,這麼一來,經過兩個變量保存的引用訪問到的實質就是同一塊堆內存,也就是同一個對象。code

let person = {
        name : "kelly",
        love : { sport : "swim", movie : "love story" }
}
let new_person = person
new_person.name = "lin"
console.log( person )
console.log( new_person )

淺拷貝是指在進行對象拷貝的時候,只對對象的第一層鍵值進行拷貝。對象

在上面的例子當中,若是不但願修改 new_person 對象的 name 值的時候,源對象的 name 值也跟着一塊兒改變,咱們能夠嘗試對拷貝過程作一些處理,而再也不只是簡單的直接賦值。blog

let person = {
        name : "kelly",
        love : { sport : "swim", movie : "love story" }
}
let new_person = {  }

function shallowCopy( target, source ){
        if( !source || typeof( source ) !== "object" ){
                return
        }
        if( !target || typeof( target ) !== "object" ){
                return
        }
        for( let key in source ){
                if( source.hasOwnProperty( key ) ){
                        target[ key ] = source[ key ]
                }
        }
}

shallowCopy( new_person, person )
new_person.name = "lin"
console.log( new_person )
console.log( person )

這時候咱們修改了拷貝對象的 name 值,源對象的 name 值不會再跟着改變了,但是當咱們修改屬性 sport 的值的時候,源對象的 sport 值卻又跟着改變了。遞歸

shallowCopy( new_person, person )
new_person.love.sport = "run"
console.log( new_person )
console.log( person )

前面也說了,淺拷貝只是對第一層鍵值進行的拷貝,當源對象內部還嵌套着其它的對象的時候,就又會出現變量間直接賦值的狀況。拷貝對象的 love 屬性拷貝的是源對象 love 屬性所對應對象的引用,因此也致使了拷貝對象和源對象的 love 屬性指向的都是堆內存中的同一引用對象。
Object.assign(  )方法所實現的也是淺拷貝。
 
Object.assign( new_person, person )
new_person.name = "lin"
new_person.love.sport = "run"
console.log( new_person )
console.log( person )

除此以外,Array 的 concat 方法和 slice 方法實現的也是數組對象的淺拷貝。

let arr = [ 1, [ 2, 3, 4 ] ]
let arr_concat = arr.concat(  )
let arr_slice = arr.slice( 0 )
arr_concat[ 1 ][ 0 ] = "NO"
console.log( arr ) //[ 1, [ 'NO', 3, 4 ] ]
console.log( arr_concat ) //[ 1, [ 'NO', 3, 4 ] ]
arr_slice[ 1 ][ 1 ] = "NONO!"
console.log( arr ) //[ 1, [ 'NO', 'NONO!', 4 ] ]
console.log( arr_slice ) //[ 1, [ 'NO', 'NONO!', 4 ] ]

數組 arr,arr_concat, arr_slice 的第二個元素引用的都是同一個堆內存對象。

那如何才能徹底獨立的拷貝出一份呢?其實只要遞歸下去,對內部屬性的值還是對象的再次進入對象內部對其屬性值一一拷貝便可。
 
function deepCopy( target, source ){
        if( !source || typeof( source ) !== "object" ){
                return
        }
        if( !target || typeof( target ) !== "object" ){
                return
        }
        for( let key in source ){
                if( source.hasOwnProperty( key ) ){
                        if( source[ key ] && typeof( source[ key ] ) === "object" ){
                                target[ key ] = target.constructor === "Array" ? [  ] : {  }
                                deepCopy( target[ key ], source[ key ] )
                        } else {
                                target[ key ] = source[ key ]
                        }
                }
        }
}

deepCopy( new_person, person )
new_person.name = "lin"
new_person.love.sport = "run"
console.log( new_person )
console.log( person )
 
 
對象深拷貝還能夠藉助 JSON 實現。
var obj = {
    a: 1,
    b: 2,
    c: undefined,
    sum: function() { return a + b }
};

var obj2 = JSON.parse( JSON.stringify( obj ) )
console.log( obj2 )
 
 
可是這種方式實現的拷貝會忽略掉值爲 undefined 和 函數表達式 的屬性。
相關文章
相關標籤/搜索