0


【前端】JavaScript 引用变量赋值行为与共享引用的深度剖析


在这里插入图片描述
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端


文章目录


在这里插入图片描述


💯前言

  • 在编写 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 中的引用类型值类型之间的区别。

  • 值类型:基本类型的数据(如 numberstringboolean 等)都是通过值来进行存储的。当你将一个值类型变量赋值给另一个变量时,实际上是将值进行了拷贝,因此两个变量互不影响。
  • 引用类型:对象(包括数组)是通过引用来进行操作的。当你将一个引用类型赋值给另一个变量时,实际上是将这个引用(也就是内存地址)传递过去,因此它们指向的是同一个内存位置,对其中一个的修改会反映在另一个上。

在我们的代码中,数组

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 的引用类型有了更深刻的理解。我们了解到:
  • 引用类型的赋值是通过引用进行的,因此在处理对象和数组时,必须特别小心共享引用的问题,避免多个变量指向同一个内存位置而导致数据混淆。
  • 在循环中处理数组时,重置数组或者进行深拷贝是确保数据干净的有效手段,这可以避免残留数据对下一次迭代的影响。
  • 调试器的实时监控可能会引发一些困惑,因为它显示的是当前引用指向的内容,而不是每一轮循环中的静态快照。这也让我们理解了在调试时关注代码执行流程的重要性。

问题解决过程记录

  1. 从代码来看,困惑的点是为什么要在每次循环重置arr2,一开始认为在下面的内层for循环中,arr2只充当了一个变量容器的身份,用来接受arr的赋值,明明只有for循环结束以后才会作为传值赋值给arr,那重置不重置有什么影响呢?就算没有重置,那五个值不也会被5次for循环全部更新吗?在这里插入图片描述
  2. 尝试修改不重置,结果不符合预期。在这里插入图片描述在这里插入图片描述
  3. 尝试通过调试发现,for循环过程中arr也发生改变。想明白了数组变量是引用类型,而一开始把数组变量当成了值类型,困惑解决。在这里插入图片描述

在这里插入图片描述



本文转载自: https://blog.csdn.net/2201_75539691/article/details/143928825
版权归原作者 小ᶻ☡꙳ᵃⁱᵍᶜ꙳ 所有, 如有侵权,请联系我们删除。

“【前端】JavaScript 引用变量赋值行为与共享引用的深度剖析”的评论:

还没有评论