0

题目描述

js中引用类型的复制,浅拷贝,深拷贝过程中堆栈如何变化?

题目来源及自己的思路

在学习复制和浅拷贝的过程中,大部分博客都不清晰,把这两个混为一谈,所以请大佬们给我解析一下这三个的过程中,他们具体运行时堆栈里面有什么变化?

下面摘抄别人的博客

复制:当我们复制引用类型的变量时,实际上复制的是栈中存储的地址,所以复制出来的obj2实际上和obj指向的堆中同一个对象。因此,我们改变其中任何一个变量的值,另一个变量都会受到影响,这就是为什么会有深拷贝和浅拷贝的原因(复制我是理解的,但既然复制和浅拷贝都不一样了,为什么还要这样写?)。

浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是引用类型,拷贝的就是内存地址
,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。(这个内存地址指的是堆中的地址吗?拷贝后在堆内存中又开辟一个新的存放还是别的?)

还有种说法:浅拷贝是一层的拷贝,深拷贝是多层的拷贝…这种说法和上述的说法到底谁对谁错?

我迷惑的是

1:括号里面的问题?
2:赋值和浅拷贝的区别到底在哪里?
4.如果浅拷贝的一层都拷贝了,为什么两个引用类型还会相互影响?
---------------v--------------

根据下面两位回答的理解补充:

5.赋值就等于浅拷贝?除了等号赋值之外还有其他浅拷贝方式吗?
6.那“浅拷贝是一层数据的拷贝,深拷贝是多层的拷贝…”这个话是错的?
7.那如果使用单层循环去给新对象赋值或者手动赋值的方法是就属于深拷贝,对吗?

//循环
 let obj={a:1}, obj2 = {}
 for (const key in obj) {
     obj2[key] = obj[key]
 }
 obj2.a =2
 console.log(obj);//{a: 1}
 console.log(obj2);//{a: 2}
//赋值
 let obj3 ={
     a:obj.a
 }
 obj3.a = 3
 console.log(obj);//{a: 1}
 console.log(obj3);//{a: 3}

8.浅拷贝的大致流程就是:在栈内存分配内存来存储新对象的地址,而这个地址与原对象一致,指向原对象在堆内存的地址,堆内存不做操作。那浅拷贝数据obj = {a:1}时候原对象会随着新对象的值得改变而改变,对吗?

最重要的问题!!! 9:浅拷贝obj时多层属性时,其中堆栈变化是什么或者说怎么浅拷贝这个obj?

let obj = {a:1,b:{c:2},d:[1,2,3]}
各位大佬不用讲那么基础的,emmm我是懂那些的,只是因为网上的信息说法不统一,有些混乱了不知道正确的是什么了,以上几个问题,可以直接回答对或不对,错在哪里就可以了,码字不易很感谢你们,谢谢
3 个回答
0

已采纳

自答一下,我问了好几个人,回答方式都和上述几位一样,专门查了书,发现浅拷贝这里的东西没有具体定义,但是我更认可:引用类型的 赋值不等于浅拷贝浅拷贝是一层数据的拷贝,深拷贝是多层的拷贝这个观点。

let obj = {a:1,b:{c:2}}
let obj2 = obj1

赋值:新对象改变会影响原数据,原因引用类型在赋值的过程,只是把栈内存中的指针复制了一次,栈内存多了obj2,但是堆内存中的数据{a:1,b:{c:2}}没有改变,两个对象指向同一个堆内存地址。

MDN中数组的slice方法中有这句话 ,slice不会修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。原数组的元素会按照下述规则浅拷贝...那就用slice来验证一下什么是浅拷贝:

var a = [ 1, 3, 5, { x: 1 } ];
var b = Array.prototype.slice.call(a);
b[0] = 2;
b[3].x = 2
console.log(a); // [ 1, 3, 5, { x: 2 } ];
console.log(b); // [ 2, 3, 5, { x: 2 } ];

这里b[0] = 2;时候a[0]没有随着改变,b[3].x = 2时候a[3].x发生了变化。

MDN中splice的规则:
如果该元素是个对象引用 (不是实际的对象),slice会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。

对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。

综上:我认为的浅拷贝:新的数据复制了原数据中 非对象属性的值对象属性的引用,也就是说对象属性并不复制到内存,但非对象属性的值却复制到内存中。

而深拷贝会另外拷贝一份一个一模一样的对象,从堆内存中开辟一个新的区域存放新对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

2

变量分为基本类型引用类型

基本类型的赋值是的赋值,不涉及引用的浅拷贝、深拷贝,就相当于给你一百块钱,你拿去随便花跟我没关系

引用类型的赋值是引用地址的赋值,相当于浅拷贝,就是给你个银行卡,密码给你,你花钱刷卡,我也可以刷卡,都会影响卡里面的钱

为了解决引用浅拷贝,就有了深拷贝,就是让引用类型基本类型一样的赋值,赋值后相互不影响,深拷贝一般都是递归遍历直到基本类型,去一个个赋值,这样就没有引用了,就是你注册一张卡,我把我卡里的钱打给你,你花你卡里的钱,我花我卡里的钱,不影响

0

1.1 “复制”和“拷贝”在汉语中是同一个意思,原博文写得不是很严谨,作者所谓“当我们复制引用类型的变量时”大概是在说这样的操作:

const a = {prop: 110};
const b = a;  // 此次赋值即所谓“复制引用类型的变量a”
b.prop1 = 100;
console.log(a.prop1);  // {prop: 110}
// 对 b 所做修改作用到了 a 上,说明 a,b 所引用的对象其实是同一个

1.2 “如果其中一个对象改变了这个地址”这种说法并不成立,计算机做不到改变内存地址,其只能做到把一块内存上存储的信息复制到另一块内存上,所以不要纠结这里的“地址”指的是什么地址。

2.js数据类型分基本类型和引用类型,你需要先理解二者的区别,才能理解深浅拷贝的区别。简单来说,给一个变量赋一个简单类型的数据,那么这个数据会被拷贝一份,赋值前后的两个值不会相互干扰;而给一个变量赋一个引用类型的“值”,只是复制了该引用类型数据在栈空间中的地址,有一个很通俗的比喻,就是只复制了引用类型数据的门牌号,顺着门牌号去找到的数据,和赋值前的数据还是同一份。
深拷贝和浅拷贝是针对引用类型的数据而言的,引用类型的数据可能占用大量内存空间,通过引用同一批内存中数据的方式,可以节省内存开销,所以语言设计者发明了只复制引用地址,不复制所有值的赋值方式,即浅拷贝,复杂类型数据的赋值就是浅拷贝
就上面的例子而言,如果你希望修改 b 的时候不影响到 a,那么你就应该对 a 进行一次深拷贝。深拷贝的实现是基于基本类型的赋值是把数据复制一份这个事实的,引用类型的数据相当于一个框架,框架上放着一些基本类型的数据,你只要按照框架 a 的结构做一个新的框架,再把 a 框架对应的各个简单类型数据复制过去,就完成了一次深拷贝。
比如下面的例子:

const a = {
    prop: 'my name is a'
}
let b = {};
// 可以看到 b 是一个新建的对象,与 a 其实没有任何关系,这一点很重要,所有深拷贝的实现都是创建一个个与原对象/数
// 组没有关系的新对象/数组来实现的
b.prop = a.prop;  // 复制对应的各个简单数据类型
b.prop1 = 'my name is not a';
console.log(a.prop1);  // undefined,对 b 的操作并不影响 a

3.第三题,我也不会答,因为尚未学习到堆栈内存的物理区别。浅拷贝的大致流程就是:在栈内存分配内存来存储新对象的地址,而这个地址与原对象一致,指向原对象在堆内存的地址,堆内存不做操作。

4.如果看到这里还不明白的话,可能是我没有表述清楚。

撰写答案

推广链接