本文來自 @xiaoyuze88 連接:http://xiaoyuze88.github.io/javascript
過久沒碰代碼了,那天想到關於循環調用setTimeout
實現每隔一秒輸出遞增的數的那個問題,搞了搞,發現不少概念模糊了,在此總結下。java
所謂的循環調用setTimeout
實現遞增輸出,就是說用for
循環10次,每隔一秒輸出一個從0~9的數。git
很少說,直接上最終代碼再說,細節後面再談。github
for (var i = 0; i < 10; i++) { //這裏用閉包,爲每個i生成一個獨立的上下文環境,傳遞給裏面的console.log,而不會受到setTimeout延時而影響 (function (i) { `setTimeout`(function () { console.log(i); }, 1000 * i) })(i); }
這裏主要的問題在:ajax
setTimeout
等函數的工做機制。首先閉包,這裏就很少說了,在這裏,閉包的做用就是給閉包內的函數生成一個不受外面環境干擾的上下文環境,因爲js的做用域問題。瀏覽器
若是這裏不用閉包,寫成諸如:閉包
for (var i = 0; i < 10; i++) { `setTimeout`(function () { console.log(i); }, i * 1000); }
會發現,每隔一秒鐘,輸出一個10。異步
這是因爲這一個for循環的執行,瞬間就完成了,也就是說,瞬間註冊了10個延時執行的函數,每個隔一秒鐘執行。函數
當註冊的時間點到來,開始執行setTimeout
中的語句,因爲定義域的問題,此時console.log(i)
的這個i指向的是已經到達10的for循環中的i,這就是爲何要用閉包來給setTimeout
設置獨立的上下文環境,而避免須要訪問i時訪問到了外面的變量。線程
另外,若是細想一下,會發現setTimeout
的工做過程多少讓人有點迷惑,到底setTimeout
等延時類函數在瀏覽器中是如何運做的?這就牽扯到下一個問題,關於瀏覽器中是若是運做的問題。
瀏覽器中,JS引擎是單線程的,假設一個瀏覽器中有三個常駐線程,既JS引擎線程、渲染線程、事件觸發線程,還有處理完即結束的線程如AJAX異步請求。
其中,JS線程與渲染線程是互斥的,這是爲了不JS控制DOM時與頁面渲染髮生衝突。而對於JS線程,它是由事件驅動的,因爲單線程,全部任務依隊列排序。若是頁面上觸發了事件,如onclick=function(){}
、或者由setTimeout
添加了一個函數、ajax
請求返回的事件等,全部新添加的任務位於隊尾等待處理。
因爲是單線程,若是線程被阻塞,如while(true){}
死循環,則一切新添加的任務都將被阻塞。
由上面所述,就能夠理解爲何setTimeout
或setInterval設置的延時事件並非真是函數處理的延時時間,既setTimout(code,1000)並非必定會在1秒後處理,這段代碼發生的僅僅是在1秒後,將待處理函數排與js任務隊列末尾。