首頁  >  問答  >  主體

javascript - setTimeout第一個參數是立即執行函數,看不懂了

setTimeout第一個參數是立即執行函數,看不懂了

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

雖然結果是立即輸出0,1,2,3,4,但是不知道為啥

大家讲道理大家讲道理2663 天前945

全部回覆(9)我來回復

  • 给我你的怀抱

    给我你的怀抱2017-07-05 10:58:26

    這樣其實是一下子全部印出來的。 。 。不是每隔一秒列印出來。 。建議這樣寫。 。

    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.

    回覆
    0
  • 大家讲道理

    大家讲道理2017-07-05 10:58:26

    你先看括住整個function的那個括號
    也就是:

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

    這一段,理解就是把自己包成一個包裹,是一個整體,這個整體是一個函數

    那接下來要怎麼呼叫函數呢?是不是函數名稱()這樣,在函數名稱後面加上括號,可以傳遞參數進去

    所以很自然的,就出現了這樣:

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

    這種調用,就是寫個函數,用()包起來,在後面接個(),裡面寫參數,就直接執行了

    回覆
    0
  • 为情所困

    为情所困2017-07-05 10:58:26

    就像樓上說的是立刻印出來了,你的setTimeout根本就沒起作用。
    原因就是立即執行函數執行後沒有回傳值,所以相當於setTimeout(undefined, i*1000)。

    回覆
    0
  • 迷茫

    迷茫2017-07-05 10:58:26

    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);
    }

    這樣至少對於組內其他成員讀起來更容易理解一些。

    回覆
    0
  • 我想大声告诉你

    我想大声告诉你2017-07-05 10:58:26

    因為是立即執行的函數啊,當然立即輸出了

    回覆
    0
  • 淡淡烟草味

    淡淡烟草味2017-07-05 10:58:26

    函數作用域問題,改變this指向就可以了。

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

    回覆
    0
  • 習慣沉默

    習慣沉默2017-07-05 10:58:26

    沒有立即執行函數的話,印出來的是5個5,而且是每隔一秒列印,因為這裡setTimeout裡面的函數要等循環完成之後才會執行,這時全域變數i就是5了。使用立即執行函數,會取得迴圈中的每一個i,這裡有閉包的效果,這個i這時就是一個局部變數了,存在於此函數中,每次執行時變數i的值都不一樣。

    回覆
    0
  • 黄舟

    黄舟2017-07-05 10:58:26

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

    回覆
    0
  • 黄舟

    黄舟2017-07-05 10:58:26

    • 理解一下原理,閉包,堆疊,事件隊列,同步,非同步

    • 翻轉一下思路:把傳進去的參數i去掉,看看是不是能正常打印,不能的話,分析一下為什麼

    • 能不能換幾種寫法,實現上面一樣的效果,分析一下為什麼
      完成上面的幾點,你就知道原因 過程 結果

    回覆
    0
  • 取消回覆