通过项目实战学习:深拷贝与浅拷贝的内涵,实现深拷贝的几种方法,深拷贝解决项目异常bug

Posted Kabukiyo Lin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通过项目实战学习:深拷贝与浅拷贝的内涵,实现深拷贝的几种方法,深拷贝解决项目异常bug相关的知识,希望对你有一定的参考价值。

问题描述

问题描述:有一个可接受用户edit编辑的表单。若用户输入错误,需要cancel取消原输入的值。但bug在于,用户点击cancel之后,却在表单保存了新输入错误的值,并没有在点击cancel之后还原原来的值。

出错代码

cancel(){
	// change status after 'cancel' button clicked
	// do something
	// 遍历赋值 (关键点!!)
	// 还原表单原本的值
	for(let i = 0; i < this.data.length; i++){
		this.data[i] = this.dataFromDataBase[i];
		// 即点击了cancel按钮后,应当把数据库中的data重新赋值还给表单中的data。从而没有保存错误的值,还原原本的数据。
	}
	// do something
	// this.service.emit();
	// this.service.closeBanner()
}

当时发现这个bug时,我大致觉得是赋值这里出了问题。但由于并不是太熟悉这一part,所以我想先log出来data和dataFromDataBase具体是什么,好进一步“诊断”。

console.log(this.data[i]);
console.log(this.dataFormDataBase[i]);
this.data[i] = this.dataFromDataBase[i];
console.log(this.data[i]);

结果分析

我们只关注数组中的一个代表
举个例子,譬如未改动之前原表单的data和数据库的都是typescript。
第一次改动:

  1. 我在typescript后面加了个1,即表单的data变成typescript1
  2. 第一次点击cancel
  3. 输出结果如下:
console.log(this.data[i]); //typescript1;
console.log(this.dataFormDataBase[i]) //typescript
this.data[i] = this.dataFromDataBase[i];
console.log(this.data[i]); //typescript
  1. 说明第一次改动后cancel,数据库里的数据dataFromDataBase能够把原本的数据typescript还原给表单中的data,从而起到还原的作用

第二次改动:

  1. 我还是在原本表单输入框,在typescript后面加个1
  2. 第二次cancel
  3. 输出结果
console.log(this.data[i]); //typescript1;
console.log(this.dataFormDataBase[i]) //typescript1
this.data[i] = this.dataFromDataBase[i];
console.log(this.data[i]); //typescript1

??数据库里面的data也变成了typescript1???
为什么第一次dataFromDataBase没有问题,第二次就出现问题了
从此以后,不管怎么cancel,表单输入的值都会保存错误输入的值。

OMG

到这里,这么多疑惑,我们就得去了解一下深拷贝和浅拷贝的内涵了!

等了解了深拷贝和浅拷贝的区别原理,那就知道为什么会出现这样的bug。

再关注一下这一句关于赋值的代码,它在后面讲解是重点对象。

this.data[i] = this.dataFromDataBase[i];

深拷贝与浅拷贝

赋值操作

在了解深拷贝和浅拷贝之前,得先了解什么是拷贝copy。
其实在日常敲码的过程中,我们是经常在拷贝copy的。
这个拷贝,就是赋值操作。

赋值: 简单来说就是把某一数值或对象交给另外一个变量。
在JavaScript中,变量只是一个用于保存值的占位符

1.如果我们赋值赋的只是基本数据类型,那么变量只是保存了这个值。
2. 如果我们赋值的是引用数据类型,那么这个变量实际上保存的是该引用数据的地址。赋值,实际上是赋址

举个例子再理解一下赋值与“赋址”的区别:

一、基本数据类型:赋值后,两个变量互不影响

let a = 'coffee';
let b = a; //赋值
console.log(a); //coffee;
console.log(b); //coffee; 赋值成功

a = 'milk';
console.log(a); //milk
console.log(b); //coffee; 赋值后,变量a和变量b并没有关系

二、引用数据类型:赋值后,两个变量保存同一个地址,指向同一个对象,互相有影响。

let a = {
	name:'coffee',
	ice:'less'
	price:30
}
let b = a;
console.log(a);//{ name:'coffee',ice:'less',price:30};
console.log(b);//{ name:'coffee',ice:'less',price:30};

a.name = 'milk';
a.ice = 'none';
a.price = 8;
console.log(a);//{ name:'milk',ice:'none',price:8};
console.log(b);//{ name:'milk',ice:'none',price:8};
// 改变a,b也被改变了
// 因为实际上把a赋值给b,b就保存了a指向对象的地址,即a和b保存的是同一个对象的地址,他们都指向同一个对象,都引用了同一个对象。

破案:
到这里,其实我们已经能知道为什么会有上述的bug了:data和dataFromDataBase都是引用数据类型。在第一次cancel时,由于dataFromDataBase赋值给data,那么,data和dataFromDataBase则都引用了同一个对象;在第二次cancel时,当data发生变化,dataFromDataBase也就发生了变化!因为他们都指向同一个对象了。

那怎么办,我们就单纯地只想赋值呀。并不想这二者纠缠不清,表单的数据变了就影响到db的数据。

这时候就需要,深拷贝!进一步了解**什么是浅拷贝,什么是深拷贝!**最后的最后,知道怎么实现深拷贝,就可完结撒花了!

深拷贝的三种实现

深拷贝有什么用,开门见山:深拷贝之后,两个变量所引用的对象是两个毫无关系的对象,各过各的,互不影响

深拷贝会拷贝所有的属性,相当于浅拷贝来说时间长开销大。

噢~ ,那深拷贝正合我意,我就是想data和dataFromDataBase两个变量互不影响,我在赋值的时候实现深拷贝就好!

那怎么实现深拷贝?

总结一下实现深拷贝三种方法,具体如何使用可以上文档查看:

  1. JSON.stringify,JSON.parse : let b = JSON.parse(JSON.stringify(a));
  2. JQ的extend方法:let b = $.extend(true,[],a);
  3. lodash的cloneDeep方法:let b = cloneDeep(a);

深拷贝解决bug

这三种方法用哪一种都可以,我发现项目里这一part也有使用lodash的cloneDeep,所以,我也使用cloneDeep()

cancel(){
	// change status after 'cancel' button clicked
	// do something
	// 遍历赋值 (关键点!!)
	// 还原表单原本的值
	for(let i = 0; i < this.data.length; i++){
		this.data[i] = cloneDeep(this.dataFromDataBase[i]);
		// 使用cloneDeep(),实现深拷贝。
	}
	// do something
	// this.service.emit();
	// this.service.closeBanner()
}

浅拷贝

一个对象如果进行浅拷贝,所拷贝的属性若是基本数据类型,拷贝之后二者的属性不会互相影响;但若所拷贝的属性是引用数据类型,则两个变量中该属性都是指向同一个引用对象。

也就是说,变量a引用一个对象,这个对象中有基本数据类型和引用数据类型。若浅拷贝给变量b,变量a中的基本数据类型发生变化,不会影响到b,但若是引用数据类型变化,b的也会被影响。

具体实现也有三种方式

  1. Object.assign() :let b = Object.assign({},a);
  2. 展开语法 : let b = {…a};
  3. array.prototype.slice(): let b = a.slice(0);

总结

一、普通赋值拷贝:

  1. 若是基本数据类型,互不影响
  2. 若是引用数据类型,对象中的值会互相影响

二、浅拷贝:

  1. 对象中的基本数据类型互不影响
  2. 对象中的引用数据类型互相影响

三、深拷贝

  1. 对象中的基本数据类型互不影响
  2. 对象中的引用数据类型互不影响
  3. 总之两个变量所引用的对象,毫无关系互不影响

从普通的赋值拷贝,到浅拷贝,最后的深拷贝,层层递进,拷贝的程度越来越深。浅拷贝之后的两个对象a和b,他们的基本数据类型是互不影响了,但他们的引用数据类型还是有关系的。深拷贝之后的两个变量,则脱离了关系,互不影响了。

实现总结:

一、浅拷贝实现:

  1. Object.assign()
  2. 展开语法
  3. array.prototype.slice()

二、深拷贝实现:

  1. JSON.parse(),JSON.stringify()
  2. JQ的extend()
  3. lodash的cloneDeep()

以上是关于通过项目实战学习:深拷贝与浅拷贝的内涵,实现深拷贝的几种方法,深拷贝解决项目异常bug的主要内容,如果未能解决你的问题,请参考以下文章

js中的深拷贝与浅拷贝

面试深拷贝与浅拷贝的实现原理

尚硅谷设计模式学习--- [原型模式(Prototype模式),深拷贝与浅拷贝]

深拷贝与浅拷贝的区别,实现深拷贝的几种方法

JS深拷贝与浅拷贝的区别,实现深拷贝的几种方法

JavaScript深拷贝与浅拷贝