想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你!
JavsScript 是一門單線程的編程語言,這就意味著一個時間里只能處理一件事,也就是說 JavaScript 引擎一次只能在一個線程里處理一條語句。
雖然單線程簡化了編程代碼,因為你不必太擔心并發引出的問題,這也意味著你將在阻塞主線程的情況下執行長時間的操作,如網絡請求。
想象一下從API請求一些數據,根據具體的情況,服務器需要一些時間來處理請求,同時阻塞主線程,使網頁長時間處于無響應的狀態。
這就是引入異步 JavaScript 的原因。使用異步 JavaScript(如 回調函數、promise、async/await),可以不用阻塞主線程的情況下長時間執行網絡請求 :)
可能你知道不知道 異步 JavsScript 是如何工作,并不要緊,但知道它是如何工作,對 JavaScript 異步更深入的了解是有幫助的。
所以不在啰嗦了,我們開始吧 :)
同步JavaScript是如何工作的?在深入研究異步JavaScript之前,讓我們首先了解同步 JavaScript 代碼如何在 JavaScript 引擎中執行。例如:
const second = () => { console.log("Hello there!"); } const first = () => { console.log("Hi there!"); second(); console.log("The End"); } first();
要理解上述代碼如何在 JavaScript 引擎中執行,我們必須理解執行上下文和調用堆棧(也稱為執行堆棧)的概念。
函數代碼在函數執行上下文中執行,全局代碼在全局執行上下文中執行。每個函數都有自己的執行上下文。
調用棧調用堆棧顧名思義是一個具有LIFO(后進先出)結構的堆棧,用于存儲在代碼執行期間創建的所有執行上下文。
JavaScript 只有一個調用棧,因為它是一種單線程編程語言。調用堆棧具有 LIFO 結構,這意味著項目只能從堆棧頂部添加或刪除。
讓我們回到上面的代碼片段,并嘗試理解代碼如何在JavaScript引擎中執行。
const second = () => { console.log("Hello there!"); } const first = () => { console.log("Hi there!"); second(); console.log("The End"); } first();這里發生了什么?
當執行此代碼時,將創建一個全局執行上下文(由main()表示)并將其推到調用堆棧的頂部。當遇到對first()的調用時,它會被推送到堆棧的頂部。
接下來,console.log("Hi there!")被推送到堆棧的頂部,當它完成時,它會從堆棧中彈出。之后,我們調用second(),因此second()函數被推到堆棧的頂部。
console.log("Hello there!")被推送到堆棧頂部,并在完成時彈出堆棧。second() 函數結束,因此它從堆棧中彈出。
console.log(“the End”)被推到堆棧的頂部,并在完成時刪除。之后,first()函數完成,因此從堆棧中刪除它。
程序在這一點上完成了它的執行,所以全局執行上下文(main())從堆棧中彈出。
異步JavaScript是如何工作的?現在我們已經對調用堆棧和同步JavaScript的工作原理有了基本的了解,讓我們回到異步JavaScript。
阻塞是什么?讓我們假設我們正在以同步的方式進行圖像處理或網絡請求。例如:
const processImage = (image) => { /** * doing some operations on image **/ console.log("Image processed"); } const networkRequest = (url) => { /** * requesting network resource **/ return someData; } const greeting = () => { console.log("Hello World"); } processImage(logo.jpg); networkRequest("www.somerandomurl.com"); greeting();
做圖像處理和網絡請求需要時間,當processImage()函數被調用時,它會根據圖像的大小花費一些時間。
processImage() 函數完成后,將從堆棧中刪除它。然后調用 networkRequest() 函數并將其推入堆棧。同樣,它也需要一些時間來完成執行。
最后,當networkRequest()函數完成時,調用greeting()函數,因為它只包含一個控制臺。日志語句和控制臺。日志語句通常很快,因此greeting()函數立即執行并返回。
因此,我們必須等待函數(如processImage()或networkRequest())完成。這意味著這些函數阻塞了調用堆棧或主線程。因此,在執行上述代碼時,我們不能執行任何其他操作,這是不理想的。
那么解決辦法是什么呢?最簡單的解決方案是異步回調。我們使用異步回調使代碼非阻塞。例如:
const networkRequest = () => { setTimeout(() => { console.log("Async Code"); }, 2000); }; console.log("Hello World"); networkRequest();
這里我使用了setTimeout方法來模擬網絡請求。請記住setTimeout不是JavaScript引擎的一部分,它是web api(在瀏覽器中)和C/ c++ api(在node.js中)的一部分。
為了理解這段代碼是如何執行的,我們必須理解更多的概念,比如事件輪詢和回調隊列(或消息隊列)。
事件輪詢、web api和消息隊列不是JavaScript引擎的一部分,而是瀏覽器的JavaScript運行時環境或Nodejs JavaScript運行時環境的一部分(對于Nodejs)。在Nodejs中,web api被c/c++ api所替代。
現在讓我們回到上面的代碼,看看它是如何異步執行的。
const networkRequest = () => { setTimeout(() => { console.log("Async Code"); }, 2000); }; console.log("Hello World"); networkRequest(); console.log("The End");
當上述代碼在瀏覽器中加載時,console.log(" Hello World ") 被推送到堆棧中,并在完成后彈出堆棧。接下來,將遇到對 networkRequest() 的調用,因此將它推到堆棧的頂部。
下一個 setTimeout() 函數被調用,因此它被推到堆棧的頂部。setTimeout()有兩個參數:
1) 回調和
2) 以毫秒(ms)為單位的時間。
setTimeout() 方法在web api環境中啟動一個2s的計時器。此時,setTimeout()已經完成,并從堆棧中彈出。cosole.log(“the end”) 被推送到堆棧中,在完成后執行并從堆棧中刪除。
同時,計時器已經過期,現在回調被推送到消息隊列。但是回調不會立即執行,這就是事件輪詢開始的地方。
事件輪詢事件輪詢的工作是監聽調用堆棧,并確定調用堆棧是否為空。如果調用堆棧是空的,它將檢查消息隊列,看看是否有任何掛起的回調等待執行。
在這種情況下,消息隊列包含一個回調,此時調用堆棧為空。因此,事件輪詢將回調推到堆棧的頂部。
然后是 console.log(“Async Code”) 被推送到堆棧頂部,執行并從堆棧中彈出。此時,回調已經完成,因此從堆棧中刪除它,程序最終完成。
消息隊列還包含來自DOM事件(如單擊事件和鍵盤事件)的回調。例如:
document.querySelector(".btn").addEventListener("click",(event) => { console.log("Button Clicked"); });
對于DOM事件,事件偵聽器位于web api環境中,等待某個事件(在本例中單擊event)發生,當該事件發生時,回調函數被放置在等待執行的消息隊列中。
同樣,事件輪詢檢查調用堆棧是否為空,并在調用堆棧為空并執行回調時將事件回調推送到堆棧。
延遲函數執行我們還可以使用setTimeout來延遲函數的執行,直到堆棧清空為止。例如
const bar = () => { console.log("bar"); } const baz = () => { console.log("baz"); } const foo = () => { console.log("foo"); setTimeout(bar, 0); baz(); } foo();
打印結果:
foo baz bar
當這段代碼運行時,第一個函數foo()被調用,在foo內部我們調用console.log("foo"),然后setTimeout()被調用,bar()作為回調函數和時0秒計時器。
現在,如果我們沒有使用 setTimeout, bar() 函數將立即執行,但是使用 setTimeout 和0秒計時器,將bar的執行延遲到堆棧為空的時候。
0秒后,bar()回調被放入等待執行的消息隊列中。但是它只會在堆棧完全空的時候執行,也就是在baz和foo函數完成之后。
ES6 任務隊列我們已經了解了異步回調和DOM事件是如何執行的,它們使用消息隊列存儲等待執行所有回調。
ES6引入了任務隊列的概念,任務隊列是 JavaScript 中的 promise 所使用的。消息隊列和任務隊列的區別在于,任務隊列的優先級高于消息隊列,這意味著任務隊列中的promise 作業將在消息隊列中的回調之前執行,例如:
const bar = () => { console.log("bar"); }; const baz = () => { console.log("baz"); }; const foo = () => { console.log("foo"); setTimeout(bar, 0); new Promise((resolve, reject) => { resolve("Promise resolved"); }).then(res => console.log(res)) .catch(err => console.log(err)); baz(); }; foo();
打印結果:
foo baz Promised resolved bar
我們可以看到 promise 在 setTimeout 之前執行,因為 promise 響應存儲在任務隊列中,任務隊列的優先級高于消息隊列。
小結因此,我們了解了異步 JavaScript 是如何工作的,以及調用堆棧、事件循環、消息隊列和任務隊列等概念,這些概念共同構成了 JavaScript 運行時環境。雖然成為一名出色的JavaScript開發人員并不需要學習所有這些概念,但是了解這些概念是有幫助的:)
參考:
Understanding Asynchronous JavaScript?—?the Event Loop
你的點贊是我持續分享好東西的動力,歡迎點贊!
歡迎加入前端大家庭,里面會經常分享一些技術資源。文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/99335.html
摘要:主線程不斷重復上面的三步,此過程也就是常說的事件循環。所以主線程代碼執行時間過長,會阻塞事件循環的執行。參考資料這一次,徹底弄懂執行機制任務隊列的順序機制事件循環搞懂異步事件輪詢與中的事件循環 1. 說明 讀過本文章后,您能知道: JavaScript代碼在瀏覽器中的執行機制和事件循環 面試中經常遇到的代碼輸出順序問題 首先通過一段代碼來驗證你是否了解代碼輸出順序,如果你不知道輸出...
摘要:徹底搞懂執行機制首先我們大家都了解的是,是一門單線程語言,所以我們就可以得出是按照語句順序執行的首先看這個顯然大家都知道結果,依次輸出,然而換一種這個時候再看代碼的順序執行,輸出,,,。不過即使主線程為空,也是達不到的,根據標準,最低是。 徹底搞懂JavaScript執行機制 首先我們大家都了解的是,JavaScript 是一門單線程語言,所以我們就可以得出: JavaScript 是...
本文涵蓋 面試題的引入 對事件循環面試題執行順序的一些疑問 通過面試題對微任務、事件循環、定時器等對深入理解 結論總結 面試題 面試題如下,大家可以先試著寫一下輸出結果,然后再看我下面的詳細講解,看看會不會有什么出入,如果把整個順序弄清楚 Node.js 的執行順序應該就沒問題了。 async function async1(){ console.log(async1 start) ...
摘要:瀏覽器是多進程的詳情看我上篇總結瀏覽器執行機制的文章深入前端徹底搞懂瀏覽器運行機制瀏覽器每打開一個標簽頁,就相當于創建了一個獨立的瀏覽器進程。執行異步操作事件完成,回調函數進入。主線程從讀取回調函數并執行。 最近看了很多關于JS運行機制的文章,每篇都獲益匪淺,但各有不同,所以在這里對這幾篇文章里說的很精辟的地方做一個總結,參考文章鏈接見最后。本文博客地址 了解進程和線程 進程是應用...
摘要:瀏覽器是多進程的詳情看我上篇總結瀏覽器執行機制的文章深入前端徹底搞懂瀏覽器運行機制瀏覽器每打開一個標簽頁,就相當于創建了一個獨立的瀏覽器進程。執行異步操作事件完成,回調函數進入。主線程從讀取回調函數并執行。 最近看了很多關于JS運行機制的文章,每篇都獲益匪淺,但各有不同,所以在這里對這幾篇文章里說的很精辟的地方做一個總結,參考文章鏈接見最后。本文博客地址 了解進程和線程 進程是應用...
閱讀 703·2021-11-18 10:02
閱讀 3579·2021-09-02 10:21
閱讀 1725·2021-08-27 16:16
閱讀 2058·2019-08-30 15:56
閱讀 2386·2019-08-29 16:53
閱讀 1373·2019-08-29 11:18
閱讀 2952·2019-08-26 10:33
閱讀 2641·2019-08-23 18:34