同步與異步、事件循環(huán)與消息隊列惠遏、微任務(wù)與宏任務(wù)

JavaScript 是單線程砾跃、異步、非阻塞节吮、解釋型腳本語言抽高。

單線程與多線程

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

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

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

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

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

那如果一個任務(wù)的處理耗時(或者是等待)很久的話莹桅,如:網(wǎng)絡(luò)請求、定時器烛亦、等待鼠標點擊等诈泼,后面的任務(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 overscript end 后再打印呜师,也就是說計時器并沒有阻塞后面的代碼娶桦。那贾节,發(fā)生了什么汁汗?
其實,JavaScript 單線程指的是瀏覽器中負責解釋和執(zhí)行 JavaScript 代碼的只有一個線程栗涂,即為 JS引擎線程知牌,但是瀏覽器的渲染進程是提供多個線程的,如下:

  • JS引擎線程
  • 事件觸發(fā)線程
  • 定時觸發(fā)器線程
  • 異步http請求線程
  • GUI渲染線程
    瀏覽器渲染進程參考此處

當遇到計時器斤程、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)交給 消息隊列 去維護,JS引擎線程會在適當?shù)臅r候去消息隊列取出消息并執(zhí)行望薄。
這里疟游,JavaScript 通過 事件循環(huán) event loop 的機制來解決這個問題

同步與異步

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) 機制和 消息隊列 的維護是由事件觸發(fā)線程控制的。

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

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

同時,JS引擎線程會維護一個 執(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ù)亚享,這種機制就被稱為事件循環(huán)(event loop)機制。

宏任務(wù)與微任務(wù)

以上機制在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

這里有一個新概念: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)的機制呻惕。

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

microtask必然是在某個宏任務(wù)執(zhí)行的時候創(chuàng)建的做院,而在下一個宏任務(wù)開始之前,瀏覽器會對頁面重新渲染(task >> 渲染 >> 下一個task(從任務(wù)隊列中取一個))濒持。同時键耕,在上一個宏任務(wù)執(zhí)行完成后,渲染頁面之前柑营,會執(zhí)行當前微任務(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í)行機制:

  • 執(zhí)行一個宏任務(wù)(棧中沒有就從事件隊列中獲冉衢)
  • 執(zhí)行過程中如果遇到微任務(wù),就將它添加到微任務(wù)的任務(wù)隊列中
  • 宏任務(wù)執(zhí)行完畢后囤捻,立即執(zhí)行當前微任務(wù)隊列中的所有微任務(wù)(依次執(zhí)行)
  • 當前宏任務(wù)執(zhí)行完畢臼朗,開始檢查渲染,然后GUI線程接管渲染
  • 渲染完畢后蝎土,JS引擎線程繼續(xù)视哑,開始下一個宏任務(wù)(從宏任務(wù)隊列中獲取)

總結(jié)

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

異步一般是指:

  • 網(wǎng)絡(luò)請求
  • 計時器
  • DOM事件監(jiān)聽

事件循環(huán)機制

  • JS引擎線程會維護一個執(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)機制。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末苟径,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子棘街,更是在濱河造成了極大的恐慌承边,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件险污,死亡現(xiàn)場離奇詭異翔始,居然都是意外死亡罗心,警方通過查閱死者的電腦和手機城瞎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門脖镀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜒灰,你說我怎么就攤上這事⊥勾唬” “怎么了翅溺?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咙崎。 經(jīng)常有香客問我,道長网杆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任队秩,我火速辦了婚禮追城,結(jié)果婚禮上燥撞,老公的妹妹穿的比我還像新娘。我一直安慰自己物舒,他們只是感情好,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布火诸。 她就那樣靜靜地躺著荠察,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盯荤。 梳的紋絲不亂的頭發(fā)上焕盟,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音脚翘,去河邊找鬼。 笑死鞋真,一個胖子當著我的面吹牛沃于,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播揽涮,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼蒋困,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起溉跃,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤告抄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后打洼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡炫惩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年他嚷,在試婚紗的時候發(fā)現(xiàn)自己被綠了芭毙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡退敦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涂炎,到底是詐尸還是另有隱情设哗,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布震缭,位于F島的核電站,受9級特大地震影響拣宰,放射性物質(zhì)發(fā)生泄漏烦感。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一晌该、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧朝群,春花似錦、人聲如沸姜胖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隧出。三九已至阀捅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饲鄙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工帆谍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留轴咱,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓窖剑,卻偏偏與公主長得像戈稿,于是被迫代替她去往敵國和親西土。 傳聞我的和親對象是個殘疾皇子鞍盗,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容