Javascript 浅拷贝与深拷贝
在了解JS的浅拷贝与深拷贝之前,我们需要先知道什么是值传递与引用传递。
在JS中,基本类型值的拷贝是按值传递的,而引用类型值的拷贝则是按引用传递的。通过值传递的变量间不会有任何牵连,互相独立;但是引用传递拷贝的变量间则会相互影响,修改其中任何一方所引用的对象的值都会在另一方中体现,之所以会有这样的表现和JS的内存机制有关。
JS的内存也分为堆和栈,但是注意这里的堆栈和数据结构的堆栈是不同的概念。
栈:由系统自动分配,自动回收,效率高,但容量小
堆:由程序员手动分配内存,并且手动销毁(高级语言如JS中有垃圾自动回收机制),效率不如栈,但容量大
JS定义的基本类型值会被存放在栈内存中,JS可以直接访问栈内存,所以访问存放基本类型值的变量可以直接访问到基本类型值。而引用类型因为其大小不固定,系统会为引用类型分配堆内存存放,而只将指向该堆内存空间的指针(引用)存放在栈中,JS不予许直接访问存放引用类型值的堆内存,只能通过访问引用间接的访问引用类型值。这样一来,我们访问引用类型值时,实质上只是在访问它的引用,然后再按照这个引用地址去堆中找到它的实际内容。
因此进行拷贝的时候,对于基本类型值变量,系统会为新变量单独开辟一个新的栈内存空间,并将源变量保存的基本类型值拷贝一份保存到里面。而对于引用类型值,新变量拷贝得到的只是引用对象的引用而已,这么一来,通过两个变量保存的引用访问到的实质就是同一块堆内存,也就是同一个对象。
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 值也跟着一起改变,我们可以尝试对拷贝过程做一些处理,而不再只是简单的直接赋值。
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 )
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 )
var obj = { a: 1, b: 2, c: undefined, sum: function() { return a + b } }; var obj2 = JSON.parse( JSON.stringify( obj ) ) console.log( obj2 )