深入理解js_for循环条件中使用var为什么会出问题?(js块级作用域理解)

初学JavaScript时,都会听前辈建议不要用var声明变量,es6以后要用let,道理是啥也没当回事,直到for循环时候才发现循环条件for(var i=0;i<arr.length;i++)中的i是一个i,这就导致做点击事件的时候,无论谁点击,结果i都是退出循环后最终的i固定不变。其实这个问题在C#中压根不是问题........最后查了下,再加上技术群大佬们的指点,终于理解了原因。接下来就举例子给大家详细说明下。


开门见山,先说核心结论。若不理解请继续往后读,相信读完全文再回头看你一定会深有体会。

1.for循环是同步的,内部事件处理函数是异步的,所以等到异步事件触发的时候,同步代码已经跑完,因此i也加完了(这就是为什么i固定为最大值不变的原因)。

2.处理的核心思想就是在他跑完前就立即记录i的值,以备异步事件处理函数使用。

3.说白了就是利用var只有函数作用域,制造独立空间,让每个i都是独立的。


目录

1.需求介绍

2.ES6以前常规处理代码(var)

3.原因分析

4.解决办法

4.1 es6方法--let

4.2 自执行函数方法

4.3添加自定义属性固定住i

4.4给事件处理函数里面加一个for循环

4.5扩展

5总结


 1.需求介绍

ui界面:

代码:

说明:

获取三个button按钮,结果放入一个htmlcollection数组中,通过for循环处理,当点击第一个button时,弹出i=0,第二个弹出i=1,第三个弹出i=2.

2.ES6以前常规处理代码(var)

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button type="button" class="btn" myTag='0'>aaa</button>
    <button type="button" class="btn" myTag='1'>bbb</button>
    <button type="button" class="btn" myTag='2'>ccc</button>
</body>
</html>
<script>
//常规处理方法
 var btn=document.querySelectorAll('button');
    for (var i = 0; i < btn.length; i++) {  
        btn[i].onclick=function(){
            alert(i);
        }
    }
</script>

这要是C#,绝对啥问题没有,点击aaa弹出0,点击bbb弹出1,点击ccc弹出2.可现在结果却是你无论点击那个按钮,弹出的都是3.具体看下图:

3.原因分析

原因:

可为什么会出现这种情况呢,要想弄清,必然要先来了解下“块级作用域的概念”

块级作用域:

广义的讲:{ }内就算一个块级作用域。

所以if判断、for循环、function函数等这些有{ }包围的地方就可以都算作是块级作用域。

但是注意:var没有块级作用域,他只有函数作用域。就是说var只有在function{ }内部才有作用域的概念,其他地方则没有这个概念。这意味着在函数function以外用var定义的变量是同一个,你所有的修改其实都是针对他的。

分析:

因此for循环虽然循环了3次,但是循环变量i一直是一个,并不是3个独立的i。同时因为事件处理函数是异步的(不知道用户什么时候点击按钮),for循环的同步的。

代码一进入for循环,就会立即将每一个button按钮都加上事件,i也在自己循环中,循环很快跑完了,i就是3。这个时候当用户要点击的时候,自然就是上面的结果:无论点击谁,弹出的都是3........................

4.解决办法

4.1 es6方法--let

先说es6方法,这个方法最简单最优秀,并且以后也会一直是这个方法,就是使用let,因为let具有块级作用域的概念,所以为了少麻烦,以后都是用的let,var就已经退出历史了。(除非有人像我一样想研究下老办法是咋回事)。

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button type="button" class="btn" myTag='0'>aaa</button>
    <button type="button" class="btn" myTag='1'>bbb</button>
    <button type="button" class="btn" myTag='2'>ccc</button>
</body>
</html>
<script>
    //[2]使用let
    let btn=document.querySelectorAll('button');
    for (let i = 0; i < btn.length; i++) {
        btn[i].onclick=function(){
            alert(i);
        }
    }
</script>

将循环变量的var改成let声明,因为let具备块级作用域的概念,所以循环三次相当于3个独立的函数,每个i都是不同得。

4.2 自执行函数方法

前面已经分析了原因:

①因为var没有块级作用域。

②因为点击事件是异步的。

既然知道了原因,那我们就利用他加以注意就好了。

 //[1]使用自执行函数
    var btn=document.getElementsByTagName('button');
    for (var i = 0; i < btn.length; i++) {
        (function(i){
            btn[i].onclick=function(){
                alert(i);
            }
        })(i)
    }

既然var没有块级作用域,只有函数function作用域,那再循环内我们就将事件包在function内,并将i作为参数传入function内,这样每次的i就是不同。达到和let一样的效果。

4.3添加自定义属性固定住i

这类方法的核心思路就是在标签中固定住i,这样每个标签的i自然是不同得,通过这个i就可以调用各自内容。

这里this的使用需要注意:请看我这篇文章:

https://blog.csdn.net/qq_42539194/article/details/113918593

 //[3]给类数组元素加自定义属性记录同步值
    var btn=document.getElementsByClassName('btn');
    for (var i = 0; i < btn.length; i++) {
        btn[i].setAttribute('num',i);
        btn[i].onclick=function(){
            alert(this.getAttribute('num'));
        }
    }

4.4给事件处理函数里面加一个for循环

类似于4.3方法。

//[4]给事件处理函数里面加一个for循环,for的变量j再和事件源this做对比筛选出来当前的i
    var btn=document.querySelectorAll('button');
    for (var i=0; i < btn.length; i++) {
        btn[i].onclick=function(){
            for (var j = 0; j < i; j++) {
                if (this.getAttribute('myTag')==j) {
                    alert(j);
                    continue;
                }
            }  
        }
    }

通过这四个方法的举例,不知道大家有没有发现核心解决办法我的总结就是:

固定住i

①通过制造独立function空间,来固定

②可以利用标签属性进行固定,

③................................

4.5扩展

通过上面的分析,相信你已经明白了。那么请看这种处理可以么?

var btn=document.querySelectorAll('button');
    for (var i = 0; i < btn.length; i++) {
         btn[i].onclick=function(){
             myClick(i)();
        }
    }
    function myClick(i){
       return function(){
            alert(i);
       };
    }

答案是否定的,因为事件处理函数是异步的,当你点击的时候,i已经跑完了,而你并没有及时固定住i(虽然循环体内有三个function函数,但是函数并没有执行,也没有进入里面,其内部并没有调用执行myClick函数,因此i没传出去,自然没有固定),所以当用户点击的时候,函数执行,并且参数一直都会是3不变。

这个并没有营造独立运行空间,因为你要明白,事件处理函数只有用户触发了才会执行。

将上面的代码稍加调整,你看可以么?

 var btn=document.querySelectorAll('button');
    for (var i = 0; i < btn.length; i++) {
        btn[i].onclick=myClick(i);
    }
    function myClick(i){
       return function(){
            alert(i);
       };
    }

答案是可以,这种方法就是独立了三个function函数,并且每一次都传递出来一个i并调用一次myClick方法。看到这里你懂了么?

5总结

对于var的理解还是应该上手操练下,虽然es6后let使用,你可以不用管这些,但是通过这么一顿折腾,一定会加深你的理解。

  • 40
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

韦_恩

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

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

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

打赏作者

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

抵扣说明:

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

余额充值