完全掌握JavaScript執行機制

2022-01-20 19:00:31
本篇文章給大家帶來了關於JavaScript執行機制的相關問題,其中包括JavaScript單執行緒和JavaScript同步非同步的相關知識,希望對大家有幫助。

一、為什麼JavaScript是單執行緒

如果想了解JavaScript為什麼是單執行緒的,我們就要從JavaScript是用來做什麼工作的來入手。

JavaScript作為瀏覽器的指令碼語言,產出的目的就是為了瀏覽器與使用者進行互動,操作DOM元素,從而提升使用者的互動及體驗感。JavaScript要操作瀏覽器的DOM元素,因此導致JavaScript無法成為多執行緒語言,我們假設一個場景,如果JavaScript是多執行緒語言,兩個執行緒同時操作一個DOM元素,一個執行緒需要編輯更新DOM元素,而另一個則是刪除DOM元素節點,這是瀏覽器應該以哪個為準呢?

同一時間只能做同一件事情,因為操作DOM元素的原因,導致單執行緒是JavaScript這門語言的核心,也是這門語言特點。

HTML5提出Web Worker標準,允許JavaScript指令碼建立多個執行緒,但是子執行緒完全受主執行緒控制,且不得操作DOM。即使這樣的改動也並沒有改變js單執行緒的本質。

二、JavaScript中的同步與非同步

javaScript的單執行緒機制,就導致同一時間只能做一件事情。就像一堆人在ATM取款機取款,即使後面再多的人在著急,也只能一個一個的排隊,等待前一個人取完款,才能輪到後一人。

可是這樣會導致如果說前一個任務消耗時間過長,後一個任務就會等待非常久,比如,我們需要載入一個資料量非常大的Ajax請求,我們不得不等待請求相應結果,再繼續往下行執後續任務。

那我們該如何處理這種情況呢?既然我們無法改變JavaScript的單執行緒機制,我們是否可以將一些耗時久的任務進行暫時掛起,等到主任務執行完成之後,再將這些掛載的任務執行。JavaScript的作者也想到了這樣的方式,JavaScript擁有了同步任務與非同步任務。

同步任務就是,任務在主執行緒上進行排隊,下一個任務必須等待上一個任務執行完成,才可以執行。而非同步任務是指,任務不進入主執行緒,而進入任務佇列(task queue)進行等待,進入任務佇列的任務只有"任務佇列"通知主執行緒,某個非同步任務可以執行了,該任務才會進入主執行緒執行。

三、Event Loop事件迴圈機制

JavaScript的所有同步任務都在主執行緒上執行,形成一個執行棧。

任務佇列是先進先出的原則,先進佇列的事件先執行,後進佇列的事件後執行。

Event Loop

  • 執行執行棧中的同步任務。

  • 當遇到一個非同步任務後不會一直等待其返回結果,會先將非同步任務進行暫時掛起,繼續執行其他的同步任務。

  • 當非同步任務有結果之後,JavaScript會任務將新增進任務佇列中。被新增進任務佇列中的任務不會立刻進行回撥執行而是等待主執行緒(執行棧)空閒時才加入到執行棧中進行回撥執行

  • 等待執行棧中的任務執行完畢。

  • 將任務佇列中的任務加入執行棧後執行。

如此反覆,這樣就形成了一個無限的迴圈(event loop)。(下圖來自網路)

38.png

學習了 Event Loop 我們一起來看看下面這道題:

setTimeout(function(){
     console.log('setTimeout start')
 });
 new Promise(function(resolve){
     console.log('promise start');
     resolve()
 }).then(function(){
     console.log('promise then')
 });
 console.log('script end');

嘗試按照,上文我們剛學到的JavaScript執行機制去分析

1. 首先執行同步任務,執行到setTimeout,但是setTimeout是非同步任務的暫時掛起,等待計時超時,新增進任務佇列中。

2. 繼續往下,在執行到new Promise,new Promise裡面的是同步任務,列印 "promise start"。

3. 在執行到resolve將.then新增進任務佇列中。

4. 在執行 console.log('script end');列印"script end"。

5. 這時主任務都已經執行完成,在將非同步任務新增進主任務中直接執行,列印"setTimeout start",再將.then新增進主任務中,列印"promise then"。

所以結果應該是:promise start -> script end -> setTimeout start -> promise then 嗎?

親自在瀏覽器執行後,結果居然不是這樣,而是 promise start -> script end -> promise then -> setTimeout start

宏任務與微任務

那為什麼上文中的結果為什麼跟我們預想的不一致,為什麼 setTimeout start 會在 promise 之後列印。

其實是因為非同步的執行也是有先後順序的。其實用非同步跟同步的方式去劃分任務佇列的執行順序是不準確的。應該劃分為 微任務 與 宏任務。

  • 宏任務(macro-task):script 程式碼、setTimeout、setInterval

  • 微任務(micro-task):Promise、process.nextTick

所以說setTimeout是非同步任務中的 宏任務 ,而Promise是非同步任務中的 微任務 。不管是 微任務 還是 宏任務,都會進入相應的 Event Queue, 接下來我們在看一個流程圖。

39.png

我們來稍微理解一下:

  • 1. 執行宏任務(script程式碼)

  • 2. 當執行宏任務的時遇到了微任務,就會將微任務新增進 Event Queue

  • 3. 在當前宏任務執行完成後,會檢視微任務的 Event Queue ,並將裡面全部的微任務依次執行完

  • 4. 在執行玩所有的為微任務之後,繼續進行第一步,以此迴圈

這便也是 javaScript 的執行機制,結合這個我們再重新的分析一下上面的例子。

  • 1. 首先執宏任務(script程式碼 ),遇到setTimeout將其新增進宏任務的Event Queue。

  • 2. 繼續往下,在執行到new Promise,列印 "promise start"。

  • 3. 在執行到resolve,.then是微任務,新增進微任務的Event Queue。

  • 4. 在執行 console.log('script end');列印 "script end"。

  • 5. 到這裡本輪的宏任務就已經全部執行結束了,這時查詢微任務的 Eevent Queue 是否存在可執行的微任務, 發現有剛才在第三步新增進去額度.then,執行並列印 "promise then"

  • 6. 這時第一輪的 event loop 就已經徹底結束了,下一輪 event loop 先執行一個宏任務,發現宏任務的Event Queue中有新增進去的setTimeout,執行並列印 "setTimeout start"

永遠記住JavaScript是單執行緒,以前是、現在是、將來也會是。所有的多執行緒說法都是扯淡。

即使是Event Queue,也只不是實現非同步的方式,也是js的執行機制。

以後能用JavaScript實現的。都將會用JavaScript來實現。

相關推薦:

以上就是完全掌握JavaScript執行機制的詳細內容,更多請關注TW511.COM其它相關文章!