本博文基於知乎"JavaScript做用域問題?"一問,而引發了對JavaScript事件循環和單線程等概念與實踐上的研究、深刻理解。javascript
1、概念
0.關鍵詞:JavaScript單線程、事件循環(event loop)、事件隊列(event queue)、執行棧(execution context stack)
php
1.JavaScript引擎屬於單線程做業。所謂單線程,是指在JS引擎中負責解釋和執行JavaScript代碼的線程只有一個,也不妨叫它主線程。JavaScript引擎屬於單線程做業,意味着:在同一時間只能執行一個代碼塊,這些代碼塊的執行就阻塞了異步事件的處理。[From JavaScript忍者祕籍]
JavaScript的單線程,與它的用途有關。做爲瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操做DOM。這決定了它只能是單線程,不然會帶來很複雜的同步問題。好比,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另外一個線程刪除了這個節點,這時瀏覽器應該以哪一個線程爲準?
1.1 單線程意味着,【全部任務】都須要排隊,前一個任務結束,纔會執行後一個任務。若是前一個任務耗時很長,後一個任務就不得不一直等着。
1.2 若是排隊是由於計算量大,CPU忙不過來,倒也算了,可是不少時候CPU是閒着的,由於IO設備(輸入輸出設備)很慢(好比Ajax操做從網絡讀取數據),不得不等着結果出來,再往下執行。
1.3 JavaScript語言的設計者意識到,這時主線程徹底能夠無論IO設備,掛起處於等待中的任務,先運行排在後面的任務。等到IO設備返回告終果,再回過頭,把掛起的任務繼續執行下去。
1.4 因而,全部任務能夠分紅兩種,一種是同步任務(synchronous),另外一種是異步任務(asynchronous)。
1.5 同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;
1.6 異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。
1.7 具體來講,異步執行的運行機制以下。(同步執行也是如此,由於它能夠被視爲沒有異步任務的異步執行。)
(1)全部同步任務都在主線程上執行,造成一個執行棧(execution context stack)。
(2)主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
(3)一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重複上面的第三步。
1.8 只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。這個過程會不斷重複。
[From 阮一峯老師:http://www.ruanyifeng.com/blog/2014/10/event-loop.html]
2.事件循環(用於解決:異步問題/異步事件):在初期許多人會把異步理解成相似多線程的編程模式,其實他們中有着很大的差異,要徹底理解異步,就須要瞭解 JS 的運行核心——事件循環(event loop)。
2.1 事件循環:【事件隊列】是一個存儲着待執行任務的隊列,其中的任務嚴格按照時間前後順序執行,排在隊頭的任務將會率先執行,而排在隊尾的任務會最後執行。事件隊列每次僅執行一個任務,在該任務執行完畢以後,再執行下一個任務。【執行棧】則是一個相似於函數調用棧的運行容器,當執行棧爲空時,JavaScript引擎便檢查事件隊列,若是不爲空的話,事件隊列便將第一個任務壓入中運行。
[From http://www.php.cn/js-tutorial-369771.html]
2.2 常見異步任務:定時器任務(setTimeout();setInterval();)、Ajax事件、瀏覽器/用戶行爲事件(例如:瀏覽器加載(load)、鼠標單擊click、鼠標滑動/滑過/離開(mouseover、mouseout、mouseleave等)html
2、分析java
因爲JavaScript是單線程做業,當一個異步事件發生時(好比:鼠標單擊、定時器觸發甚至是XMLHttpRequest的完成事件),它就會排隊,而且在線程空閒時才進行執行。且實際上,每一個瀏覽器的排隊機制是不一樣的。當咱們設置一個延遲函數的時候,當前腳本並不會阻塞,它只是會在瀏覽器的事件表中進行記錄,程序會繼續向下執行。當延遲的時間結束以後,事件表會將回調函數添加至事件隊列(task queue)中,事件隊列拿到了任務事後便將任務壓入執行棧(stack)當中,執行棧執行任務,執行console.log("after 1000 mills:",i);編程
for(var i = 0; i < 10; i++) { console.log("cur:",i); setTimeout(function() { console.log("after 1000 mills:",i); //當 console.log 被調用的時候,匿名函數保持對外部變量 i 的引用,此時for循環已經結束, i 的值被修改爲了 10. }, 1000); }
3、解決方案瀏覽器
即時函數網絡
格式:(function(){ //...statement })(); 多線程
for(var i = 0; i < 10; i++) { console.log("cur:",i), (function(i){ setTimeout(function() { console.log("after 1000 mills:",i); }, 1000); })(i);//經過即時函數(1.建立函數實例,2.執行該函數,3.銷燬該函數),將循環體異步事件壓入執行棧中,當即執行的特性,以維護好變量當前的值 }
4、引用文獻異步
《JavaScript忍者祕籍》async