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

时间: 2019-03-04阅读: 811标签: 闭包

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》上卷


原文链接

 

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/2202

关闭

js闭包问题

使用闭包能够让局部变量模拟全局变量一样,但是它只能被特定函数使用。我们都知道:1.全局变量可能会造成命名冲突,使用闭包不用担心这个问题,因为它是私有化,加强了封装性,这样保护变量的安全

Js中的闭包与高级函数

在JavaScript中,函数是一等公民。JavaScript是一门面向对象的编程语言,但是同时也有很多函数式编程的特性,如Lambda表达式,闭包,高阶函数等,函数式编程时一种编程范式。

深入了解JavaScript的闭包机制

这篇文章主要介绍了JavaScript的闭包机制,针对内嵌函数的变量访问等问题分析了JS的闭包,写的十分的全面细致,具有一定的参考价值,JavaScript 变量可以是局部变量或全局变量。 私有变量可以用到闭包。

深入理解Js闭包

《JavaScript权威指南》:函数对象可以通过作用域链相互关联起来,函数体内部的变量可以保存在函数作用域内,这种特性称为“闭包”。不好理解?那就通俗点讲:所谓闭包,就是一个函数,这个函数能够访问其他函数的作用域中的变量。

前端开发闭包理解,JavaScript-闭包

闭包(closure)是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠...

我终于理解了闭包

闭包这个词一听就很高级,令人害怕。但实际上,闭包非常的强大,JS 的精髓之一就是闭包。实际上,我们经常在使用闭包,而不自知!

Js闭包的实现原理和作用

闭包的概念:指有权访问另一个函数作用域中的变量的函数,一般情况就是在一个函数中包含另一个函数。闭包的作用:访问函数内部变量、保持函数在环境中一直存在,不会被垃圾回收机制处理

带你一分钟理解 JavaScript 闭包

闭包就是一个函数引用另外一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。这是优点也是缺点,不必要的闭包只会徒增内存消耗!另外使用闭包也要注意变量的值是否符合你的要求

闭包实现:异步变同步

在不使用ES6的前提下如何将一个多个异步请求按顺序执行呢?要求使用JavaScript代码按顺序依次请求这5张图片,一次只能请求一张,可以结合 闭包+回调+递归 组合来解决

搞懂JS闭包

闭包(Closure)是JS比较难懂的一个东西,或者说别人说的难以理解, 本文将以简洁的语言+面试题来深入浅出地介绍一下。在将闭包之前,需要先讲一下作用域。JS中有全局作用域和局部作用域两种。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!