深拷贝浅拷贝和赋值的原理及实现剖析
在工作中我们经常会用到深拷贝与浅拷贝,但是你有没有去分析什么场景下使用它,为什么需要使用呢,深浅拷贝有何异同呢,什么是深拷贝呢,如何实现呢,你会有这些问题吗,今天就为大家总结一下吧。
栈内存与堆内存
区别
- 浅拷贝—拷贝的是一个对象的指针,而不是复制对象本身,拷贝出来的对象共用一个指针,其中一个改变了值,其他的也会同时改变。
- 深拷贝—拷贝出来一个新的对象,开辟一块新的空间,拷贝前后的对象相互独立,互相不会改变,拥有不同的指针。
简单的总结下,假设有个A,我们拷贝了一个为B,就是修改A或者B的时候看看另一个会不会也变化,如果改变A的值B也变了那么就是浅拷贝,如果改变A之后B的值没有发生变化就是深拷贝,当然这是基础理解,下面我们一起来分析下吧。
赋值
/** demo1基本数据类型 */
let a = 1;
let b = a;
b = 10;
console.log(a,b)// 1 10
/** demo2引用数据类型 */
let a = {
name: '小九',
age: 23,
favorite: ['吃饭','睡觉','打豆豆']
}
let b = a;
a.name = '小七'
a.age = 18
a.favorite = ['上班','下班','加班']
console.log(a,b)
/** { name: '小七', age: 18, favorite: [ '上班', '下班', '加班' ] } { name: '小七', age: 18, favorite: [ '上班', '下班', '加班' ] }*/
通过看上面的例子可以看出通过赋值去拿到新的值,赋值对于基本数据来说就是在栈中新开了一个变量,相当于是两个独立的栈内存,所以相互不会影响,但是对于引用数据类型,他只是复制了一份a在栈内存的指针,所以两个指针指向了同一个堆内存的空间,通过任何一个指针改变值都会影响其他的,通过这样的赋值可以产生多个指针,但是堆内存的空间始终只有一个,这就是赋值产生的问题,我们在开发中当然不希望改变B而影响了A,所以这个时候就需要用到浅拷贝和深拷贝了。
- 针对基本数据类型,随便赋值都不会相互影响
- 针对引用数据类型,赋值就会出现我们不想看到的,改动一方双方都变化。
浅拷贝
Object.assign()
/** Object.assign */
let A = {
name: '小九',
age: 23,
sex: '男'
}
let B = Object.assign( {
}, A);
B.name = '小七'
B.sex = '女'
B.age = 18
console.log(A,B)
/** { name: '小九', age: 23, sex: '男' } { name: '小七', age: 18, sex: '女' } */
首先实现浅拷贝的第一个方法是通过 Object.assign()
这个 方法,Object.assign()
方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
先不管这个方法具体干嘛,我们先来看看结果,我们发现拷贝一个A之后的B改变了name
、age
、sex
之后A的值并没有发生变化,在这里,你可能会想,这不是就成功了么,AB宜家互相不影响了,可是和我们上面讲的浅拷贝会AB互相不变化就是深拷贝产生了矛盾,那么是为什么呢,其实上面已经说到了,这个demo里面用到的全是基本数据类型,所以拷贝和赋值一样,针对基本数据类型,都是在栈重新开辟一个变量,所以相互不会影响,那我们看看引用数据类型,
let A = {
name: '小九',
age: 23,
sex: '男',
favorite: {
item_a:['打游戏','上网'],
item_b:['读书','网课']
}
}
let B = Object.assign( {
}, A);
B.name = '小七'
B.sex = '女'
B.age = 18
B.favorite.item_a =['打篮球']
B.favorite.item_b =['写笔记']
console.log(A)
console.log(B)
/** 打印结果对比 */
{
name: '小九',
age: 23,
sex: '男',
favorite: {
item_a: [ '打篮球' ], item_b: [ '写笔记' ] } }
----------------------------------------------------------------
{
name: '小七',
age: 18,
sex: '女',
favorite: {
item_a: [ '打篮球' ], item_b: [ '写笔记' ] } }
通过对比发现我们同样拷贝了A之后发现改变B的name
age
sex
都不会影响,但是改变facorite
的时候却影响了A,那么问题来了,这我们通过浅拷贝发现依然无法满足我们的需求,改变B同样影响了A,回到这个方法,Object.assign()
这个方法是可以把任意的多个源对象的可枚举属性拷贝给目标对象,然后返回目标对象,它进行的是对象的浅拷贝,拷贝的是对象的引用,而不是对象本身,所以针对于这种有两层的数据结构就出出现只拷贝了第一层,第二层以下的对象依然拷贝不了,所以我们称Object.assign()
为浅拷贝,只有在对象只有一层结构的时候才时候使用,
- 很多人说
Object.assign
是深拷贝,其实是错误的, - 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
- 该方法只拷贝源对象自身的属性,不拷贝其继承的属性。
- 该方法不会拷贝对象不可枚举的属性
- undefined和null无法转成对象,他们不能作为Object.assign参数,但是可以作为源对象
- 属性为Symbol的值,可以被该方法拷贝。
- 浅拷贝,拷贝了第一层的基本数据类型结构,但是深层的依然没有拷贝到,也就是第一层基本类型数据已经不会影响了,但是引用却不行,所以还不够
参考 前端进阶面试题详细解答
Array.prototype.silce
看这个方法之前先给大家看看mdn对于这个方法的描述。
返回值
返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。
说明
请注意,该方法并不会修改数组,而是返回一个子数组。如果想删除数组中的一段元素,应该使用方法 Array.splice()。
看完它的描述大家就应该差不多明白了吧,让我们继续用刚刚的例子来实现下
let A = [1,2,3,[4,