Javascript 運(yùn)行機(jī)制詳解咖祭,Event Loop

javascript的運(yùn)行機(jī)制锻弓,Event Loop

單線程

javascript為什么是單線程語(yǔ)言砾赔,原因在于如果是多線程,當(dāng)一個(gè)線程對(duì)DOM節(jié)點(diǎn)做添加內(nèi)容操作的時(shí)候青灼,另一個(gè)線程要?jiǎng)h除這個(gè)DOM節(jié)點(diǎn)暴心,這個(gè)時(shí)候,瀏覽器應(yīng)該怎么選擇杂拨,這就造成了混亂专普,為了解決這類(lèi)問(wèn)題,在一開(kāi)始的時(shí)候弹沽,javascript就采用單線程模式檀夹。

在后面H5出的web worker標(biāo)準(zhǔn)的時(shí)候筋粗,看似是多線程,其實(shí)是在一個(gè)主線程來(lái)控制其他線程击胜,而且不能操作DOM亏狰,所以本質(zhì)還是單線程

任務(wù)隊(duì)列

任務(wù)可以分為兩種役纹,一種為同步偶摔,另一種為異步(具有回調(diào)函數(shù))。如下圖:

eventloop.png

所有的同步任務(wù)都在主線程上執(zhí)行促脉,形成一個(gè)執(zhí)行棧 stack辰斋。當(dāng)所有同步任務(wù)執(zhí)行完畢后,它會(huì)去執(zhí)行microtask queue中的異步任務(wù)(nextTick瘸味,Promise)宫仗,將他們?nèi)繄?zhí)行。主線程之外還有一個(gè)任務(wù)隊(duì)列task queue旁仿,當(dāng)有異步任務(wù)(DOM藕夫,AJAX,setTimeout枯冈,setImmediate)有結(jié)果的時(shí)候毅贮,就在任務(wù)隊(duì)列里放一個(gè)事件,一旦執(zhí)行棧和microtask queue任務(wù)執(zhí)行完畢尘奏,系統(tǒng)就會(huì)讀取任務(wù)隊(duì)列滩褥,將取出排在最前面的事件加入執(zhí)行棧執(zhí)行,這種機(jī)制就是任務(wù)隊(duì)列炫加。

Event Loop

主線程在任務(wù)隊(duì)列中讀取事件瑰煎,這個(gè)過(guò)程是循環(huán)不斷地,所以這種運(yùn)行機(jī)制叫做Event Loop(事件循環(huán))

nextTick俗孝、setImmediate酒甸、setTimeout

nextTick是在執(zhí)行棧同步代碼結(jié)束之后,下一次Event Loop(任務(wù)隊(duì)列)執(zhí)行之前赋铝。當(dāng)所有同步任務(wù)執(zhí)行完烘挫,會(huì)在queue中執(zhí)行nextTick,無(wú)論nextTick有多少層回調(diào)柬甥,都會(huì)執(zhí)行完畢后再去任務(wù)隊(duì)列饮六,所以會(huì)造成一直停留在當(dāng)前執(zhí)行棧,無(wú)法執(zhí)行任務(wù)隊(duì)列苛蒲,請(qǐng)看下面代碼

process.nextTick(function () {
    console.log('nextTick1');
    process.nextTick(function (){console.log('nextTick2')});
});
  
setTimeout(function timeout() {
    console.log('setTimeout');
}, 0)

執(zhí)行完畢后輸出nextTick1卤橄、nextTick2、setTimeout臂外,原因是nextTick是在當(dāng)前執(zhí)行棧末尾執(zhí)行窟扑,而setTimeout是在下次任務(wù)隊(duì)列在執(zhí)行

setImmediate方法是在Event Loop(任務(wù)隊(duì)列)末尾喇颁,也就是下一次Event Loop時(shí)執(zhí)行。

setTimeout方法是按照?qǐng)?zhí)行時(shí)間嚎货,放入任務(wù)隊(duì)列橘霎,有時(shí)快與setImmediate有時(shí)慢。請(qǐng)看以下代碼

setImmediate(function () {
    console.log('setImmediate1');
    setImmediate(function (){console.log('setImmediate2')});
});
  
setTimeout(function timeout() {
    console.log('setTimeout');
}, 0);

這段代碼執(zhí)行完可能是setImmediate1殖属、setTimeout姐叁、setImmediate2,也可能是setTimeout洗显、setImmediate1外潜、setImmediate2,原因是setTimeout和setImmediate1都是在下次Event Loop中觸發(fā)挠唆,所以先后不確定处窥,但是setImmediate2肯定是最后,因?yàn)樗窃趕etImmediate1任務(wù)隊(duì)列之后玄组,也就是下下次Event Loop執(zhí)行

Node.js的Event Loop

Node.js也是單線程的Event Loop但是和瀏覽器有些區(qū)別滔驾,如圖所示,

1.先通過(guò)Chrom V8引擎解析Javascript腳本

2.解析完畢后調(diào)用Node API

3.LIBUV庫(kù)負(fù)責(zé)Node API的執(zhí)行俄讹,將不同任務(wù)分配給不同的線程哆致,形成一個(gè)Event Loop(任務(wù)隊(duì)列)

4.最后Chrom V8引擎將結(jié)果返回給用戶

nodesystem.png

Node.js Event Loop原理

node.js的特點(diǎn)是事件驅(qū)動(dòng),非阻塞單線程颅悉。當(dāng)應(yīng)用程序需要I/O操作的時(shí)候沽瞭,線程并不會(huì)阻塞,而是把I/O操作交給底層庫(kù)(LIBUV)剩瓶。此時(shí)node線程會(huì)去處理其他任務(wù)驹溃,當(dāng)?shù)讓訋?kù)處理完I/O操作后,會(huì)將主動(dòng)權(quán)交還給Node線程延曙,所以Event Loop的用處是調(diào)度線程豌鹤,例如:當(dāng)?shù)讓訋?kù)處理I/O操作后調(diào)度Node線程處理后續(xù)工作,所以雖然node是單線程枝缔,但是底層庫(kù)處理操作依然是多線程

Node Event Loop的事件處理機(jī)制

   ┌───────────────────────┐

┌─>│        timers         │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

│  │     I/O callbacks     │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

│  │     idle, prepare     │

│  └──────────┬────────────┘      ┌───────────────┐

│  ┌──────────┴────────────┐      │   incoming:   │

│  │         poll          │<─────┤  connections, │

│  └──────────┬────────────┘      │   data, etc.  │

│  ┌──────────┴────────────┐      └───────────────┘

│  │        check          │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

└──┤    close callbacks    │

   └───────────────────────┘

上面處理階段都是按照先進(jìn)先出的規(guī)則執(zhí)行回調(diào)函數(shù)布疙,按順序執(zhí)行,直到隊(duì)列為空或是該階段執(zhí)行的回調(diào)函數(shù)達(dá)到該階段所允許一次執(zhí)行回調(diào)函數(shù)的最大限制后愿卸,才會(huì)將操作權(quán)移交給下一階段灵临。

  • timers: 用來(lái)檢查setTimeout()和setInterval()定時(shí)器是否到期,如果到期則執(zhí)行它趴荸,否則下一階段
  • I/O callbacks: 用來(lái)處理timers階段儒溉、setImmediate、和TCP他們的異撤⒍郏回調(diào)函數(shù)或者error
  • idle, prepare: nodejs內(nèi)部函數(shù)調(diào)用顿涣,在循環(huán)被I/O阻塞之前prepare回調(diào)就會(huì)立即調(diào)用
  • poll: 用來(lái)監(jiān)聽(tīng)fd的事件的波闹,比如socket的可讀,可寫(xiě)涛碑,文件的可讀可等等
  • check: setImmediate()函數(shù)只會(huì)在這個(gè)階段執(zhí)行
  • close callbacks: 執(zhí)行一些諸如關(guān)閉事件的回調(diào)函數(shù)精堕,如socket.on('close', ...)

具體分析,看下圖:

1.當(dāng)setTimeout時(shí)間最小蒲障,讀取文件不存在的時(shí)候

eventloop-setTimeout0-unfile.png

如圖所示歹篓,分別是nextTick、readFile晌涕、setTimeout滋捶、setImmediate痛悯,然而現(xiàn)在并沒(méi)有1.txt和2.txt文件余黎,輸出結(jié)果是next Tick、setTimeout载萌、readFile惧财、setImmediate,在event loop中先判斷的是timeers,最先出書(shū)next Tick因?yàn)閜rocess.nextTick的實(shí)現(xiàn)是基于v8 MicroTask(是在當(dāng)前js call stack 中沒(méi)有可執(zhí)行代碼才會(huì)執(zhí)行的隊(duì)列,低于js call stack 代碼,但高于事件循環(huán)聋亡,不屬于Event Loop犀斋,上面javascript的Event Loop介紹過(guò)了,所以最先輸出萎攒。然后開(kāi)始走Event Loop,第一階段是timers,判斷setTimeout到期仰迁,所以輸出setTimeout,進(jìn)入下一階段顽分,poll將I/O操作權(quán)交出徐许,新線程操作,但是并沒(méi)有相關(guān)讀取文件卒蘸,所以直接返回回調(diào)函數(shù)雌隅,所以處處readFile,最后到check階段缸沃,輸出setImmediate

2.當(dāng)setTimeout時(shí)間最小恰起,讀取文件存在的時(shí)候

eventloop-setTimeout0-file.png

如圖所示,分別是nextTick趾牧、setTimeout检盼、setImmediate、readFile武氓,這次readFile在最后面梯皿,是因?yàn)槲募嬖诔鹣洌瑘?zhí)行到poll階段的時(shí)候,執(zhí)行I/O操作东羹,node線程開(kāi)始執(zhí)行check階段剂桥,當(dāng)交出的I/O操作結(jié)束后,返回給Event Loop所以再執(zhí)行readFile的回調(diào)函數(shù)属提,所以他在最后面

3.當(dāng)setTimeout時(shí)間為100毫秒权逗,讀取文件不存在的時(shí)候

eventloop-setTimeout100-unfile.png

如圖所示,分別是nextTick冤议、readFile斟薇、setImmediate、setTimeout恕酸,它和1不同的地方是setTimeout排在最后了堪滨,這是因?yàn)樵趫?zhí)行timers的時(shí)候,setTimeout沒(méi)有到期蕊温,所以直接執(zhí)行下一階段袱箱,當(dāng)執(zhí)行完poll的時(shí)候,會(huì)去執(zhí)行查看定時(shí)器有沒(méi)有到期义矛,如果沒(méi)有下一次Event Loop再次查看发笔,知道定時(shí)器到期,所以他在最后面

4.當(dāng)setTimeout時(shí)間為100毫秒凉翻,讀取文件存在的時(shí)候

eventloop-setTimeout100-file.png

如圖所示了讨,分別是nextTick、setImmediate制轰、readFile前计、setTimeout,它和2的區(qū)別是setTimeout在最后艇挨,原因和3一樣残炮。

總結(jié)一下

1.javascript和node.js都是單線程,但是node底層是多線程操作

2.Event Loop —— 任務(wù)隊(duì)列

3.當(dāng)同時(shí)設(shè)置nextTick, setImmediate, setTimeout時(shí)一定是nextTick先執(zhí)行缩滨,nextTick不屬于Event LOop势就,它屬于v8的micro tasks,并且會(huì)阻塞Event Loop

4.setImmediate脉漏,setTimeout屬于Event Loop但是苞冯,直接階段不同

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市侧巨,隨后出現(xiàn)的幾起案子舅锄,更是在濱河造成了極大的恐慌,老刑警劉巖司忱,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件皇忿,死亡現(xiàn)場(chǎng)離奇詭異畴蹭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)鳍烁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)叨襟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人幔荒,你說(shuō)我怎么就攤上這事糊闽。” “怎么了爹梁?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵右犹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我姚垃,道長(zhǎng)念链,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任莉炉,我火速辦了婚禮钓账,結(jié)果婚禮上碴犬,老公的妹妹穿的比我還像新娘絮宁。我一直安慰自己,他們只是感情好服协,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布绍昂。 她就那樣靜靜地躺著,像睡著了一般偿荷。 火紅的嫁衣襯著肌膚如雪窘游。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天跳纳,我揣著相機(jī)與錄音忍饰,去河邊找鬼。 笑死寺庄,一個(gè)胖子當(dāng)著我的面吹牛艾蓝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播斗塘,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼赢织,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了馍盟?” 一聲冷哼從身側(cè)響起于置,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贞岭,沒(méi)想到半個(gè)月后八毯,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體搓侄,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年话速,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了休讳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡尿孔,死狀恐怖俊柔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情活合,我是刑警寧澤雏婶,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站白指,受9級(jí)特大地震影響留晚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜告嘲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一错维、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧橄唬,春花似錦赋焕、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至僧界,卻和暖如春侨嘀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捂襟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工咬腕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人葬荷。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓涨共,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親闯狱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子煞赢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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