成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

總結(jié):JavaScript異步、事件循環(huán)與消息隊列、微任務(wù)與宏任務(wù)

qianfeng / 3884人閱讀

摘要:單線程異步非阻塞然后,這又牽扯到了事件循環(huán)消息隊列,還有微任務(wù)宏任務(wù)這些。此步的位置不確定某個時刻后,定時器觸發(fā)線程通知事件觸發(fā)線程,事件觸發(fā)線程將回調(diào)函數(shù)加入消息隊列隊尾,等待引擎線程執(zhí)行。

前言

Philip Roberts 在演講 great talk at JSConf on the event loop 中說:要是用一句話來形容 JavaScript,我可能會這樣:

“JavaScript 是單線程、異步、非阻塞、解釋型腳本語言?!?/pre>

單線程 ?

異步 ? ?

非阻塞 ? ? ?

然后,這又牽扯到了事件循環(huán)、消息隊列,還有微任務(wù)、宏任務(wù)這些。

作為一個初學(xué)者,對這些了解甚少。

這幾天翻閱了不少資料,似乎了解到了一二,是時候總結(jié)一下了,它們困擾了我好一段時間,就像學(xué)高數(shù)那會兒自己去理解一個概念一樣。

單線程與多線程

單線程語言:JavaScript 的設(shè)計就是為了處理瀏覽器網(wǎng)頁的交互(DOM操作的處理、UI動畫等),決定了它是一門單線程語言。

如果有多個線程,它們同時在操作 DOM,那網(wǎng)頁將會一團(tuán)糟。

JavaScript 是單線程的,那么處理任務(wù)是一件接著一件處理,從上往下順序執(zhí)行:

console.log("script start")
console.log("do something...")
console.log("script end")

// script start
// do something...
// script end

上面的代碼會依次打印: "script start" >> "do something..." >> "script end"

那如果一個任務(wù)的處理耗時(或者是等待)很久的話,如:網(wǎng)絡(luò)請求、定時器、等待鼠標(biāo)點擊等,后面的任務(wù)也就會被阻塞,也就是說會阻塞所有的用戶交互(按鈕、滾動條等),會帶來極不友好的體驗。

但是:

console.log("script start")

console.log("do something...")

setTimeout(() => {
  console.log("timer over")
}, 1000)

// 點擊頁面
console.log("click page")

console.log("script end")

// script start
// do something...
// click page
// script end
// timer over

"timer over""script end" 后再打印,也就是說計時器并沒有阻塞后面的代碼。那,發(fā)生了什么?

其實,JavaScript 單線程指的是瀏覽器中負(fù)責(zé)解釋和執(zhí)行 JavaScript 代碼的只有一個線程,即為JS引擎線程,但是瀏覽器的渲染進(jìn)程是提供多個線程的,如下:

JS引擎線程

事件觸發(fā)線程

定時觸發(fā)器線程

異步http請求線程

GUI渲染線程

瀏覽器渲染進(jìn)程參考這里

當(dāng)遇到計時器、DOM事件監(jiān)聽或者是網(wǎng)絡(luò)請求的任務(wù)時,JS引擎會將它們直接交給 webapi,也就是瀏覽器提供的相應(yīng)線程(如定時器線程為setTimeout計時、異步http請求線程處理網(wǎng)絡(luò)請求)去處理,而JS引擎線程繼續(xù)后面的其他任務(wù),這樣便實現(xiàn)了 異步非阻塞。

定時器觸發(fā)線程也只是為 setTimeout(..., 1000) 定時而已,時間一到,還會把它對應(yīng)的回調(diào)函數(shù)(callback)交給 消息隊列 去維護(hù),JS引擎線程會在適當(dāng)?shù)臅r候去消息隊列取出消息并執(zhí)行。

JS引擎線程什么時候去處理呢?消息隊列又是什么?

這里,JavaScript 通過 事件循環(huán) event loop 的機(jī)制來解決這個問題。

這個放在后面再討論吧!

同步與異步

上面說到了異步,JavaScript 中有同步代碼與異步代碼。

下面便是同步:

console.log("hello 0")

console.log("hello 1")

console.log("hello 2")

// hello 0
// hello 1
// hello 2

它們會依次執(zhí)行,執(zhí)行完了后便會返回結(jié)果(打印結(jié)果)。

setTimeout(() => {
  console.log("hello 0")
}, 1000)

console.log("hello 1")

// hello 1
// hello 0

上面的 setTimeout 函數(shù)便不會立刻返回結(jié)果,而是發(fā)起了一個異步,setTimeout 便是異步的發(fā)起函數(shù)或者是注冊函數(shù),() => {...} 便是異步的回調(diào)函數(shù)。

這里,JS引擎線程只會關(guān)心異步的發(fā)起函數(shù)是誰、回調(diào)函數(shù)是什么?并將異步交給 webapi 去處理,然后繼續(xù)執(zhí)行其他任務(wù)。

異步一般是以下:

網(wǎng)絡(luò)請求

計時器

DOM時間監(jiān)聽

...

事件循環(huán)與消息隊列

回到事件循環(huán) event loop

其實 事件循環(huán) 機(jī)制和 消息隊列 的維護(hù)是由事件觸發(fā)線程控制的。

事件觸發(fā)線程 同樣是瀏覽器渲染引擎提供的,它會維護(hù)一個 消息隊列。

JS引擎線程遇到異步(DOM事件監(jiān)聽、網(wǎng)絡(luò)請求、setTimeout計時器等...),會交給相應(yīng)的線程多帶帶去維護(hù)異步任務(wù),等待某個時機(jī)(計時器結(jié)束、網(wǎng)絡(luò)請求成功、用戶點擊DOM),然后由 事件觸發(fā)線程 將異步對應(yīng)的 回調(diào)函數(shù) 加入到消息隊列中,消息隊列中的回調(diào)函數(shù)等待被執(zhí)行。

同時,JS引擎線程會維護(hù)一個 執(zhí)行棧,同步代碼會依次加入執(zhí)行棧然后執(zhí)行,結(jié)束會退出執(zhí)行棧。

如果執(zhí)行棧里的任務(wù)執(zhí)行完成,即執(zhí)行棧為空的時候(即JS引擎線程空閑),事件觸發(fā)線程才會從消息隊列取出一個任務(wù)(即異步的回調(diào)函數(shù))放入執(zhí)行棧中執(zhí)行。

消息隊列是類似隊列的數(shù)據(jù)結(jié)構(gòu),遵循先入先出(FIFO)的規(guī)則。

執(zhí)行完了后,執(zhí)行棧再次為空,事件觸發(fā)線程會重復(fù)上一步操作,再取出一個消息隊列中的任務(wù),這種機(jī)制就被稱為事件循環(huán)(event loop)機(jī)制。

還是上面的代碼:

console.log("script start")

setTimeout(() => {
  console.log("timer over")
}, 1000)

// 點擊頁面
console.log("click page")

console.log("script end")

// script start
// click page
// script end
// timer over

執(zhí)行過程:

主代碼塊(script)依次加入執(zhí)行棧,依次執(zhí)行,主代碼塊為:

console.log("script start")

setTimeout()

console.log("click page")

console.log("script end")

console.log() 為同步代碼,JS引擎線程處理,打印 "script start",出棧;

遇到異步函數(shù) setTimeout,交給定時器觸發(fā)線程(異步觸發(fā)函數(shù)為:setTimeout,回調(diào)函數(shù)為:() => { ... }),JS引擎線程繼續(xù),出棧;

console.log() 為同步代碼,JS引擎線程處理,打印 "click page",出棧;

console.log() 為同步代碼,JS引擎線程處理,打印 "script end",出棧;

執(zhí)行棧為空,也就是JS引擎線程空閑,這時從消息隊列中取出(如果有的話)一條任務(wù)(callback)加入執(zhí)行棧,并執(zhí)行;

重復(fù)第6步。

(此步的位置不確定)某個時刻(1000ms后),定時器觸發(fā)線程通知事件觸發(fā)線程,事件觸發(fā)線程將回調(diào)函數(shù) () => { ... } 加入消息隊列隊尾,等待JS引擎線程執(zhí)行。

可以看出,setTimeout異步函數(shù)對應(yīng)的回調(diào)函數(shù)( () => {} )會在執(zhí)行棧為空,主代碼塊執(zhí)行完了后才會執(zhí)行。

零延時:

console.log("script start")

setTimeout(() => {
  console.log("timer 1 over")
}, 1000)

setTimeout(() => {
  console.log("timer 2 over")
}, 0)

console.log("script end")

// script start
// script end
// timer 2 over
// timer 1 over

這里會先打印 "timer 2 over",然后打印 "timer 1 over",盡管 timer 1 先被定時器觸發(fā)線程處理,但是 timer 2 的callback會先加入消息隊列。

上面,timer 2 的延時為 0ms,HTML5標(biāo)準(zhǔn)規(guī)定 setTimeout 第二個參數(shù)不得小于4(不同瀏覽器最小值會不一樣),不足會自動增加,所以 "timer 2 over" 還是會在 "script end" 之后。

就算延時為 0ms,只是 timer 2 的回調(diào)函數(shù)會立即加入消息隊列而已,回調(diào)的執(zhí)行還是得等執(zhí)行棧為空(JS引擎線程空閑)時執(zhí)行。

其實 setTimeout 的第二個參數(shù)并不能代表回調(diào)執(zhí)行的準(zhǔn)確的延時事件,它只能表示回調(diào)執(zhí)行的最小延時時間,因為回調(diào)函數(shù)進(jìn)入消息隊列后需要等待執(zhí)行棧中的同步任務(wù)執(zhí)行完成,執(zhí)行棧為空時才會被執(zhí)行。
宏任務(wù)與微任務(wù)

以上機(jī)制在ES5的情況下夠用了,但是ES6會有一些問題。

Promise同樣是用來處理異步的:

console.log("script start")

setTimeout(function() {
    console.log("timer over")
}, 0)

Promise.resolve().then(function() {
    console.log("promise1")
}).then(function() {
    console.log("promise2")
})

console.log("script end")

// script start
// script end
// promise1
// promise2
// timer over

WTF?? "promise 1" "promise 2" 在 "timer over" 之前打印了?

這里有一個新概念:macrotask(宏任務(wù)) 和 microtask(微任務(wù))。

所有任務(wù)分為 macrotaskmicrotask:

macrotask:主代碼塊、setTimeout、setInterval等(可以看到,事件隊列中的每一個事件都是一個 macrotask,現(xiàn)在稱之為宏任務(wù)隊列)

microtask:Promise、process.nextTick等

JS引擎線程首先執(zhí)行主代碼塊。

每次執(zhí)行棧執(zhí)行的代碼就是一個宏任務(wù),包括任務(wù)隊列(宏任務(wù)隊列)中的,因為執(zhí)行棧中的宏任務(wù)執(zhí)行完會去取任務(wù)隊列(宏任務(wù)隊列)中的任務(wù)加入執(zhí)行棧中,即同樣是事件循環(huán)的機(jī)制。

在執(zhí)行宏任務(wù)時遇到Promise等,會創(chuàng)建微任務(wù)(.then()里面的回調(diào)),并加入到微任務(wù)隊列隊尾。

microtask必然是在某個宏任務(wù)執(zhí)行的時候創(chuàng)建的,而在下一個宏任務(wù)開始之前,瀏覽器會對頁面重新渲染(task >> 渲染 >> 下一個task(從任務(wù)隊列中取一個))。同時,在上一個宏任務(wù)執(zhí)行完成后,渲染頁面之前,會執(zhí)行當(dāng)前微任務(wù)隊列中的所有微任務(wù)。

也就是說,在某一個macrotask執(zhí)行完后,在重新渲染與開始下一個宏任務(wù)之前,就會將在它執(zhí)行期間產(chǎn)生的所有microtask都執(zhí)行完畢(在渲染前)。

這樣就可以解釋 "promise 1" "promise 2" 在 "timer over" 之前打印了。"promise 1" "promise 2" 做為微任務(wù)加入到微任務(wù)隊列中,而 "timer over" 做為宏任務(wù)加入到宏任務(wù)隊列中,它們同時在等待被執(zhí)行,但是微任務(wù)隊列中的所有微任務(wù)都會在開始下一個宏任務(wù)之前都被執(zhí)行完。

在node環(huán)境下,process.nextTick的優(yōu)先級高于Promise,也就是說:在宏任務(wù)結(jié)束后會先執(zhí)行微任務(wù)隊列中的nextTickQueue,然后才會執(zhí)行微任務(wù)中的Promise。

執(zhí)行機(jī)制:

執(zhí)行一個宏任務(wù)(棧中沒有就從事件隊列中獲取)

執(zhí)行過程中如果遇到微任務(wù),就將它添加到微任務(wù)的任務(wù)隊列中

宏任務(wù)執(zhí)行完畢后,立即執(zhí)行當(dāng)前微任務(wù)隊列中的所有微任務(wù)(依次執(zhí)行)

當(dāng)前宏任務(wù)執(zhí)行完畢,開始檢查渲染,然后GUI線程接管渲染

渲染完畢后,JS引擎線程繼續(xù),開始下一個宏任務(wù)(從宏任務(wù)隊列中獲取)

總結(jié)

JavaScript 是單線程語言,決定于它的設(shè)計最初是用來處理瀏覽器網(wǎng)頁的交互。瀏覽器負(fù)責(zé)解釋和執(zhí)行 JavaScript 的線程只有一個(所有說是單線程),即JS引擎線程,但是瀏覽器同樣提供其他線程,如:事件觸發(fā)線程、定時器觸發(fā)線程等。

異步一般是指:

網(wǎng)絡(luò)請求

計時器

DOM事件監(jiān)聽

事件循環(huán)機(jī)制:

JS引擎線程會維護(hù)一個執(zhí)行棧,同步代碼會依次加入到執(zhí)行棧中依次執(zhí)行并出棧。

JS引擎線程遇到異步函數(shù),會將異步函數(shù)交給相應(yīng)的Webapi,而繼續(xù)執(zhí)行后面的任務(wù)。

Webapi會在條件滿足的時候,將異步對應(yīng)的回調(diào)加入到消息隊列中,等待執(zhí)行。

執(zhí)行棧為空時,JS引擎線程會去取消息隊列中的回調(diào)函數(shù)(如果有的話),并加入到執(zhí)行棧中執(zhí)行。

完成后出棧,執(zhí)行棧再次為空,重復(fù)上面的操作,這就是事件循環(huán)(event loop)機(jī)制。

原文鏈接

參考:

Tasks, microtasks, queues and schedules

great talk at JSConf on the event loop

并發(fā)模型與事件循環(huán) - JavaScript | MDN

從瀏覽器多進(jìn)程到JS單線程,JS運行... - 掘金

瀏覽器篇-Event-Loop.md - PDKSophia/blog.io

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/99143.html

相關(guān)文章

  • 深入淺出JavaScript運行機(jī)制

    摘要:主線程從任務(wù)隊列中讀取事件,這個過程是循環(huán)不斷的,所以整個的這種運行機(jī)制又稱為事件循環(huán)。上面也提到,在到達(dá)指定時間時,定時器就會將相應(yīng)回調(diào)函數(shù)插入任務(wù)隊列尾部。這就是定時器功能。關(guān)于定時器的重要補(bǔ)充定時器包括與兩個方法。 一、引子 本文介紹JavaScript運行機(jī)制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...

    mochixuan 評論0 收藏0
  • 深入淺出JavaScript運行機(jī)制

    摘要:主線程從任務(wù)隊列中讀取事件,這個過程是循環(huán)不斷的,所以整個的這種運行機(jī)制又稱為事件循環(huán)。上面也提到,在到達(dá)指定時間時,定時器就會將相應(yīng)回調(diào)函數(shù)插入任務(wù)隊列尾部。這就是定時器功能。關(guān)于定時器的重要補(bǔ)充定時器包括與兩個方法。 一、引子 本文介紹JavaScript運行機(jī)制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...

    魏明 評論0 收藏0
  • 深入淺出JavaScript運行機(jī)制

    摘要:主線程從任務(wù)隊列中讀取事件,這個過程是循環(huán)不斷的,所以整個的這種運行機(jī)制又稱為事件循環(huán)。上面也提到,在到達(dá)指定時間時,定時器就會將相應(yīng)回調(diào)函數(shù)插入任務(wù)隊列尾部。這就是定時器功能。關(guān)于定時器的重要補(bǔ)充定時器包括與兩個方法。 一、引子 本文介紹JavaScript運行機(jī)制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...

    chaosx110 評論0 收藏0
  • JavaScript 異步編程

    摘要:下面我將介紹的基本用法以及如何在異步編程中使用它們。在沒有發(fā)布之前,作為異步編程主力軍的回調(diào)函數(shù)一直被人詬病,其原因有太多比如回調(diào)地獄代碼執(zhí)行順序難以追蹤后期因代碼變得十分復(fù)雜導(dǎo)致無法維護(hù)和更新等,而的出現(xiàn)在很大程度上改變了之前的窘境。 前言 自己著手準(zhǔn)備寫這篇文章的初衷是覺得如果想要更深入的理解 JS,異步編程則是必須要跨過的一道坎。由于這里面涉及到的東西很多也很廣,在初學(xué) JS 的...

    lordharrd 評論0 收藏0

發(fā)表評論

0條評論

qianfeng

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<