前言
我們知道JavaScript的單線程,與它的用途有關。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另一個線程刪除了這個節點,這時瀏覽器應該以哪個線程為准?
所以,為了避免復雜性,從一誕生,JavaScript就是單線程,這已經成這門語言的核心特征,將來也不會改變。
所謂單線程是指在JS引擎中負責解釋和執行JavaScript代碼的線程只有一個。就是下面的討論的同步和異步的概念。
同步
同步:同步,是指代碼從上向下執行,執行完一條,才去執行下一條,是按照順序按照步驟的執行。比如,如果直接加載js文件,首先加載第一個文件a.js,並且執行這個js文件,完成后在加載b.js。這叫做同步:同步是在渲染DOM之前做的
異步
異步,代碼執行需要有一個過程,或者需要一定的時間,或者開始的時間不確定,這時候
我們先讓別的不相關的代碼執行,而當前代碼當執行完成后去執行一個回調函數。
js為什么需要異步?
如果JS中不存在異步,只能自上而下執行,萬一上一行解析時間很長,那么下面的代碼就會被阻塞。對於用戶而言,阻塞就意味着"卡死",這樣就導致了很差的用戶體驗。
下面我們來看一段代碼
console.log("script start");
setTimeout(function(){
console.log("setTimeout");
},0)
Promise.resolve().then(function(){
console.log("promise1");
}).then(function(){
console.log("promise2");
})
console.log('script end');
不用我說也知道讓你判斷打印順序是吧。那是什么呢?
正確答案是
為什么會出現這樣的打印順序?
要理解這些首先需要對事件循環機制處理宏任務和微任務的方式有所了解
每個線程都會有它自己的event Loop(事件循環),所以能夠獨立運行。然而所有的同源窗口會共享一個eventLoop以進行通信。event Loop會一直運行,來執行進入隊列的宏任務。一個event Loop會宏任務源,這些宏任務源保證了本任務源內的順序。但是瀏覽器每次都會選擇源中的一個宏任務去執行。這保證可瀏覽器給與一些宏任務(如用戶輸入)以更高的優先級
宏任務(task)
瀏覽器為了能夠使得JS內部Task與DOM任務能夠有序的執行,會在一個task執行完畢結束后,在下一個task執行開始前,對頁面進行重新渲染(render)
task->rander->task
鼠標點擊會觸發一個事件回調,需要執行一個宏任務,然后解析HTML.還有下面這個setTimeOut,setTimeOut的作用是等待給定的時間后回調產生一個新的宏任務。這就是為什么打印“setTimeOut”在打印“script end” 之后,因為打印“script end”是第一個宏任務里面的事情,而setTimeout 是更一個獨立的任務里面的打印的。
異步有:setTimeout setInterval
微任務(Microtasks)
微任務通常來說就是在當前task執行結束后立即執行的任務,比如對一系列動作做出反饋,或者是需要異步的執行任務而又不需要分配一個新的task,這樣便可以減小一點性能的開銷。只要執行棧中沒有其他JS代碼正在執行且每個宏任務執行完,微任務隊列會立即執行。如果在微任務執行期間微任務隊列中加入了新的微任務,就會把這個新的微任務加入到隊列的尾部,之后也會被執行。微任務包括了promise async await。
一旦一個promise有了結果,或者早已有了結果(有了結果是指這個promise到了rejected狀態),他就會為他的回調產生一個微任務,這就保證了回調異步執行的即使這個promise有了結果。所以對一個已經有了結果的promise調用.then()方法會立即產生一個微任務。這就為什么“promise1”,"promise2"會打印在“script end”之后,因為所有微任務執行的時候,當前執行棧的代碼必須執行完畢。"promise1","promise2"會打印在"setTimeOut"之前,是因為所有的微任務總會在下一個宏任務之前全部執行完畢!
微任務有:promise async await
所以,我們用一張圖來看任務的處理過程
雖然JS是單線程的但是瀏覽器的內核是多線程的,在瀏覽器的內核中不同的異步操作由不同的瀏覽器內核模塊調度執行,異步操作會將相關回調添加到任務隊列中。而不同的異步操作添加到任務隊列的時機也不同,如 onclick, setTimeout, ajax 處理的方式都不同,這些異步操作是由瀏覽器內核的 webcore 來執行的,webcore 包含上圖中的3種 webAPI,分別是 DOM Binding、network、timer模塊。
最后我們總結一下運行機制:
- 同一個隊列中,先執行的是宏任務,再執行其他任務,最后執行微任務
- 在當前隊列中出現的異步,如果是微任務就會放在當前任務隊列最底端, 如果當前隊列出現的異步是宏任務,就會出現在下一個隊列最頂端。
- 同一個隊列中觸發異步,微任務先執行,宏任務后執行