【问题标题】:Unexpected behavior: Javascript, setTimeout(), and IIFE意外行为:Javascript、setTimeout() 和 IIFE
【发布时间】:2019-01-02 02:30:38
【问题描述】:

Javascript、事件循环、setTimeout、IIFE、闭包

根据下面的参考资料,我对以下代码的理解是:

setTimeout() 是非阻塞的,由浏览器 Web API 处理,当计时器完成时,它会将回调放在回调队列中。然后事件循环等待调用堆栈空闲来依次运行每个回调。 setTimeout 闭包关闭匿名 IIFE,并且每次迭代都有正确的 index 值。

for(var i = 0; i < 3; i++){
    (function(index){
        setTimeout(function(){
            console.log(index);
        }, 5000);
    })(i);
    console.log("loop="+i);
}
/*Output in console is
loop=0
loop=1
loop=2
//after 5 seconds
0
1
2
*/

我正在寻找有关 Chrome 中以下代码发生了什么的解释。

for (var i = 0; i < 3; i++) {
    setTimeout(
        function(index) { 
            console.log(index);
        }(i), 5000
    );
    console.log("loop="+i);
}
/* Output in console without any delay is:
0
loop=0
1
loop=1
2
loop=2
*/

为什么 'console.log(index)' 立即执行,没有 5 秒的延迟?

Web API 如何使用回调作为 IIFE 执行 setTimeout()?

回调队列中是否有任何回调?

事件循环是否将任何回调移动到调用堆栈?

或者是 setTimeout() 被忽略并且它的回调被立即在调用堆栈上执行?


我查阅过的参考资料:

Philip Roberts:事件循环到底是什么? | JSConf 欧盟 2014 https://www.youtube.com/watch?v=8aGhZQkoFbQ

Philip Roberts 帮助我陷入了事件循环 2016 https://www.youtube.com/watch?v=6MXRNXXgP_0

调用堆栈和事件循环 https://www.youtube.com/watch?v=mk0lu9MKBto

JavaScript closure inside loops – simple practical example

Use IIFE in setTimeout in a loop, but why?

【问题讨论】:

  • 附带说明,如果您想将参数传递给 setTimeout,您需要将它们放在延迟参数之后,例如setTimeout(function (index) { /* 做事 */ }, 5000, i);

标签: javascript closures settimeout iife


【解决方案1】:

setTimeout(
    function(index) { 
        console.log(index);
    }(i), 5000
);

您正在调用传递给setTimeout的第一个参数立即。当解释器遇到setTimeout 行时,它首先尝试将其所有参数解析为值。第一个参数是一个函数调用,因此它调用该函数的期望是它会解析为 另一个 函数 - 就像一个人可以做的那样

setTimeout(makeFn('foo'), 5000);

makeFn 返回一个函数。

所以,在你的代码中,

    function(index) { 
        console.log(index);
    }(i)

立即运行,但不返回任何内容 - 解释器将 setTimeout 行解析为

setTimeout(undefined, 5000);

但是undefined 不是一个函数,所以没有任何异步被排队。

这里没有任何 IIFE - 将整个 setTimeout 行放在 IIFE 中:

for (var i = 0; i < 3; i++) {
  ((i) => {
    setTimeout(
      function() {
        console.log(i);
      }, 500
    );
    console.log("loop=" + i);
  })(i);
}

(或者,当然,使用constlet 而不是var - 最好避免使用var,它的提升和函数范围非常不直观,并且需要像for 循环中这样的冗长解决方法)

for (let i = 0; i < 3; i++) {
  setTimeout(
    function() {
      console.log(i);
    }, 500
  );
  console.log("loop=" + i);
}

【讨论】:

    【解决方案2】:

    在第二个示例中,您没有将函数传递给 setTimeout,而是将其传递给函数调用的结果(在本例中为 void)。

            function(index) { 
                console.log(index);
            }(i)
    

    你看,在这个例子中你的函数是立即调用的,以后没有什么可调用的,控制台日志是按顺序的。

    【讨论】:

      【解决方案3】:

      我正在寻找对以下情况的解释 Chrome 中的代码。

      for (var i = 0; i < 3; i++) {
          setTimeout(
              function(index) { 
                  console.log(index);
              }(i), 5000
          );
          console.log("loop="+i);
      }
      /* Output in console without any delay is:
      0
      loop=0
      1
      loop=1
      2
      loop=2
      */
      

      考虑以下语句:

      function(index) { 
          console.log(index);
      }(i)
      

      这是一个匿名函数并立即执行(末尾的括号'()'执行该函数):参见语法function(param) {...}()。所以效果是每次迭代上面的代码都会立即执行。

      结果是(如你所见):

      0
      1
      2
      

      setTimeout at MDN。方法需要一个函数(在定时器到期后执行)或代码作为它的第一个参数。在这种情况下,您有立即执行的代码(不是函数)。因此,您会立即看到结果。

      5 秒的延迟没有效果,从未使用过。延迟之后没有什么可以执行的。

      在 Firefox 中效果也一样。

      你可以试试匿名函数末尾不带括号的代码,看看会发生什么:

      function(index) { 
          console.log(index);
      }
      

      函数会在延迟五秒后执行,在这种情况下!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-09-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多