文章來自個人 github 博客,包括技術輸出和學習筆記,歡迎star。javascript
先來明白些概念性內容。java
瀏覽器內核有多種線程在工做。git
GUI 渲染線程:github
JS 引擎線程:web
事件觸發線程:segmentfault
定時器觸發線程:promise
http 請求線程:瀏覽器
JavaScript 引擎是單線程,也就是說每次只能執行一項任務,其餘任務都得按照順序排隊等待被執行,只有當前的任務執行完成以後纔會往下執行下一個任務。數據結構
HTML5 中提出了 Web-Worker API,主要是爲了解決頁面阻塞問題,可是並無改變 JavaScript 是單線程的本質。瞭解 Web-Worker。異步
JavaScript 事件循環機制分爲瀏覽器和 Node 事件循環機制,二者的實現技術不同,瀏覽器 Event Loop 是 HTML 中定義的規範,Node Event Loop 是由 libuv 庫實現。這裏主要講的是瀏覽器部分。
Javascript 有一個 main thread 主線程和 call-stack 調用棧(執行棧),全部的任務都會被放到調用棧等待主線程執行。
JS 調用棧是一種後進先出的數據結構。當函數被調用時,會被添加到棧中的頂部,執行完成以後就從棧頂部移出該函數,直到棧內被清空。
JavaScript 單線程中的任務分爲同步任務和異步任務。同步任務會在調用棧中按照順序排隊等待主線程執行,異步任務則會在異步有告終果後將註冊的回調函數添加到任務隊列(消息隊列)中等待主線程空閒的時候,也就是棧內被清空的時候,被讀取到棧中等待主線程執行。任務隊列是先進先出的數據結構。
調用棧中的同步任務都執行完畢,棧內被清空了,就表明主線程空閒了,這個時候就會去任務隊列中按照順序讀取一個任務放入到棧中執行。每次棧內被清空,都會去讀取任務隊列有沒有任務,有就讀取執行,一直循環讀取-執行的操做,就造成了事件循環。
定時器會開啓一條定時器觸發線程來觸發計時,定時器會在等待了指定的時間後將事件放入到任務隊列中等待讀取到主線程執行。
定時器指定的延時毫秒數其實並不許確,由於定時器只是在到了指定的時間時將事件放入到任務隊列中,必需要等到同步的任務和現有的任務隊列中的事件所有執行完成以後,纔會去讀取定時器的事件到主線程執行,中間可能會存在耗時比較久的任務,那麼就不可能保證在指定的時間執行。
宏任務(macro-task)、微任務(micro-task)
除了廣義的同步任務和異步任務,JavaScript 單線程中的任務能夠細分爲宏任務和微任務。
macro-task包括:script(總體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver。
console.log(1); setTimeout(function() { console.log(2); }) var promise = new Promise(function(resolve, reject) { console.log(3); resolve(); }) promise.then(function() { console.log(4); }) console.log(5);
示例中,setTimeout 和 Promise被稱爲任務源,來自不一樣的任務源註冊的回調函數會被放入到不一樣的任務隊列中。
有了宏任務和微任務的概念後,那 JS 的執行順序是怎樣的?是宏任務先仍是微任務先?
第一次事件循環中,JavaScript 引擎會把整個 script 代碼當成一個宏任務執行,執行完成以後,再檢測本次循環中是否尋在微任務,存在的話就依次從微任務的任務隊列中讀取執行完全部的微任務,再讀取宏任務的任務隊列中的任務執行,再執行全部的微任務,如此循環。JS 的執行順序就是每次事件循環中的宏任務-微任務。