例子:
1 for (var i = 0; i < 5; i++) { 2 setTimeout(function () { 3 console.log(i); 4 }, 100) 5 }
上述代碼,輸出結果顯而易見是5個5,且並沒有任何的延遲效果。那么為什么呢?
首先這樣的結果需要從JS的執行機制說起。JS是單線程環境,也就是說代碼的執行是從上到下,依次執行。這樣的執行稱為同步執行。因為種種不要浪費和節約的原因。JS中引進了異步的機制。在這段代碼中,哪個是同步哪個是異步呢?for循環是同步代碼,而setTimeout中的是異步代碼。那么JS碰到這個有同步和異步的情況下會先從上到下執行同步代碼,碰到異步的代碼會將其插入到任務隊列當中等待。而setTimeout是延時,也就是說碰到setTimeout這個異步的代碼塊會根據它里面的第二個參數:延時時間來將代碼插入到任務隊列當中,比如上面這段代碼中,第二個參數延時時間是100,也就是說執行到它的時候會在100ms之后將它插入到任務隊列當中。同步代碼都執行完成之后,那么JS引擎就空閑了,這個時候就輪到任務隊列中的異步代碼依次加載了。
這是上面這段代碼的答案的一半。另一半就來自於作用域,作用域是變量等資源的作用范圍。在這段代碼中准確的說是作用域鏈的問題,當同步代碼執行完畢開始執行異步的setTimeout代碼時,setTimeout中需要一個變量i,而執行的時候在當前的作用域中開始找,找不到變量i的定義,這個時候就把創建這個函數的作用域作為當前作用域,再次尋找,創建這個函數的作用域就是全局作用域,也就是找到了for循環中i,找到了之后就結束尋找變量i的行程。由於這個時候的i是全局的,而且人家已經變為了最終形態:5,setTimeout找到的就是這個i=5;所以就輸出了5,下面的4次setTimeout 的執行都是類似,所以結果都是5;
所以我對這個答案的理解歸結起來就是 異步加載+作用域鏈。
正確寫法:
1 for (var i = 0; i < 5; i++) { 2 (function (i) { 3 setTimeout(function () { 4 console.log(i); 5 }, 100 * i); 6 })(i); 7 }
將延遲操作閉包,立即執行。