JS 的線程、事件循環、任務隊列簡介


JS 是單線程的,但是卻能執行異步任務,這主要是因為 JS 中存在事件循環(Event Loop)和任務隊列(Task Queue)。

事件循環:JS 會創建一個類似於 while (true) 的循環,每執行一次循環體的過程稱之為 Tick。每次 Tick 的過程就是查看是否有待處理事件,如果有則取出相關事件及回調函數放入執行棧中由主線程執行。待處理的事件會存儲在一個任務隊列中,也就是每次 Tick 會查看任務隊列中是否有需要執行的任務。

任務隊列:異步操作會將相關回調添加到任務隊列中。而不同的異步操作添加到任務隊列的時機也不同,如 onclick, setTimeout, ajax 處理的方式都不同,這些異步操作是由瀏覽器內核的 webcore 來執行的,webcore 包含上圖中的3種 webAPI,分別是 DOM Binding、network、timer模塊。

  onclick 由瀏覽器內核的 DOM Binding 模塊來處理,當事件觸發的時候,回調函數會立即添加到任務隊列中。

  setTimeout 會由瀏覽器內核的 timer 模塊來進行延時處理,當時間到達的時候,才會將回調函數添加到任務隊列中。

  ajax 則會由瀏覽器內核的 network 模塊來處理,在網絡請求完成返回之后,才將回調添加到任務隊列中。

主線程:JS 只有一個線程,稱之為主線程。而事件循環是主線程中執行棧里的代碼執行完畢之后,才開始執行的。所以,主線程中要執行的代碼時間過長,會阻塞事件循環的執行,也就會阻塞異步操作的執行。只有當主線程中執行棧為空的時候(即同步代碼執行完后),才會進行事件循環來觀察要執行的事件回調,當事件循環檢測到任務隊列中有事件就取出相關回調放入執行棧中由主線程執行。

例1:
var req = new XMLHttpRequest();
    req.open('GET', url);    
    req.onload = function (){};    // 這兩個異步方法就會在 ajax 完成后推入任務隊列,再由主線程執行
    req.onerror = function (){};    
    req.send();

例2:
setTimeout(function(){
  // 如果有大量的操作,可能會阻塞 UI 等,則可以使用 setTimeout 讓這些操作在主線程把更重要的代碼執行完畢之后,再來執行這里的操作。從而提高瀏覽器的性能。
},0);  // 設置為 0,也會有個最小間隔值,也會在主線程中的代碼運行完成后,由事件循環從任務隊列將回調添加到執行棧中才執行

例3: // 事件循環測試。執行結果是 2-3-4-1,1在最后輸出,說明事件循環是所有同步代碼執行完后才開始執行的。 'use strict'; setTimeout(function() {   console.log(1); }, 0); console.log(2); let end = Date.now() + 1000*5; while (Date.now() < end) { } console.log(3); end = Date.now() + 1000*5; while (Date.now() < end) { } console.log(4);

 

Update:

《你不知道的 JavaScript》一書中,重新講解了 ES6 新增的任務隊列,和上面的任務隊列略有不同,上面的任務隊列書中稱為事件隊列。

上面提到的任務(事件)隊列是在事件循環中的,事件循環每一次 tick 便執行上面所述的任務(事件)隊列中的一個任務。而任務(事件)隊列是只能往尾部添加任務。

而 ES6 中新增的任務隊列是在事件循環之上的,事件循環每次 tick 后會查看 ES6 的任務隊列中是否有任務要執行,也就是 ES6 的任務隊列比事件循環中的任務(事件)隊列優先級更高。

如 Promise 就使用了 ES6 的任務隊列特性。

 

參考:

http://www.cnblogs.com/Medeor/p/4945687.html

http://www.ruanyifeng.com/blog/2014/10/event-loop.html

http://www.cnblogs.com/zhaodongyu/p/3922961.html

深入淺出 node.js  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM