博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端
文章目录
💯前言
- 在编写 JavaScript 程序时,我们经常会遇到一些看似简单但实际隐藏了深层次概念的问题。这次,我们深入探讨了关于数组赋值与引用共享的问题,具体来说,是如何处理数组在多个循环中引用变化的问题。这篇文章将详细叙述我们对问题的逐步分析、理解,以及最终得出正确解决方案的全过程。 JavaScript
💯问题描述:数组赋值与共享引用的困惑
在我们最初编写的代码中,我们试图实现一个数组元素的偏移功能。代码结构如下:
var arr =['a','b','c','d','e'];for(var j =1; j <=15; j++){var arr2 =[];// 在每次外层循环中初始化 arr2// 内层循环:对 arr 的元素进行偏移for(var i =0; i < arr.length; i++){
arr2[(i +2)% arr.length]= arr[i];}
arr = arr2;// 将 arr2 的引用赋值给 arr
console.log("第", j +1,"次打印结果是:", arr2);}
在这段代码中,我们遇到了一个关键性的问题:**为何数组
arr
在第二次偏移开始时会受到之前的数组状态影响**?更具体来说,当
arr2
没有在每次外层循环中被重置为空数组时,结果会混合了新旧数据,导致程序输出不符合预期。
在探索这个问题的过程中,我们深入探讨了 JavaScript 中引用类型的特性以及数组赋值的行为,最终理解了代码背后深层次的运作逻辑。
💯深入分析:为什么需要重置
arr2
在最初的讨论中,我们以为每次内层
for
循环都会重新填充
arr2
,因此即使没有对
arr2
进行重置,它也应该在每次迭代中被覆盖掉。然而,实际情况并非如此。接下来我们深入分析为什么每次外层循环中必须对
arr2
进行重置。
JavaScript 中的引用类型
首先,需要理解 JavaScript 中的引用类型和值类型之间的区别。
- 值类型:基本类型的数据(如
number
、string
、boolean
等)都是通过值来进行存储的。当你将一个值类型变量赋值给另一个变量时,实际上是将值进行了拷贝,因此两个变量互不影响。 - 引用类型:对象(包括数组)是通过引用来进行操作的。当你将一个引用类型赋值给另一个变量时,实际上是将这个引用(也就是内存地址)传递过去,因此它们指向的是同一个内存位置,对其中一个的修改会反映在另一个上。
在我们的代码中,数组
arr
和
arr2
都是引用类型,因此
arr = arr2
实际上是将
arr2
的引用赋值给
arr
,使得
arr
和
arr2
指向同一个数组对象。
共享引用导致的问题
考虑以下代码片段:
arr = arr2;// 在每次外层循环结束后执行
执行这行代码之后,
arr
和
arr2
就指向了同一个内存地址,这意味着它们是共享引用的。因此,当你在接下来的内层循环中对
arr2
进行赋值时,
arr
的内容也会随之改变。
假设我们没有对
arr2
进行重置,而是直接在内层循环中继续修改
arr2
,因为
arr
和
arr2
引用了相同的数组对象,导致
arr2
的变化会同时体现在
arr
上,这就产生了数据污染的问题,即
arr2
中混合了之前的值和当前的值。
残留数据的影响
具体来说,如果不对
arr2
进行重置,那么在内层循环中只对数组的某些索引位置进行更新,剩下的索引位置会保留之前的值。这些“残留数据”会导致我们得到一个包含新旧数据混合的数组,无法得到我们预期的结果。
💯正确的解决方案:重置
arr2
为了解决这个问题,我们需要确保每次进入外层循环时,
arr2
都是一个全新的空数组,这可以通过在每次外层循环开始时对
arr2
进行初始化来实现:
var arr2 =[];// 在每次外层循环中重置 arr2
这样做的好处是:
- 每次内层循环执行时,
arr2
都是干净的,不会受到上一次迭代的影响。 - 避免了共享引用导致的数据污染问题,确保每次偏移计算都是基于上一轮结果的独立计算。
💯引用类型与深拷贝的探讨
在解决了
arr
和
arr2
共享引用的问题之后,我们进一步讨论了如何避免这种引用共享带来的问题。一个典型的解决方案是使用深拷贝。
深拷贝与浅拷贝的区别
- 浅拷贝:只是拷贝对象的第一层属性,对于嵌套的引用类型数据,浅拷贝只拷贝引用,而不是实际的对象本身。使用
Object.assign
或扩展运算符(...
)可以实现浅拷贝。 - 深拷贝:完全拷贝整个对象,包括嵌套的引用类型。深拷贝可以使用
JSON.parse(JSON.stringify(obj))
或者其他库(如 Lodash 的cloneDeep
)来实现。
在我们的例子中,如果不想在每次外层循环中重新初始化
arr2
,我们也可以选择在赋值时对
arr2
进行深拷贝,从而确保
arr
和
arr2
不再共享引用。
例如:
arr =[...arr2];// 使用扩展运算符实现浅拷贝,适用于数组
或者使用其他深拷贝的方式确保
arr
是
arr2
的一个独立副本,而不是它的引用。
💯代码优化与最终理解
经过多次尝试和讨论,我们最终得出了如下改进的代码:
var arr =['a','b','c','d','e'];for(var j =1; j <=15; j++){var arr2 =[];// 每次外层循环重置 arr2// 内层循环:对 arr 的元素进行偏移for(var i =0; i < arr.length; i++){
arr2[(i +2)% arr.length]= arr[i];}
arr =[...arr2];// 使用浅拷贝,避免共享引用问题
console.log("第", j +1,"次打印结果是:", arr);}
通过这种方式,我们确保每次外层循环中,
arr2
是干净的,并且在赋值给
arr
时,
arr
是
arr2
的一个副本,从而彻底解决了共享引用带来的问题。
💯小结
- 这次问题的探讨和解决过程让我们对 JavaScript 的引用类型有了更深刻的理解。我们了解到:
- 引用类型的赋值是通过引用进行的,因此在处理对象和数组时,必须特别小心共享引用的问题,避免多个变量指向同一个内存位置而导致数据混淆。
- 在循环中处理数组时,重置数组或者进行深拷贝是确保数据干净的有效手段,这可以避免残留数据对下一次迭代的影响。
- 调试器的实时监控可能会引发一些困惑,因为它显示的是当前引用指向的内容,而不是每一轮循环中的静态快照。这也让我们理解了在调试时关注代码执行流程的重要性。
问题解决过程记录
- 从代码来看,困惑的点是为什么要在每次循环重置arr2,一开始认为在下面的内层for循环中,arr2只充当了一个变量容器的身份,用来接受arr的赋值,明明只有for循环结束以后才会作为传值赋值给arr,那重置不重置有什么影响呢?就算没有重置,那五个值不也会被5次for循环全部更新吗?
- 尝试修改不重置,结果不符合预期。
- 尝试通过调试发现,for循环过程中arr也发生改变。想明白了数组变量是引用类型,而一开始把数组变量当成了值类型,困惑解决。
版权归原作者 小ᶻ☡꙳ᵃⁱᵍᶜ꙳ 所有, 如有侵权,请联系我们删除。