49

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

 5 years ago
source link: https://www.qdtalk.com/2019/03/03/0303-1/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在这之前先要了解一下

for循环中let 和var的区别

目录

  • for循环中let 和var的区别
    • setTimeout(func,time)函数运行机制
    • 一个需求,一个数组array[1,2,3,4,5],循环打印,间隔1秒
      • 方式一 放弃for循环,使用setInterval
      • 方式三 for循环配合setTimeout(常规思路,不赘述,面试必备技能)

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()

看下输出

QzmiiiN.png!web

关于let的

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

看下输出

Mjq6far.png!web

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK