想要一个五星好评咋就这么难

作者在学习JavaScript时遇到一个小案例,需实现鼠标悬停和点击星星改变显示效果。问题在于,由于使用`var`在循环中创建闭包导致index值始终为5,通过改用`let`解决了问题。文章详细解释了闭包的工作原理并给出了代码修正。
摘要由CSDN通过智能技术生成

前言

最近在学JS,在做一个用户通过点击星星来进行量化评价的小案例时,出现了点小问题,我把我的案例简化一下聚焦在我出现的小问题上。

这里要实现的效果就是用户将鼠标悬停在星星图案上,悬停的星星以及它左边的星星图案都会被点亮。
鼠标点击星星图案后,星星悬停点亮效果将不会出现,
然后点击按钮一个循环确认星星数,即可提交评价。

具体效果可以参照下图
这不小菜一碟吗?但在实现这个案例时,我却犯了一个致命性的错误。
请添加图片描述

先用HTML和CSS写出静态内容

这里面的star0.png表示星星熄灭状态的图片,
star1.png则表示星星点亮状态的图片。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>五星好评</title>
        <style>
            .main{
                margin: 0px;
                padding: 0px;
            }
            #stars{
                border-radius: 0.5rem;
                width:100%;
                height:auto;
            }
            img{
                width:100px;
                height:100px; 
            }
            ![请添加图片描述](https://img-blog.csdnimg.cn/direct/b2495e8734e0484e856f7ea3dadd2743.gif)

            
        </style>
    </head>
    <body>
        <div class="main">
        <div id="stars_label">
            <img src="star0.png">
            <img src="star0.png">
            <img src="star0.png">
            <img src="star0.png">
            <img src="star0.png">
        </div>
        <input type="button" value="提交评价" id="btn">
        <label id="display">
            <strong>您的评价是:<span id="assess"></span></strong>
        </label>
        </div>
    </body>
 </html>

编写JS代码实现动态效果

我最开始的思路是:

在窗口加载完毕后,开始循环这五个img元素的onmouseover(鼠标悬停)事件。

一旦鼠标在某个img元素上悬停,就会根据悬停元素在img元素集合的索引,通过替换src属性实现图片切换达到点亮效果。

在悬停时,不会立即触发图片切换按钮,而是先通过unsure判断是否有进行过img元素单击事件(案例中规定鼠标单击后就表示已确定评价,固定点亮星星数),如已经进行,则无需实现悬停效果。

<script>
		//获取到五个图片元素的集合
	   starImgs=document.getElementsByTagName("img");
       mySpan=document.getElementById("assess");
       myBtn=document.getElementById("btn");
       
       window.onload=function(){
       		//设定初始时,未确定评价状态为真
            unsure=true;
            for(var index=0;index<starImgs.length;index++){
                starImgs[index].onmouseover=function(){
                    if(unsure){//判断是否为未确定评价状态
                        lightStars(index);//根据索引触发切换图片的函数
                    }
                }
            }
            //单击星星事件,表示确认评价
            for(let index=0;index<starImgs.length;index++){
                starImgs[index].onclick=function(){
                    sureStars(index);
                }
            }
        }
        function lightStars(img_index){
            for(var i=0;i<=img_index;i++){
            	//点亮从左至悬停处的星星
                starImgs[i].src="star1.png";            }
            for(;i<starImgs.length;i++){
                // 熄灭悬停处右边的星星
                starImgs[i].src="star0.png";
            }
           
        }
        function sureStars(stars){
            unsure=false;//设置为false,即已确定评价状态取消鼠标悬停触发的函数
            starImgs.data=stars+1;//设置数据,也就是分数
            
        }
        myBtn.onclick=function(){
            mySpan.innerHTML=starImgs.data+"颗星星";
        }
 <script>

代码问题

这个思路虽然是笨了一点,但按道理来说实现案例效果是没有问题的。

在浏览器加载网页的时候,我们可以看到,它并没有我文章开头展示的效果,而且JS还报了错。
请添加图片描述


具体报错图片

一般报这个错我自己的经验就是两种原因,
要么就是对象压根就没这个属性,无法更改该属性;
要么是对象压根没正确获取;
在这里插入图片描述


进入报错行:

我可以保证我的img元素是被正常获取到的,所以src这个属性是绝对会有的,
那么只可能是第二个原因了,没有正常获取到对象,也就是说starImgs[i]不存在。
在这里插入图片描述


进入调试:

我发现,无论我的鼠标悬停到哪个星星上,它传给img_index的值永远是5,这明显有问题啊!一共就五颗星星,按照索引来说,就算是悬停到第五颗星星的地方,传入的参数值也只能是4啊
在这里插入图片描述


找到传入参数值的地方:

乍一看,我还真没看出什么问题来,
所以开始我是拿foreach以另一种方式去解决这个传入参数值总是5的问题,也确实轻轻松松地就解决了。
后来我越想越不对劲,又看了看这段已经要被我删除的代码,终于看到了出错的地方。

for(var index=0;index<starImgs.length;index++){
      starImgs[index].onmouseover=function(){
          if(unsure){//判断是否为未确定评价状态
              lightStars(index);//根据索引触发切换图片的函数
          }
      }
  }

出错问题类型

代码中存在一个常见问题,这与循环中的 index 变量的闭包有关。

在JavaScript中,闭包捕获的是变量最终值,而不是闭包创建时变量的值。因此,当 onmouseover事件触发时,它总是引用 index 的最后一个值,而不是创建事件处理程序时的值。


什么是闭包

通俗点来讲,当一个函数内部定义的函数可以访问到外部函数的变量时,这个内部函数就是一个闭包。闭包就像是一个背包,可以携带着外部函数的变量,即使外部函数已经执行结束了,内部函数仍然可以使用这些变量。所以,闭包可以让我们在函数外部访问函数内部的数据,就像是在一个安全的“背包”里面携带着需要的东西一样。


代码具体问题

for(var index=0;index<starImgs.length;index++){
      starImgs[index].onmouseover=function(){
          if(unsure){//判断是否为未确定评价状态
              lightStars(index);//根据索引触发切换图片的函数
          }
      }
  }

当在循环中创建闭包时,闭包内部的函数(在这里是 onmouseover事件处理函数)引用的是循环结束后的变量值。在给每个图片元素绑定onmouseover 事件处理函数时,index 的值在每次循环中都会递增,并且闭包(onmouseover 事件处理函数)会捕获到最终的 index 值。

当循环结束后,index 的值会变成 5,因为它是循环结束时的最终值。
当任何一个图片元素触发 onmouseover 事件时,它调用的是同一个事件处理函数,而这个函数内部引用的 index 变量已经是 5。

即所有图片元素触发 onmouseover事件时都会执行 lightStars(5)。

因此,尽管我们希望每个图片元素触发 onmouseover 事件时都只执行对应的 lightStars 函数,但实际上它们都会执行 lightStars(5),因为它们共享了同一个闭包,这个闭包中的 ``index 已经被设置成了 5。

解决办法

通过在循环中使用 let 而不是 var 来声明 index 变量,循环的每次迭代都会有自己的 index 变量副本,这样事件处理程序中的闭包就能正确捕获。

for(let index=0;index<starImgs.length;index++){
      starImgs[index].onmouseover=function(){
          if(unsure){//判断是否为未确定评价状态
              lightStars(index);//根据索引触发切换图片的函数
          }
      }
  }

然后就能达到开头的效果实现五星好评了。
请添加图片描述

let和var的区别

说真的,看那么多let和var的区别还是没有自己去踩一次坑深刻😅
请添加图片描述

在JavaScript中,let 和 var 都用于声明变量,但它们之间有一些关键区别:


作用域:

var 声明的变量的作用域是函数作用域或全局作用域。如果在函数内部声明的变量,它只在该函数内部有效;如果在函数外部声明的变量,它则在全局范围内有效。

let 声明的变量的作用域是块级作用域。块级作用域通常由花括号({})定义,如 if 语句、for 循环、while 循环等。在块级作用域内部声明的变量只在该块内部有效。


变量提升:

使用 var 声明的变量存在变量提升(hoisting)现象,即变量可以在声明之前使用,但值为 undefined。这是因为在执行代码之前,JavaScript 会将 var 声明的变量提升到函数或全局作用域的顶部。

使用 let 声明的变量也存在变量提升,但它们不会被初始化为 undefined,而是保持在“暂时性死区”(Temporal Dead Zone,TDZ)中,直到执行到声明的位置才能被访问。


重复声明

使用 var 声明的变量可以被重复声明,而不会报错。

使用 let 声明的变量在同一作用域内不能被重复声明,否则会抛出 SyntaxError。


全局对象属性

使用 var 声明的全局变量会成为全局对象(window 或 global)的属性。

使用 let 声明的全局变量不会成为全局对象的属性。

总结

循环中,慎用var,用let还是稍微靠谱点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗不丢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值