setTimeout第一个参数是立即执行函数,看不懂了

setTimeout第一个参数是立即执行函数,看不懂了

for (var i = 0; i < 5; i++) {
  setTimeout((function(i) {
    console.log(i);
  })(i), i * 1000);
}

虽然结果是立即输出0,1,2,3,4,但是不知道为啥

阅读 4.3k
评论
    10 个回答

    这样其实是一下子全部打印出来的。。。不是每隔一秒打印出来。。建议这样写。。

    for (var i = 0; i < 5; i++) {
      setTimeout((function(i) {
        return function() {
            console.log(i);
        }
      })(i), i * 1000);
    }

    我来解释一下这个为什么可以获取到0、1、2、3、4.
    网上关于JS预解释的文章也不少,在进入执行上下文阶段的时候函数并不会执行,简单来说就是当你声明这个函数的时候,只要不调用就不会执行,上下文里面只会保存着这个函数的引用,可以看做这个函数保存在内存中,只有到调用的时候函数才会执行,我说说自己的理解,有不对的地方请指出来。
    如果没有立即执行函数:
    你在for循环里面实际上相当于定义了5个定时器,但是js是单线程,这五个函数会被放到队列里面等待执行,举个不一定恰当的例子,你就把这五个函数function() {console.log(i);}当成字符串保存到内存中,一直没什么动静,等到这五个函数调用执行的时候(就是setTimeout的第二个参数的时间到了的时候),才会开始执行这个函数,因为函数里面有个i,这个时候会通过作用域链来查找这个i,最后在外面的作用域里面查找到了i,但是这个时候for循环已经执行结束了,i已经变成4了,所以会打印出5个4.
    如果有立即执行函数(比如我上面写的那个):
    你在for循环里面实际上相当于定义了5个定时器,但是js是单线程,这五个函数会被放到队列里面等待执行。

    (function(i) {

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

    })(i)

    但是由于外面是立即执行函数,所以会立即就执行了,并且把i传了进去,等到这五个函数执行的时候,向上查找i,正好在这个立即调用函数的作用域里面查找到了i,所以会打印出0、1、2、3、4.

      • 11.8k

      setTimeout要求第1个参数是一个函数,这样等第2个参数规定的时间到了之后,开始执行第1个参数定义的函数。

      当你这么写的时候:

      for (var i = 0; i < 5; i++) {
        setTimeout(function(i) {
          console.log(i);
        }, i * 1000);
      }

      你会注意到定时器已经起作用了,只不过是每隔1秒种打出来一个undefined。因为当执行这个函数的时候,i已经从循环中跳出,已经没有值了。

      所以你改成这样:

      for (var i = 0; i < 5; i++) {
        setTimeout((function(i) {
          console.log(i);
        })(i), i * 1000);
      }

      但在这种情况下,第1个参数不是一个函数,而是一个表达式,也就是说会立即执行的函数,它不会等到计时器起作用才执行,而是只要一碰到就会执行,所以表现形式就是直接打出了0,1,2,3,4。

      按照楼上的说法改成这样:

      for (var i = 0; i < 5; i++) {
        setTimeout((function(i) {
          return function() {
              console.log(i);
          }
        })(i), i * 1000);
      }

      虽然第1个参数是一个表达式,还是会立即执行,但是这个表达式执行的结果不是输出数值,而是返回一个函数,这就满足了setTimeout对第1个参数是函数的要求,并且给定了正确的输入参数,所以每隔1秒种会输出一个正确的结果。

      不过,为了团队协作起见,我一般不建议这么写,我建议还是规规矩矩按照setTimeout的标准写法写成这样:

      var j = 0;
      for (i = 0; i < 5; i++) {
        setTimeout(function() {
           console.log(j);
           j++;
        }, i * 1000);
      }

      这样至少对于组内其他成员读起来更容易理解一些。

        就像楼上说的是立刻打印出来了,你的setTimeout根本就没起作用。
        原因就是立即执行函数执行后没有返回值,所以相当于setTimeout(undefined, i*1000)。

          • 569

          你先看括住整个function的那个括号
          也就是:

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

          这一段,理解就是把自己包成一个包裹,是一个整体,这个整体是一个函数

          那么接下来怎么调用函数呢?是不是函数名()这样,在函数名后面加上括号,并且可以传递参数进去

          所以很自然的,就出现了这样:

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

          这种调用,就是写个函数,用()包起来,在后面接个(),里面写参数,就直接执行了

            • 1.3k
            (function(i) {
                console.log(i);
            })(i)

            声明了马上执行 这样 就是 0 1 2 3 4

            然而这个没有返回值 因此默认是 undefined

            因此你的代码可以认为是这样:

            for (var i = 0; i < 5; i++) {
              var temp = (function(i) {
                console.log(i);
              })(i); 
              // temp 是 undefined 
              setTimeout(temp, i * 1000);
            }

            clipboard.png

              因为是立即执行的函数啊,当然立即输出了

                • 3
                • 新人请关照

                函数作用域问题,改变this指向就可以了。

                for (var i = 0; i < 5; i++) {
                    setTimeout((function(i) {
                        console.log(i);
                    }).bind(this,i), i * 1000);
                }

                  没有立即执行函数的话,打印出来的是5个5,而且是每隔一秒打印,因为这里setTimeout里面的函数要等循环完成之后才会执行,这时全局变量i就是5了。使用立即执行函数,会获取循环中的每一个i,这里有闭包的效果,这个i这时就是一个局部变量了,存在于此函数中,每次执行时变量i的值都不一样。

                    • 理解一下原理,闭包,堆栈,事件队列,同步,异步

                    • 翻转一下思路:把传进去的参数i去掉,看看是不是能正常打印,不能的话,分析一下为什么

                    • 能不能换几种写法,实现上面一样的效果,分析一下为什么
                      完成上面的几点,你就知道原因 过程 结果

                      • 1
                      • 新人请关照

                      你应该是想问两个问题,
                      1、为什么使用var i=0但是能够正常打印出0-4,而不是5个5;
                      2、为什么timeout不起作用;

                      1是因为你将i作为参数传递给了匿名函数,所以匿名函数接收到的参数是随着循环改变的;

                      2是因为setTimeout的执行过程是:
                      在主线程中:进入循环 → 进入setTimeout → 把timeout的第一个参数放到定时触发线程中等待执行 → 在主线程中开始计时 → 进入下一次循环 → (重复直到循环结束);
                      在定时触发线程中:储存了timeout第一个参数的函数 → 到时,通过事件循环机制把存储的函数添加到主线程执行栈的顶层执行;
                      但是在你写的代码中,timeout的第一个参数不是一个函数,而是一个函数的调用,相当于是一句JS执行语句,所以就被立即执行了。

                        撰写回答

                        登录后参与交流、获取后续更新提醒

                        相似问题
                        推荐文章