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