摘要:下面開始分析開頭的代碼第一輪事件循環流程整體作為第一個宏任務進入主線程,遇到,輸出遇到函數聲明,聲明暫時不用管遇到,其回調函數被分發到微任務中。我們記為遇到,其回調函數被分發到宏任務中。
先上一道常見的筆試題
console.log("1"); async function async1() { console.log("2"); await async2(); console.log("3"); } async function async2() { console.log("4"); } process.nextTick(function() { console.log("5"); }) setTimeout(function() { console.log("6"); process.nextTick(function() { console.log("7"); }) new Promise(function(resolve) { console.log("8"); resolve(); }).then(function() { console.log("9") }) }) async1(); new Promise(function(resolve) { console.log("10"); resolve(); }).then(function() { console.log("11"); }); console.log("12");
大家可以先配合下面這個圖片思考一下輸出順序及這么運行的原因
上面簡化圖解可拆分為三部分:
一、JavaScript引擎*Memory Heap 內存堆 —— 這是內存發生分配的地方
*Call Stack 調用棧 —— 這是代碼運行時棧幀存放的位置
二、Runtime 運行時我們要知道的是,像setTimeOut DOM AJAX,都不是由JavaScript引擎提供,而是由瀏覽器提供,統稱為Web APIs
三、EventLoopjavascript是一門單線程語言,雖然HTML5提出了Web-works這樣的多線程解決方案,但是并沒有改變JaveScript是單線程的本質。
什么是H5 Web Works?就是將一些大計算量的代碼交由web Worker運行而不凍結用戶界面,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質
既然js是單線程的,就是同一時間只能做一件事情。那么問題來了,我們訪問一個頁面,這個頁面的初始化代碼運行時間很長,比如有很多圖片、視頻、外部資源等等,難道我們也要一直在那等著嗎?答案當然是 不能
所以就出現了兩類任務:
同步任務
異步任務
同步和異步任務分別進入不同的 "‘場所"’ 執行。所有同步任務都在主線程上執行,形成一個執行棧;而異步任務進入Event Table并注冊回調函數
當這個異步任務有了運行結果,Event Table會將這個回調函數移入Event Queue,進入等待狀態
當主線程內同步任務執行完成,會去Event Queue讀取對應的函數,并結束它的等待狀態,進入主線程執行
主線程不斷重復上面3個步驟,也就是常說的Event Loop(事件循環)。
那么我們怎么知道什么時候主線程是空的呢?js引擎存在monitoring process進程,會持續不斷的檢查主線程執行棧是否為空,一旦為空,就會去Event Queue那里檢查是否有等待被調用的函數。
setTimeout(fn,0)這里的延遲0秒時什么意思呢?
含義是,當主線程執行棧內為空時,不用等待,就馬上執行。
setInterval和setTimeout類似,只是前者是循環的執行。對于執行順序來說,setInterval會每隔指定的時間將注冊的函數置入Event Queue,如果前面的任務耗時太久,那么同樣需要等待。
對于setInterval(fn,ms)來說,我們已經知道不是每過ms秒會執行一次fn,而是每過ms秒,會有fn進入Event Queue。一旦setInterval的回調函數fn執行時間超過了延遲時間ms,那么就完全看不出來有時間間隔了。
事件循環的順序,決定js代碼的執行順序。進入整體代碼(宏任務)后,開始第一次循環。接著執行所有的微任務。然后再次從宏任務開始,找到其中一個任務隊列執行完畢,再執行所有的微任務。
除了廣義的同步任務和異步任務,我們對任務有更精細的定義:
macro-task(宏任務):包括整體代碼script、setTimeout、setInterval、I/O、UI交互事件,可以理解是每次執行棧執行的代碼就是一個宏任務;
micro-task(微任務):Promise,process.nextTick,且process.nextTick優先級大于promise.then。可以理解是在當前 task 執行結束后立即執行的任務;
setTimeout(fn, 0)在下一輪“事件循環”開始時執行,Promise.then()在本輪“事件循環”結束時執行。
不同類型的任務會進入對應的Event Queue:
Promise中的異步體現在then和catch中,所以寫在Promise中的代碼是被當做同步任務立即執行的。
await實際上是一個讓出線程的標志。await后面的表達式會先執行一遍,將await后面的代碼加入到microtask中,然后就會跳出整個async函數來執行后面的代碼;
因為async await 本身就是promise+generator的語法糖。所以await后面的代碼是microtask。
下面開始分析開頭的代碼
console.log("1"); async function async1() { console.log("2"); await async2(); console.log("3"); } async function async2() { console.log("4"); } process.nextTick(function() { console.log("5"); }) setTimeout(function() { console.log("6"); process.nextTick(function() { console.log("7"); }) new Promise(function(resolve) { console.log("8"); resolve(); }).then(function() { console.log("9") }) }) async1(); new Promise(function(resolve) { console.log("10"); resolve(); }).then(function() { console.log("11"); }); console.log("12");
第一輪事件循環流程:
整體script作為第一個宏任務進入主線程,遇到console.log,輸出1
遇到async1、async2函數聲明,聲明暫時不用管
遇到process.nextTick(),其回調函數被分發到微任務Event Queue中。我們記為process1
遇到setTimeout,其回調函數被分發到宏任務Event Queue中。我們暫且記為setTimeout1
執行async1,遇到console.log,輸出2
下面這里是最難理解的地方
我們知道使用 async 定義的函數,當它被調用時,它返回的是一個Promise對象
而當await后面的表達式是一個Promise時,它的返回值實際上是Promise的回調函數resolve的參數
遇到await async2()調用,發現async2也是一個 async 定義的函數,所有直接執行輸出4,同時返回了一個Promise。劃重點:此時返回的Promise被分配到微任務Event Queue中,我們記為await1。await會讓出線程,接下來就會跳出async1函數繼續往下執行。
遇到Promise,new Promise直接執行,輸出10。then被分發到微任務Event Queue中。我們記為then1
遇到console.log,輸出12
宏任務Event Queue | 微任務Event Queue |
---|---|
setTimeout1 | process1 |
await1 | |
then1 |
上表是第一輪事件循環宏任務結束時各Event Queue的情況,此時已經輸出了1 2 4 10 12
我們發現了process1、await1 和then1三個微任務
執行process1,輸出5
取到 await1 ,就是 async1 放進去的Promise,執行Promise時發現又遇到了他的真命天子resolve函數,劃重點:這個resolve又會被放入微任務Event Queue中,我們記為await2,然后再次跳出 async1函數 繼續下一個任務。
執行then1,輸出11
宏任務Event Queue | 微任務Event Queue |
---|---|
setTimeout1 | await2 |
到這里,已經輸出了1 2 4 10 12 5 11
此時還有一個await2 微任務
它是async1 放進去的Promise的resolve回調,執行它(因為 async2 并沒有return東西,所以這個resolve的參數是undefined),此時 await 定義的這個 Promise 已經執行完并且返回了結果,所以可以繼續往下執行 async1函數 后面的任務了,那就是console.log(3),輸出3
到這里,第一輪事件循環結束,此時,輸出順序是 1 2 4 10 12 5 11 3
第二輪時間循環從setTimeout1宏任務開始
遇到console.log,輸出6
遇到process.nextTick(),同樣將其分發到微任務Event Queue中,記為process2
遇到new Promise立即執行輸出8,then也分發到微任務Event Queue中,記為then2
宏任務Event Queue | 微任務Event Queue |
---|---|
process2 | |
then2 |
上表是第二輪事件循環宏任務結束時各Event Queue的情況,此時輸出情況是
我們發現了process2和then2兩個微任務
執行process2,輸出7
執行then2,輸出9
第二輪事件循環結束,第二輪輸出6 8 7 9
整段代碼,共進行了兩次事件循環,完整的輸出 1 2 4 10 12 5 11 3 6 8 7 9
四、寫在最后到這里,大家應該已經清楚了JS的事件循環機制,后面不管在工作還是面試中,肯定都是游刃有余啦~
本篇是我開始的第一篇文章,還希望大家多多支持,不吝賜教哇,也希望可以提出意見或建議。
資料參考:
https://segmentfault.com/a/11...
https://juejin.im/post/59e85e...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/104746.html
摘要:下面開始分析開頭的代碼第一輪事件循環流程整體作為第一個宏任務進入主線程,遇到,輸出遇到函數聲明,聲明暫時不用管遇到,其回調函數被分發到微任務中。我們記為遇到,其回調函數被分發到宏任務中。 先上一道常見的筆試題 console.log(1); async function async1() { console.log(2); await async2(); con...
摘要:今天這篇文章我們來看看一道必會面試題,即如何實現一個深拷貝。木易楊注意這里使用上面測試用例測試一下一個簡單的深拷貝就完成了,但是這個實現還存在很多問題。 引言 上篇文章詳細介紹了淺拷貝 Object.assign,并對其進行了模擬實現,在實現的過程中,介紹了很多基礎知識。今天這篇文章我們來看看一道必會面試題,即如何實現一個深拷貝。本文會詳細介紹對象、數組、循環引用、引用丟失、Symbo...
摘要:廢話不多說上代碼完整項目地址項目地址棋盤樣式棋盤元素初始化初始化角色黑旗子白旗是否已分出勝負走棋記錄當前步清空棋子和事件初始化棋盤矩陣刻畫棋盤棋盤網格刻畫棋子每次落子結束都要判斷輸贏落子如果點擊的是棋子則中斷空的棋位才可落子落 廢話不多說上代碼!完整項目地址:GitHub項目地址 class Gobang { constructor(options) { ...
摘要:響應由三個部分組成,分別是狀態行消息報頭響應正文。詳情參考小汪之前寫的文章瀏覽器內核之解釋器和模型解釋解釋過程是指從字符串經過解釋器處理后變成渲染引擎內部規則的表示過程。 showImg(https://segmentfault.com/img/remote/1460000016404846); 前言 小汪最近在看【WebKit 技術內幕】一書,說實話,這本書寫的太官方了,不通俗易懂。...
閱讀 1319·2021-11-24 09:38
閱讀 3256·2021-11-22 12:03
閱讀 4158·2021-11-11 10:59
閱讀 2317·2021-09-28 09:36
閱讀 1032·2021-09-09 09:32
閱讀 3412·2021-08-05 10:00
閱讀 2528·2021-07-23 15:30
閱讀 2973·2019-08-30 13:12