首頁  >  文章  >  web前端  >  完全掌握JavaScript執行機制

完全掌握JavaScript執行機制

WBOY
WBOY轉載
2022-01-20 17:58:501019瀏覽

這篇文章為大家帶來了關於JavaScript執行機制的相關問題,其中包括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)。 (下圖來自網路)

完全掌握JavaScript執行機制

學習了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, 接下來我們在看一個流程圖。

完全掌握JavaScript執行機制

我們來稍微理解一下:

  • # 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學習教學

#

以上是完全掌握JavaScript執行機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.im。如有侵權,請聯絡admin@php.cn刪除