欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

什么是深拷贝?什么是浅拷贝?从深入理解基本数据类型和引用数据类型开始!

程序员文章站 2022-04-29 23:29:46
...

1.从一个例子开始

例子中有两个页面:列表页和编辑页。
列表页:
*ngFor="let item of addressList"遍历一个数组渲染一个列表:

什么是深拷贝?什么是浅拷贝?从深入理解基本数据类型和引用数据类型开始!
右侧编辑按钮(click)="goEditAddress(item),在方法中传入列表中的元素item(方法如下,item是一个对象)。

 async goEditAddress(address) {
 	// 创建模态页面,ionic创建一个模态页就直接是一个页面
   const modal = await this.modalController.create({
     component: EditAddressComponent,
     enterAnimation: ModalFromRightEnter,
     leaveAnimation: ModalFromRightLeave,
     componentProps: {
       option: 'edit',
       address
     },
     mode: 'ios',
   });
   await modal.present();
 }

该方法在调用模态框(编辑页)的时候将元素item作为参数传递到编辑页中。
编辑页:
编辑页@Input() address: any;拿到列表页传过来的item。
页面初始化的时候执行:

 ionViewDidEnter() {
    this.addressModel = this.address;
 }

该页面在某个方法中执行下面语句:

 // 删除了this.addressModel里的两个属性
 delete this.addressModel.phoneShow;// 电话
 delete this.addressModel.addressShow;// 地址信息

该语句执行完之后会跳转到列表页,此时列表页的电话和地址信息没有了
什么是深拷贝?什么是浅拷贝?从深入理解基本数据类型和引用数据类型开始!
正常来说编辑页里只是把传过来的参数address赋值给addressModel,方法里删除的只是addressModel的属性,怎么会跟列表页有关系呢?渲染列表的数组里的属性怎么也被删除了?

这就涉及到js的简单赋值和引用

可以先把结论放在这里:
以上步骤走了两个对象引用,
第一个是在列表页跳转编辑页时传递参数
编辑按钮(click)="goEditAddress(item)绑定item,并在方法里传给编辑页

 async goEditAddress(address) {
 	// 创建模态页面,ionic创建一个模态页就直接是一个页面
   const modal = await this.modalController.create({
     component: EditAddressComponent,
     enterAnimation: ModalFromRightEnter,
     leaveAnimation: ModalFromRightLeave,
     componentProps: {
       option: 'edit',
       address
       // address相当于address:address
     },
     mode: 'ios',
   });
   await modal.present();
 }

也就是说传递参数时address:address这个赋值是一个对象引用。

 componentProps: {
   option: 'edit',
   address
   // address相当于address:address
 },

第二个是在页面初始化赋值的时候

 ionViewDidEnter() {
    this.addressModel = this.address;
 }

这两个对象引用的结果就是,this.addressModel指向的还是列表页的数组addressList的元素item。也就是说this.addressModel引用的是列表页数组addressList元素item的地址,所以删addressModel里的属性就是删数组addressList元素item里的属性。(为什么会这样呢?接着往下走吧)


2.js的变量存储类型

变量存储类型分两类

  1. 基本类型:直接存储在中的数据。(字符串、布尔值、数字、undefined、null

  2. 引用类型:将该对象引用地址存储在中,然后对象里面的数据存放在中。(数组、对象、Date、RegExp、函数、特殊的基本包装类型以及单体内置对象)

特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)

基本类型的变量是存放在栈区的(栈区指内存里的栈内存)


假如有以下几个基本类型的变量:

var name = 'jozo';
var city = 'guangzhou';
var age = 22;

那么它的存储结构如下图:
什么是深拷贝?什么是浅拷贝?从深入理解基本数据类型和引用数据类型开始!
栈区包括了 变量的标识符和变量的值。

引用类型的变量将对象引用地址存储在栈中,对象里面的数据存放在堆中

javascript和其他语言不同,其不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间,那我们操作啥呢? 实际上,是操作对象的引用,所以引用类型的值是按引用访问的。

准确地说,引用类型的存储需要内存的栈区和堆区(堆区是指内存里的堆内存)共同完成,栈区内存保存变量标识符和指向堆内存中该对象的指针,也可以说是该对象在堆内存的地址。

假如有以下几个对象:

var person1 = {name:'jozo'};
var person2 = {name:'xiaom'};
var person3 = {name:'xiaoq'};

则这三个对象的在内存中保存的情况如下图:
什么是深拷贝?什么是浅拷贝?从深入理解基本数据类型和引用数据类型开始!


简单赋值

在从一个变量向另一个变量赋值基本类型时,会在该变量上创建一个新值,然后再把该值复制到为新变量分配的位置上:

var a = 10;
var b = a;

a ++ ;
console.log(a); // 11
console.log(b); // 10

此时,a中保存的值为 10 ,当使用 a 来初始化 b 时,b 中保存的值也为10,但b中的10与a中的是完全独立的,该值只是a中的值的一个副本,此后,这两个变量可以参加任何操作而相互不受影响。
什么是深拷贝?什么是浅拷贝?从深入理解基本数据类型和引用数据类型开始!
也就是说基本类型在赋值操作后,两个变量是相互不受影响的。

对象引用

当从一个变量向另一个变量赋值引用类型的值时,同样也会将存储在变量中的对象的值复制一份放到为新变量分配的空间中。前面讲引用类型的时候提到,保存在变量中的是对象在堆内存中的地址,所以,与简单赋值不同,这个值的副本实际上是一个指针,而这个指针指向存储在堆内存的一个对象。那么赋值操作后,两个变量都保存了同一个对象地址,则这两个变量指向了同一个对象。因此,改变其中任何一个变量,都会相互影响:

var a = {}; // a保存了一个空对象的实例
var b = a;  // a和b都指向了这个空对象

a.name = 'jozo';
console.log(a.name); // 'jozo'
console.log(b.name); // 'jozo'

b.age = 22;
console.log(b.age);// 22
console.log(a.age);// 22

console.log(a == b);// true

它们的关系如下图:
什么是深拷贝?什么是浅拷贝?从深入理解基本数据类型和引用数据类型开始!
因此,引用类型的赋值其实是对象保存在栈区地址指针的赋值,因此两个变量指向同一个对象,任何的操作都会相互影响。


3.深拷贝和浅拷贝

最后再来看深拷贝和浅拷贝还有赋值的区别,这样就好理解多了

浅拷贝:也就是拷贝A对象里面的数据,但是不拷贝A对象里面的子对象

深拷贝:会克隆出一个对象,数据相同,但是引用地址不同(就是拷贝A对象里面的数据,而且拷贝它里面的子对象)

赋值:简单赋值和对象引用,对象引用获得该对象的引用地址

4.记一个最简单的对象深拷贝,解决文章例子中的问题

一个最简单的对象深拷贝:

var obj = {
name : 'zhl',
age : 30
}

var obj2 = JSON.parse(JSON.stringify(obj));

所以文章例子中的问题写一个这样简单的深拷贝就可以解决了:

this.addressModel = JSON.parse(JSON.stringify(this.address));

重要参考及原文:

相关标签: JavaScript学习