zoukankan      html  css  js  c++  java
  • javascript中异步和闭包产生的困惑

    这里我不打算大谈特谈什么是异步,什么是闭包,这些内容在博客园都已经写的够多的了,但是这些内容出现的多,并不代表所有初学者都已经撑握了,所以我还是打算,用一个比较常见的示例来分析一下,或许能让对这个问题有困惑的同学有一种顿悟的感觉。我在上一篇博客《从一道面试题分析闭包>中已经分析过什么是闭包了,但是那个例子应用的场景比较复杂,不适合初学者理解,这里我举一个更常见的例子.

            假如有这样一个需求:点击菜单中的每一项,显示所点击的内容,对应的内容页面如下:

    <!DOCTYPE html>
    <html>
    <head>
    <title>test</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
     
    </head>
    <body>
     <ul>
         <li>list 0</li>
         <li>list 1</li>
         <li>list 2</li>
         <li>list 3</li>
         <li>list 4</li>
     </ul> 
     <script type="text/javascript">
    
     </script>
    </body>
    </html>

    我先来一段有问题的js代码,它是这样实现的:

      var items =  document.querySelectorAll('li');
      var len = items.length;
    
      for(var i =0;i<len;i++){
          items[i].onclick = function(){
              alert('list '+i)
          }
      }

    这时候,我发现每一个li标签都邦定了点击事件,看起来运行的很好,可是当我多点几个,发现问题就来了,无论我点哪个li标签,弹出的都是list 5.这又是什么原因呢?

    热心的网友马上给出回复说,因为弹出的i是循环之后的值。这样回答,那问题又来了:

    1 :既然弹出的是循环之后的值,为什么每一个li标签上又都邦上了事件呢?

    2 : 岂不是只有第5个li 元素可以邦上事件,而页面上根本不存这个元素,岂不是点击的时候,就该报错了呢?

    说明这样回复,不但没有解释清楚问题,反而增加了更多的疑问,难道不是吗?这里产生疑问的根本原因在于有一个异步过程。循环代码是同步的,而点击操作是异步的。

    我用一个游戏来演示这个过程:

    操场上站着4个小朋友(对应4个 li元素),老师挨个的告诉他们游戏规则(对应循环),规则是这样的:呆会老师会举一个小黑板,然后点你名字的时候,你就告诉老师黑板上写的是什么字。(对应onclick所设定的function)。游戏开始了,老师在黑板上写了1,觉得不好,改为2,接着又改成3,最后改成4.小朋友们看着老师改来改去,等了好久才开始游戏。

    可是无论点哪个小朋友,他回答的都是“4”。因为他们看到的都是老师最后写的那个数字。

    现在我们再回过来看刚才的代码。

     var items =  document.querySelectorAll('li');
      var len = items.length;
    
      for(var i =0;i<len;i++){
         //当i=0的时候,即items[0]所对应的元素,这是确定的。
        //items[i]这里执行的是一个取值操作
          items[i].onclick = function(){
              //而这里边的i却要等到这个函数运行时才能确定是多少
             //函数什么时候运行,肯定发生在循环之后了。因为点击的速度显然是比不过cpu运算的速度的
              alert('list '+i)
          }
      }

    刚才那个游戏,显然不是老师期望的。这次她把小黑板换成写好字的小纸片,挨个讲规则的时候顺便把纸片传给他们每一个人。这样每个小朋友手里都拿了一个属于自己的数字。老师点名字的时候,他们都报出了自己纸片上的数字。老师为自己的创意感到满意。

    那我们这个程序,要怎么把i做成小纸片事先传给每一个li元素呢?

    方法一:

    在li上做一个标记,点的时候取这个标记上的值。

      var items =  document.querySelectorAll('li');
      var len = items.length;
    
      for(var i =0;i<len;i++){
          items[i].setAttribute('i',i);
          items[i].onclick = function(){
              i = this.getAttribute("i");
              alert('list '+i)
          }
      }

    方法二:

      利用闭包的特性

      

      var items =  document.querySelectorAll('li');
      var len = items.length;
    
      for(var i =0;i<len;i++){
          items[i].onclick = (function(){
              var t = i;
              return function(){
                  alert('list '+t)
              } 
          })()
      }

    方法三:利用闭包比较难理解,我们换一个方式 

      var items =  document.querySelectorAll('li');
      var len = items.length;
    
      for(var i =0;i<len;i++){
           
          items[i].onclick = function(t){
                  alert('list '+t)
          }.bind(this,i)
      }

    总结一下:

    以上虽然用了三种形式的小纸片进行传参,但是目的都是为了保证在循环之后,每个li的回调函数上的参数都能确定下来。这种小纸片在解决异步问题上,是一个有用的技巧。

    关于异步的问题,还有很多,由于时间关系,就不再一一列举了,有兴趣的同学可以@我,一起学习。

    如果您觉得这文章对您有帮助,请点击【推荐一下】,想跟我一起学习吗?那就【关注】我吧!

  • 相关阅读:
    Kafka 设计思路
    DBeaver——超好用可视化数据库!(墙裂推荐(づ ̄3 ̄)づ╭❤~)
    蓝图BluePrint——基于Flask框架
    SkyWalking全链路监控java项目
    win10创建ssh公钥
    mysql通过列名搜索出表名
    使用nodejs判断前端性能
    golang 栈、堆分配分析及CPU、内存性能情况
    UML图-(用例图、类图、状态图、活动图、时序图)
    linux参数调优
  • 原文地址:https://www.cnblogs.com/afrog/p/4069234.html
Copyright © 2011-2022 走看看