专栏首页前端进阶循环中的异步&&循环中的闭包

循环中的异步&&循环中的闭包

for循环中let 和var的区别

var 是函数级作用域或者全局作用域,let是块级作用域 看一个例子

    function foo() {
      for (var index = 0; index < array.length; index++) {
        //..循环中的逻辑代码
      }
      console.log(index);//=>5
    }
    foo()
   console.log(index)//Uncaught ReferenceError: index is not defined

foo函数下的index输出5,全局下的index不存在 现在我们把var 换为let

    function foo() {
      for (let index = 0; index < array.length; index++) {
        //..循环中的逻辑代码
      }
      console.log(index)//Uncaught ReferenceError: index is not defined
    }
    foo()

报错了,index不在foo函数作用域下,当然肯定也不会再全局下 因为var和let的这个区别(当然var和let的区别不止于此)所以导致了下面的这个问题 关于var的

    const array = [1, 2, 3, 4, 5]
    function foo() {
      for (var index = 0; index < array.length; index++) {
        setTimeout(() => {
          console.log(index);
        }, 1000);
      }
    }
    foo()

看下输出

关于let的

    const array = [1, 2, 3, 4, 5]
    function foo() {
      for (let index = 0; index < array.length; index++) {
        setTimeout(() => {
          console.log(index);
        }, 1000);
      }
    }
    foo()

看下输出

因为var和let 在作用域上的差别,所以到这了上面的问题 使用var 定义变量的时候,作用域是在foo函数下,在for循环外部,在整个循环中是全局的,每一次的循环实际上是为index赋值,循环一次赋值一次,5次循环完成,index最后的结果赋值就为5;就是被最终赋值的index,就是5; let的作用局的块级作用局,index的作用域在for循环内部,即每次循环的index的作用域就是本次循环,下一次循环重新定义变量index;所以index每次循环的输出都不同 这里还有另外一个问题,setTimeout,这是一个异步,这就是我们今天要讨论的

循环中的异步

setTimeout(func,time)函数运行机制

setTimeout(func,time)是在time(毫秒单位)时间后执行func函数。浏览器引擎按顺序执行程序,遇到setTimeout会将func函数放到执行队列中,等到主程序执行完毕之后,才开始从执行队列(队列中可能有多个待执行的func函数)中按照time延时时间的先后顺序取出来func并执行。即使time=0,也会等主程序运行完之后,才会执行。

一个需求,一个数组array[1,2,3,4,5],循环打印,间隔1秒

上面的let是循环打印了12345,但是不是间隔1s打印的,是在foo函数执行1s后,同时打印的

方式一 放弃for循环,使用setInterval

    function foo(){
      let index = 0;
      const array = [1, 2, 3, 4, 5]

      const t = setInterval(()=>{
        if (index < array.length) {
          console.log(array[index]);
        }
        index++;
      }, 1000);

      if (index >= array.length) {
        clearInterval(t);
      }
    }
    foo()

我们上面说到,当for循环遇到了var,变量index的作用域在foo函数下,循环一次赋值一次,5次循环完成,index最后的结果赋值就为5;就是被最终赋值的index,就是5;

方式二,引入全局变量

代码执行顺序是,先同步执行for循环,再执行异步队列,在for循环执行完毕后,异步队列开始执行之前,index经过for循环的处理,变成了5。 所以我们引入一个全局变量j,使j在for循环执行完毕后,异步队列开始执行之前,依然是0,在异步执行时进行累加

    var j = 0;
    for (var index = 0; index < array.length; index++) {
      setTimeout(() => {
        console.log(j);
        j++;
      }, 1000 * index)
    }

方式三 for循环配合setTimeout(常规思路,不赘述,面试必备技能)

    const array = [1, 2, 3, 4, 5]
    function foo() {
      for (let index = 0; index < array.length; index++) {
        setTimeout(() => {
          console.log(index);
        }, 1000*index);
      }
    }
    foo()

方式四,通过闭包实现

开始讨论方式四之前我推荐先阅读一遍我之前写过一篇文章 谈一谈javascript作用域 我们对上面的问题再次分析,for循环同步执行,在for循环内部遇到了setTimeout,setTimeout是异步执行的,所以加入了异步队列,当同步的for循环执行完毕后,再去执行异步队列,setTimeout中有唯一的一个参数数index 方式三可行,是因为let是块级作用域,每次for执行都会创建新的变量index,for循环执行完毕后,异步执行之前,创建了5个独立的作用域,5个index变量,分别是0,1,2,3,4,相互独立,互不影响,输出了预期的结果 如果说每次循环都会生成一个独立的作用域用来保存index,问题就会得到解决,所以,我们通过闭包来实现

    const array = [1, 2, 3, 4, 5]

    function foo() {
      for (var index = 0; index < array.length; index++) {
        function fun(j) {
          setTimeout(function () {
            console.log(j);
          }, 1000 * j);
        }
        fun(index)
      }
    }
    foo()

setTimeout中的匿名回调函数中引用了函数fun中的局部变量j,所以当fun执行完毕后,变量j不会被释放,这就形成了闭包 当然我们可以对此进行一下优化

    const array = [1, 2, 3, 4, 5]

    function foo() {
      for (var index = 0; index < array.length; index++) {
        (function(j) {
          setTimeout(function () {
            console.log(j);
          }, 1000 * j);
        })(index)
      }
    }
    foo()

将foo函数改为匿名的立即执行函数,结果是相同的

总结

for循环本身是同步执行的,当在for循环中遇到了异步逻辑,异步就会进入异步队列,当for循环执行结束后,才会执行异步队列 当异步函数依赖于for循环中的索引时(一定是存在依赖关系的,不然不会再循环中调动异步函数)要考虑作用域的问题, 在ES6中使用let是最佳的选择, 当使用var时,可以考虑再引入一个索引来替代for循环中的索引,新的索引逻辑要在异步中处理 也可以使用闭包,模拟实现let 在实际开发过程中,循环调用异步函数,比demo要复杂,可能还会出现if和else判断等逻辑,具体的我们下次再续

参考 通过for循环每隔两秒按顺序打印出arr中的数字 setTimeOut和闭包 《你不知道的JavaScript》上卷

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 关于javascript的原型和原型链,看我就够了(三)

    原型对象有一个constructor属性,指向该原型对象对应的构造函数 foo 为什么有 constructor 属性?那是因为 foo 是 Foo 的实例。 ...

    陌上寒
  • vue开发微信商城项目总结之四--本地代理处理跨域问题

    之前处理跨域问题是通过jsonp,但是只有开发环境是跨域的,代码打包后上传到服务器便不再跨域,所以ajax在本地做了判断,判断是否是跨域,跨域则走jsonp否则...

    陌上寒
  • vue开发微信商城项目总结之六--关于vuex的思考

    项目开发初期,由于项目比较着急上线,前端的框架在选型上比较仓促,只是因为vue学习成本较低,就选了它,没有什么别的原因,

    陌上寒
  • Apache2为什么会自动加载index.php

    我直接输入域名后,Apache2自动加载了对应目录下的index.php, 这是怎么做到的?

    Jerry Wang
  • Python_二维数组

    py3study
  • 【AlexeyAB DarkNet框架解析】七,YOLOV1损失函数代码详解(detection_layer.c)

    灵魂拷问,你真的懂YOLOV1的损失函数吗?进一步,懂了损失函数,你清楚它的反向求导过程吗?为了解决这俩问题,本文就结合DarkNet中的YOLOV1的损失函数...

    BBuf
  • 学习模板(线段树)

    glm233
  • 封装Python列表实现多下标访问

    class MyArray(object): def __init__(self, values): #values can be of...

    Python小屋屋主
  • nginx 配置目录转发

    server { listen 80; autoindex off; server_name image.imooc.com; ...

    Dar_Alpha
  • 整数转罗马数字

    https://leetcode-cn.com/problems/integer-to-roman/

    zucchiniy

扫码关注云+社区

领取腾讯云代金券