相關解釋 在打開瀏覽器的時候會創建出來若幹進程,以便於完成相關任務,其實最重要的是渲染進程。 渲染進程的主要工作為:解析HTML,繪製CSS樣式,執行JS代碼等。 其中在執行JS代碼的時候,會根據代碼任務的類型創建出來若幹隊列,其中常見的有: 延時隊列(setInterval,setTi ...
相關解釋
在打開瀏覽器的時候會創建出來若幹進程,以便於完成相關任務,其實最重要的是渲染進程。
渲染進程的主要工作為:解析HTML,繪製CSS樣式,執行JS代碼等。
其中在執行JS代碼的時候,會根據代碼任務的類型創建出來若幹隊列,其中常見的有:
- 延時隊列(
setInterval
,setTimeout
...) - 交互隊列(
click
,resize
,scroll
...) - 微隊列(
Promise
,MutationObserver
...)
但是,在執行代碼的時候,所有的代碼也就是被稱之任務的是不分優先順序的。也就是說在一個js文件中,依舊按照從上到下的順序去解析執行代碼。
然後在執行代碼的時候會將不普通的任務進行分類,放到相應的隊列中。比如在執行到延遲函數(setInterval
,setTimeout
)的時候就會將任務放到延時隊列中,遇到Promise
函數就將任務放到微隊列中。
雖然代碼任務是不分優先順序的,但是隊列是分優先順序的,其中微隊列的優先順序最高,交互隊列比延時隊列優先順序高
在渲染主線程從上到下執行完所有的代碼後,將會從不同的隊列中讀取任務繼續執行。在從隊列中讀取的時候,微隊列是最先被讀取的。其次才是其他隊列。
示例 1:
setTimeout(function() {
console.log(1)
}, 0)
console.log(2)
輸出順序是:2 1
因為渲染進程在執行的時候會從上到下依次運行,首先是運行到setTimeout計時器,這時候會有有一個負責計時的進程進行該任務,但是因為計時時間是0,所以會將回調函數所對應的任務放入延時進程中,與此同時,渲染進程仍然在繼續進行中,緊接著就執行到了console.log(2)
,因此2將會被輸出,然後所有的js代碼都被執行完了,渲染進程會先去微隊列中查詢是否有任務,發現沒有就去延時隊列中,發現有任務,於是就執行該任務,所以輸出了1。
示例 2:
function delay(duration) {
var start = Date.now()
while(Date.now() - start < duration) {};
}
setTimeout(function() {
console.log(1)
}, 0)
delay(2000)
console.log(2)
輸出順序是:2 1
首先是定義了一個普通的函數,然後遇到了計時器,因此會將該計時器結束後的任務放入到延時隊列中,然後調用了delay
函數,並且傳入了2000,該函數的作用是延遲duration
時間,在這個函數中雖然是一個什麼都沒乾的迴圈,但是渲染進程仍要等待相應的時間,因為什麼都沒乾是編寫人員的想法,但是渲染進程需要去在相應的時間進行執行,然後在duration
時間之後,才遇到了console.log(2)
,然後執行完了所有的代碼之後,渲染進程去微隊列讀取,去延時隊列讀取,才執行了console.log(1)
示例3:
setTimeout(function() {
console.log(1)
}, 0)
Promise.resolve().then(function () {
console.log(2)
})
console.log(3)
輸出順序是:3 2 1
首先是讀取了計時器,然後將計時結束後的任務放入到了延時隊列中,然後遇到了Promise
函數,然後會將相應的任務放入到微隊列中,然後執行了console.log(3)
,然後渲染進程先去讀取微隊列發現其中有一個任務,所以將會立即執行微隊列中的第一個任務,然後繼續檢測微隊列中是否還有其他任務,發現沒有任務了,將去讀取延時隊列中的任務
示例4:
function a() {
console.log(1)
Promise.resolve().then(function () {
console.log(2)
})
}
setTimeout(function() {
console.log(3)
Promise.resolve().then(a)
}, 0)
Promise.resolve().then(function () {
console.log(4)
})
console.log(5)
輸出順序是:5 4 3 1 2
首先遇到函數a
,不執行,然後遇到定時器setTimeout
,計時線程將會在時間結束後將任務放入到延時隊列中,然後遇到Promise
函數,在結束後會將回調任務放入到微隊列中,然後遇到了console.log(5)
將輸出5,然後所有的代碼執行完,去微隊列中讀取是否有任務,然後輸出4,然後微隊列中沒有任務了,就去延時隊列中讀取是否有任務,讀取到有任務,隨即輸出3,然後又遇到了Promise
函數,在當前的任務完成後,渲染進程又會先去微隊列中讀取是否有任務,然後輸出了1,然後又有一個任務被放入到了微隊列中,然後又先讀取微隊列中的任務,然後輸出2
面試題1: 簡述一下 JS 的事件迴圈
事件迴圈又叫做消息迴圈,是瀏覽器渲染主線程的工作方式。
在 Chrome 的源碼中,它開啟一個不會結束的
for
迴圈,每次迴圈從消息隊列中取出第一個任務執行,而其他線程只需要在合適的時候將任務加入到隊列末尾即可。過去把消息隊列簡單分為巨集隊列和微隊列,這種說話目前已無法滿足複雜的瀏覽器環境,取而代之的是一種更加靈活多變的處理方式。
根據W3C官方的解釋,每個任務有不同的類型,同類型的任務必須在同一個隊列,不同的任務可以屬於不同的隊列。不同任務隊列有不同的優先順序,在一次事件迴圈中,由瀏覽器自行決定哪一個隊列的任務先執行。但瀏覽器必須有一個微隊列,微隊列的任務一定具有最高的優先順序,必須優先調度執行。
面試題2: Js中的計時器能做到精確計時嗎,為什麼
不行,因為:
- 電腦的硬體沒有原子鐘,無法做到精確計時
- 操作系統的計時函數本身就有少量偏差,由於JS 的計時器最終調用的的是操作系統的函數,也就攜帶了這些偏差
- 按照W3C的標準,瀏覽器實現計時器時,如果嵌套層級超過5層,則會帶有4毫秒的最少時間,這樣在計時時間少於4毫秒時又帶來了偏差
- 受事件迴圈的影響,計時器的回調函數只能在主線程空閑時運行,因此又帶來了偏差
單線程是非同步產生的原因
事件迴圈是非同步的實現方式