【修真院web小课堂】如何实现数组深拷贝与浅拷贝


大家好,我是IT修真院郑州分院第十期的学员金俊,一枚正直纯洁善良的程序员 今天给大家分享一下,修真院官网前端任务js4,深度思考中的知识点——如何实现数组深拷贝与浅拷贝

一、背景知识:

什么是数据类型?

数据类型,是编程语言描述事物、对象的方法

描述了数值的表示法、解释和结构,[算法]操作,或是([对象]在存储器中的存储区),以及内存管理。

js中有七种类型:

【修真院web小课堂】如何实现数组深拷贝与浅拷贝

object类型

let o = { 'name':'lee' };
typeof o;
let a = ['reg','blue'];
typeof a;

Object类型是其他所有实例的基础。

​ 对于引用类型的值,是在堆内存中分配空间。但由于内存地址大小是固定的,因此内存地址保存在栈内存中,所以查询的时候先从栈内存中取到地址,然后在通过地址找到堆内存中的实际值。

二、知识剖析

什么是内存?

*内存条(电路板)通电后产生的存储空间(临时的)

​ 声明一个变量,就自动分配内存 释放内存是那个内存可以再利用(如一开始设置全局变量,后面想在使用这个全局变量,就得释放(就是重新赋给这个变量一个值))

*产生和死亡:内存条(集成电路板)==》通电==》产生一定容量的存储空间==》存储各种数据==》断电==》内存全部消失

*内存的空间是临时的,而硬盘的空间是持久

*一块内存包含2个数据

-->内存存储的数据(一般数据/地址数据)

-->内存地址值数据

【修真院web小课堂】如何实现数组深拷贝与浅拷贝

*内存分类

->栈(空间较小):全局变量,局部变量

->堆(空间较大):对象

JavaScript有两种类型的值,内存图如下:

栈:原始数据类型(Undefined,Null,Boolean,Number、String、Symbol)

堆:引用数据类型(对象、数组和函数)

【修真院web小课堂】如何实现数组深拷贝与浅拷贝

区别:

两种类型的区别是:存储位置不同;

###

1)基本类型:原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。

(2)引用类型:

  1. 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;

  2. 引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。|||存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

JavaScript存储对象都是存地址的,所以浅拷贝会导致 obj1 和obj2 指向同一块内存地址。改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变,而深拷贝是开辟一块新的内存地址,将原对象的各个属性逐个复制进去。对拷贝对象和源对象各自的操作互不影响。

例如:数组拷贝

//浅拷贝,双向改变,指向同一片内存空间
var arr1 = [1, 2, 3];
var arr2 = arr1;
arr1[0] = 'change';
console.log('shallow copy: ' + arr1 + " );   //shallow copy: change,2,3
console.log('shallow copy: ' + arr2 + " );   //shallow copy: change,2,3

浅拷贝

  • 所谓的浅拷贝就是拷贝指向对象的指针,意思就是说:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间.

  • 浅拷贝只是一种简单的拷贝,让几个对象公用一个内存,然而当内存销毁的时候,指向这个内存空间的所有指针需要重新定义,不然会造成野指针错误

深拷贝

  • 所谓的深拷贝指拷贝对象的具体内容,其内容地址是自助分配的,拷贝结束之后,内存中的值是完全相同的,但是内存地址是不一样的,两个对象之间相互不影响,也互不干涉.

变量的复制

​ 基本类型:会在栈上创建一个新的值,然后把该值复制到新变量的位置上。

let a = 1; let b = a;

b = 5;

console.log(a);

console.log(b);

​ a和b,操作不会相互影响

引用类型:

会将对象复制一份到新分配的变量中,但复制的是指针,而这个指针指向堆中的同一个对象。

三、常见问题

如何实现数组深拷贝与浅拷贝?

四、解决方案

数组浅拷贝实现:

//浅拷贝,双向改变,指向同一片内存空间
let arr1 = [1, 2, 3];
let arr2 = arr1;
arr1[0] = 'change';
console.log(arr1)
console.log(arr2)

数组深拷贝实现方式

for 循环实现数组的深拷贝

for循环是非常好用的。如果不知道高级方法,通过for循环能够完成我们大多数的需求。

let arr1 = [1,2,3,4,5]
let arr2 = copyArr(arr1)
function copyArr(arr1) {
    let res = []
    for (let i = 0; i < arr1.length; i++) {
     res.push(arr1[i])
    }
    return res
}
arr1[2] = "change"
console.log(arr1)
console.log(arr2)

如上,通过对数组的for循环,即可实现对数组的深拷贝了。

slice 方法实现数组的深拷贝

这个代码实现非常简单。原理也比较好理解,他是将原数组中抽离部分出来形成一个新数组。我们只要设置为抽离全部,即可完成数组的深拷贝。代码如下:

let arr = [1,2,3,4,5]
let arr2 = arr.slice(0)
arr[2] = "change"
console.log(arr)
console.log(arr2)

concat 方法实现数组的深拷贝

这个代码也非常简单,原理更加粗暴。它是用于连接多个数组组成一个新的数组的方法。那么,我们只要连接它自己,即可完成数组的深拷贝。代码如下:

let arr = [1,2,3,4,5]
let arr2 = arr.concat()
arr[2] = 5
console.log(arr)
console.log(arr2)

五、编码实战

六、拓展思考

实现数组深拷贝的简单方式

ES6扩展运算符实现数组的深拷贝

ES6扩展运算符实现数组的深拷贝

OK,以上之前讲的方法全部过时了,用下面的方法实现数组的深拷贝是最简单的。

let arr = [1,2,3,4,5]
let [ ...arr2 ] = arr
arr[2] = 5
console.log(arr)
console.log(arr2)

七、参考文献

https://blog.****.net/fungleo/article/details/54931379

八、更多讨论

讨论1:浅拷贝和深拷贝的区别

浅拷贝是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对引用进行拷贝,没有对引用指向的对象进行拷贝。而深拷贝是指在拷贝对象时,同时会对引用指向的对象进行拷贝。区别就在于是否对 对象中的引用变量所指向的对象进行拷贝。

讨论2:如何选择使用深拷贝还是浅拷贝

如果对象的属性全是基本类型的,那么可以使用浅拷贝,但是如果对象有引用属性,那就要基于具体的需求来选择浅拷贝还是深拷贝。我的意思是如果对象引用任何时候都不会被改变,那么没必要使用深拷贝,只需要使用浅拷贝就行了。如果对象引用经常改变,那么就要使用深拷贝。没有一成不变的规则,一切都取决于具体需求。

讨论3:深拷贝的其他办法

json.parse(),json.stringify()方法实现深拷贝。

今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~