摘要:曾經的理解首先,是單線程語言,也就意味著同一個時間只能做一件事,那么為什么不是多線程呢這樣還能提高效率啊假定同時有兩個線程,一個線程在某個節點上編輯了內容,而另一個線程刪除了這個節點,這時瀏覽器就很懵逼了,到底以執行哪個操作呢所以,設計者把
Event Loop曾經的理解
首先,JS是單線程語言,也就意味著同一個時間只能做一件事,那么
為什么JavaScript不是多線程呢?這樣還能提高效率啊
假定JS同時有兩個線程,一個線程在某個DOM節點上編輯了內容,而另一個線程刪除了這個節點,這時瀏覽器就很懵逼了,到底以執行哪個操作呢?
所以,設計者把JS設計成單線程應該就很好理解了,為了避免類似上述操作的復雜性,這一特征將來也不會變。
但是單線程有一個問題:一旦這個線程被阻塞就無法繼續工作了,這肯定是不行的
由于異步編程可以實現“非阻塞”的調用效果,引入異步編程自然就是順理成章的事情了,那么
JS單線程如何實現異步的呢?
今天的主咖登場——事件循環(Event Loop),JS異步是通過的事件循環實現的,理解了Event Loop機制,就理解
了JS的執行機制。
先來段代碼:
console.log(1) setTimeout(()=>{ console.log(2) }, 0) for(let i = 3; i < 10000; i++){ console.log(i) }
執行結果:1 3 4 5 6 7 ... 9997 9998 9999 2
setTimeout里的函數并沒有立即執行,我們都知道這部分叫異步處理模塊,延遲了一段時間,滿足一定條件后才執行
仔細想想,我們在JS里通常把任務分為“同步任務”和“異步任務”,它們有以下的執行順序:
判斷任務是同步的還是異步的,如果是同步任務就進入主線程執行棧中,如果是異步任務就進入Event Table并注冊函數,當滿足觸發條件后,進入Event Queue
只有等到主線程的同步任務執行完后,才會去Event Queue中查找是否有可執行的異步任務,如有,則進入主線程執行
以上兩步循環執行,就是所謂的Event Loop,所以上述代碼里:
console.log(1) 是同步任務,進入主線程,立即執行
setTimeout 是異步任務,進入Event Table,0ms后(實際時間可能有出入,見注文)進入Event Queue,等待進入主線程
for 是同步任務,進入主線程,立即執行
所有主線程任務執行完后,setTimeout從Event Queue進入主線程執行
*注:HTML5規范規定最小延遲時間不能小于4ms,即x如果小于4,會被當做4來處理。 不過不同瀏覽器的實現不一樣,比如,Chrome可以設置1ms,IE11/Edge是4ms
這就是我之前對Event Loop的理解,但是自從看了這篇文章深入理解JS引擎的執行機制顛覆了我對Event Loop認識三觀,看下面的代碼
Event Loop現在的理解console.log("start") setTimeout(()=>{ console.log("setTimeout") }, 0) new Promise((resolve)=>{ console.log("promise") resolve() }).then(()=>{ console.log("then") }) console.log("end")
嘗試按照我們上面的JS執行機制去分析:
console.log("start")是同步任務,進入主線程,立即執行 setTimeout是異步任務,進入Event
Table,滿足觸發條件后進入Event Queue
new Promise是同步任務,進入主線程,立即執行
.then是異步任務,進入Event Table,滿足觸發條件后進入Event Queue,排在Event Queue隊尾 console.log("end")是同步任務,進入主線程,立即執行
所以執行結果是:start > promise > end > setTimeout > then
But但是,親自跑了代碼結果卻是:start > promise > end > then > setTimeout
對比結果發現,難道Event Queue里面的順序不是隊列的先進先出的順序嗎?還是這塊執行時有什么改變,事實就是,前面按照同步和異步任務劃分的方式并不準確,那么怎么劃分才是準確的呢,先看圖(轉自谷雨JavaScript 異步、棧、事件循環、任務隊列):
咣咣咣~敲黑板,知識點,知識點,知識點:
Js 中,有兩類任務隊列:宏任務隊列(macro tasks)和微任務隊列(micro tasks)
宏任務隊列可以有多個,微任務隊列只有一個。那么什么任務,會分到哪個隊列呢?
宏任務:script(全局任務), setTimeout, setInterval, setImmediate, I/O, UI rendering.
微任務:process.nextTick, Promise的then或catch, Object.observer, MutationObserver.
那么結合上面的流程圖和最初理解的執行機制,總結了一下更為準確的JS執行機制:
取且僅取一個宏任務來執行(第一個宏任務就是script任務)。執行過程中判斷是同步還是異步任務,如果是同步任務就進入主線程執行棧中,如果是異步任務就進入異步處理模塊,這些異步處理模塊的任務當滿足觸發條件后,進入任務隊列,進入任務隊列后,按照宏任務和微任務進行劃分,劃分完畢后,執行下一步。
如果微任務隊列不為空,則依次取出微任務來執行,直到微任務隊列為空(即當前loop所有微任務執行完),執行下一步。
進入下一輪loop或更新UI渲染。
Event Loop就是循環執行上面三步,接下來使用上面的結論分析個例子幫助理解
微任務里嵌套宏任務
console.log("第一輪"); setTimeout(() => { //為了便于敘述時區分,標記為 setTimeout1 console.log("第二輪"); Promise.resolve().then(() => { //為了便于敘述時區分,標記為 then1 console.log("A"); }) }, 0); setTimeout(() => { //為了便于敘述時區分,標記為 setTimeout2 console.log("第三輪"); console.log("B"); }, 0); new Promise((resolve)=>{ //為了便于敘述時區分,標記為 Promise1 console.log("C") resolve() }).then(() => { //為了便于敘述時區分,標記為 then2 Promise.resolve().then(() => { //為了便于敘述時區分,標記為 then3 console.log("D") setTimeout(() => { //為了便于敘述時區分,標記為 setTimeout3 console.log("第四輪"); console.log("E"); }, 0); }); });
執行結果:第一輪 > C > D > 第二輪 > A > 第三輪 > B > 第四輪 > E
分析:
loop1:
第一步:首先執行全局宏任務,里面同步任務有下面兩個,都立即進入主線程執行完后出棧
1.console.log("第一輪")
2.Promise1
輸出 “第一輪” > “C”
異步任務有三個,分別進入相應的任務隊列:
1.setTimeout1,該任務按照劃分標準是 宏任務 setTimeout(() => { console.log("第二輪"); Promise.resolve().then(() => { console.log("A"); }) }, 0);
2.setTimeout2,該任務按照劃分標準是 宏任務 setTimeout(() => { console.log("第三輪"); console.log("B"); }, 0);
3.then2,該任務按照劃分標準是 微任務 .then(() => { Promise.resolve().then(() => { console.log("D") setTimeout(() => { console.log("第四輪"); console.log("E"); }, 0); }); });
所以此時宏任務隊列為: setTimeout1,setTimeout2
微任務隊列為: then2
第二步:loop1 微任務隊列不為空,then2出隊列并執行,然后這個微任務里的 then3繼續進入微任務隊列 ,setTimeout3進入宏任務隊列隊尾
那么此時微任務隊列為: then3
宏任務隊列為:setTimeout1,setTimeout2,setTimeout3
但是此時第二步并沒有完,因為微任務隊列只要不為空,就一直執行當前loop的微任務,所以從微任務隊列取出 then3 執行輸出 “D”
此時微任務隊列為: 空
宏任務隊列為:setTimeout1,setTimeout2,setTimeout3
到目前為止,當前loop的微任務對列為空,進入下一個loop,輸出情況是“第一輪” > “C” > “D”
loop2:
第一步:在宏任務隊列隊首里取出一個任務執行,即setTimeout1執行輸出“第二輪”,并then1進入微任務隊列
此時微任務隊列為: then1
宏任務隊列為:setTimeout2,setTimeout3
第二步:loop2 微任務隊列不為空,則從微任務隊列取出then1執行,輸出“A”
此時微任務隊列為: 空
宏任務隊列為:setTimeout2,setTimeout3
到目前為止,當前loop的微任務對列為空,進入下一個loop,輸出情況是“第一輪” > “C” > “D” > “第二輪” > “A”
loop3:
第一步:在宏任務隊列隊首里取出一個任務執行,即setTimeout2執行輸出“第三輪” > “B”
此時微任務隊列為: 空
宏任務隊列為:setTimeout3
第二步:由于loop3 微任務隊列為空,則直接進入下一輪loop,輸出情況是“第一輪” > “C” > “D” > “第二輪” > “A” > “第三輪” > “B”
loop4:
第一步:在宏任務隊列隊首里取出一個任務執行,即setTimeout3執行輸出“第四輪” > “E”
此時微任務隊列為: 空
宏任務隊列為:空
第二步:由于loop4 微任務隊列為空,宏任務隊列也為空,則此次Event Loop結束,最終輸出情況是“第一輪” > “C” > “D” > “第二輪” > “A” > “第三輪” > “B” > “第四輪” > “E”
上面的整個過程就是更為準確的Event Loop,下面還有個不同的例子供讀者自行嘗試
宏任務里嵌套微任務
console.log("第一輪"); setTimeout(() => { console.log("第二輪"); Promise.resolve().then(() => { console.log("A"); }) }, 0); setTimeout(() => { console.log("第三輪"); console.log("B"); }, 0); new Promise((resolve) => { console.log("C") resolve() }).then(() => { //注意,這個函數改動啦 setTimeout(() => { console.log("第四輪"); console.log("E"); Promise.resolve().then(() => { console.log("D") }); }, 0); });
執行結果:第一輪 > C > 第二輪 > A > 第三輪 > B > 第四輪 > E > D
Links:深入理解JS引擎的執行機制
JavaScript 異步、棧、事件循環、任務隊列
JavaScript 運行機制詳解:深入理解Event Loop
JavaScript:并發模型與Event Loop
JavaScript 運行機制詳解:再談Event Loop[阮一峰]
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92709.html
摘要:主線程不斷重復上面的三步,此過程也就是常說的事件循環。所以主線程代碼執行時間過長,會阻塞事件循環的執行。參考資料這一次,徹底弄懂執行機制任務隊列的順序機制事件循環搞懂異步事件輪詢與中的事件循環 1. 說明 讀過本文章后,您能知道: JavaScript代碼在瀏覽器中的執行機制和事件循環 面試中經常遇到的代碼輸出順序問題 首先通過一段代碼來驗證你是否了解代碼輸出順序,如果你不知道輸出...
摘要:從異步過程的角度看,函數就是異步過程的發起函數,事件監聽函數就是異步過程的回調函數。事件觸發時,表示異步任務完成,會將事件監聽器函數封裝成一條消息放到消息隊列中,等待主線程執行。 1.為什么JavaScript是單線程? JavaScript語言的一大特點就是單線程,也就是說,同一個時間只能做一件事。那么,為什么JavaScript不能有多個線程呢?這樣能提高效率啊。JavaScrip...
摘要:異步任務必須指定回調函數,當異步任務從任務隊列回到執行棧,回調函數就會執行。事件循環主線程從任務隊列中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為。事件循環事件循環是指主線程重復從消息隊列中取消息執行的過程。 參考鏈接:這一次,徹底弄懂 JavaScript 執行機制https://zhuanlan.zhihu.com/p/...從瀏覽器多進程到JS單線程,JS運行機制...
摘要:事件循環機制事件循環機制分為瀏覽器和事件循環機制,兩者的實現技術不一樣,瀏覽器是中定義的規范,是由庫實現。整個事件循環完成之后,會去檢測微任務的任務隊列中是否存在任務,存在就執行。 文章來自我的 github 博客,包括技術輸出和學習筆記,歡迎star。 先來明白些概念性內容。 進程、線程 進程是系統分配的獨立資源,是 CPU 資源分配的基本單位,進程是由一個或者多個線程組成的。 線...
摘要:單線程的話,如果我們做一些的操作比如說這是一個耗時的操所那么在這將近一秒內,線程就會被阻塞,無法繼續執行下面的任務。事件循環的主要機制就是任務隊列機制一個事件循環有一個或者多個任務隊列。 瀏覽器中的事件循環機制 網上一搜事件循環, 很多文章標題的前面會加上 JavaScript, 但是我覺得事件循環機制跟 JavaScript 沒什么關系, JavaScript 只是一門解釋型語言, ...
摘要:事件完成,回調函數進入。主線程從讀取回調函數并執行。終于執行完了,終于從進入了主線程執行。遇到,立即執行。宏任務微任務第三輪事件循環宏任務執行結束,執行兩個微任務和。事件循環事件循環是實現異步的一種方法,也是的執行機制。 本文的目的就是要保證你徹底弄懂javascript的執行機制,如果讀完本文還不懂,可以揍我。不論你是javascript新手還是老鳥,不論是面試求職,還是日常開發工作...
閱讀 2207·2021-11-22 11:56
閱讀 2650·2021-10-08 10:05
閱讀 7804·2021-09-22 15:53
閱讀 1918·2021-09-22 15:29
閱讀 2238·2021-09-08 09:35
閱讀 3359·2021-09-07 10:12
閱讀 1384·2019-08-30 13:11
閱讀 1978·2019-08-28 17:54