JavaScript運(yùn)行機(jī)制(文章來源于微信公眾號(hào) 珠峰培訓(xùn)竞端,此為粘貼屎即,如有格式問題,文末有文章鏈接事富,可自行點(diǎn)擊跳轉(zhuǎn)閱讀)

作為前端工程師技俐,大家都知道js是前端一開始就要學(xué)會(huì)的知識(shí)點(diǎn),js的代碼你會(huì)寫了统台,那js的運(yùn)行機(jī)制你了解嗎雕擂?只有了解了js的運(yùn)行機(jī)制,才能在工作中如魚得水贱勃,今天就跟隨珠峰的老師一起來了解下js的運(yùn)行機(jī)制吧井赌。

JavaScript單線程模型

JavaScript是單線程的,JavaScript只在一個(gè)線程上運(yùn)行贵扰,但是瀏覽器是多線程的仇穗,典型的瀏覽器有如下線程:

JavaScript引擎線程

GUI渲染線程

瀏覽器事件觸發(fā)線程

瀏覽器Http請求線程

JavaScript為什么是單線程的

JavaScript之所以采用單線程 而不是多線程,由于作為瀏覽器腳本語言戚绕,主要用途是與用戶互動(dòng)纹坐,以及操作DOM(文檔對象模型)和BOM(瀏覽器對象模型), 而多線程需要共享資源列肢,多線程編程經(jīng)?常面臨鎖恰画、狀態(tài)同步等問題宾茂。

假定JavaScript同時(shí)有兩個(gè)線程,這兩個(gè)線程同時(shí)操作同一個(gè)DOM增刪修改操作拴还,這時(shí)瀏覽器應(yīng)該以哪個(gè)線程操作為準(zhǔn)跨晴?無疑會(huì)帶來同步問題。

既然JavaScript是單線程的片林,這就意味著端盆,一次只能運(yùn)行一個(gè)任務(wù),其他任務(wù)都必須在后面排隊(duì)等待费封。

為了利用多核CPU的計(jì)算能力焕妙,HTML5提出了Web Worker,它會(huì)在當(dāng) ? 前JavaScript的執(zhí)行主線程中利用Worker類新開辟一個(gè)額外的線程來加載和運(yùn)行特定的JavaScript文件弓摘,但在HTML5 Web Worker中是不能操作DOM的焚鹊,任何需要操作DOM的任務(wù)都需要委托給JavaScript主線程來執(zhí)行,所以雖然引入HTML5 Web Worker韧献,但仍然沒有改變JavaScript單線程的本質(zhì)末患。

任務(wù)列表

Javascript有一個(gè)main thread 主進(jìn)程和call-stack(一個(gè)調(diào)用堆棧),在對一個(gè)調(diào)用堆棧中的task處理的時(shí)候锤窑,其他的都要等著璧针。當(dāng)在執(zhí)行過程中遇到一些類似于setTimeout等異步操作的時(shí)候,會(huì)交給瀏覽器的其他模塊(以webkit為例渊啰,是webcore模塊)進(jìn)行處理探橱,當(dāng)?shù)竭_(dá)setTimeout指定的延時(shí)執(zhí)行的時(shí)間之后,task(回調(diào)函數(shù))會(huì)放入到任務(wù)隊(duì)列之中绘证。

一般不同的異步任務(wù)的回調(diào)函數(shù)會(huì)放入不同的任務(wù)隊(duì)列之中隧膏。等到調(diào)用棧中所有task執(zhí)行完畢之后,接著去執(zhí)行任務(wù)隊(duì)列之中的task(回調(diào)函數(shù))嚷那。

異步和同步

一般而言私植,操作分為:發(fā)出調(diào)用和得到結(jié)果兩步。

同步:

同步是指车酣,發(fā)出調(diào)用,但無法立即得到結(jié)果索绪,需要一直等待湖员,直到返回結(jié)果。同步任務(wù)會(huì)進(jìn)入主線程, 主線程后面任務(wù)必須要等當(dāng)前任務(wù)執(zhí)行完才能執(zhí)行瑞驱,從而導(dǎo)致主線程阻塞娘摔。

異步:

異步是指,調(diào)用之后唤反,不能直接拿到結(jié)果凳寺,通過event loop事件處理機(jī)制鸭津,在Event Queue注冊回調(diào)函數(shù)最終拿到結(jié)果(拿到結(jié)果中間的時(shí)間可以介入其他任務(wù))。

JavaScript是如何工作的肠缨,首先理解幾個(gè)概念

JS Engine(JS引擎)

Runtime(運(yùn)行上下文)

Call Stack(調(diào)用棧)

Event Loop(事件循環(huán))

Callback(回調(diào))

JS Engine

JavaScript引擎就是用來執(zhí)行JS代碼的, 通過編譯器將代碼編譯成可執(zhí)行的機(jī)器碼讓計(jì)算機(jī)去執(zhí)行(Java中的JVM虛擬機(jī)一樣)逆趋。

常見的JavaScript虛擬機(jī)(一般也把虛擬機(jī)稱為引擎):

Chakra(Microsoft Internet Explorer)

Nitro/JavaScript Core (Safari)

Carakan (Opera)

SpiderMonkey (Firefox)

V8 (Chrome, Chromium)

目前比較流行的就是V8引擎,Chrome瀏覽器和Node.js采用的引擎就是V8引擎晒奕。

引擎主要由堆(Memory Heap)和棧(Call Stack)組成

Heap(堆) - JS引擎中給對象分配的內(nèi)存空間是放在堆中的

Stack(棧)- 這里存儲(chǔ)著JavaScript正在執(zhí)行的任務(wù)闻书。每個(gè)任務(wù)被稱為幀(stack of frames)。

主線程運(yùn)行的時(shí)候脑慧,產(chǎn)生堆(heap)和棧(stack),棧中的代碼調(diào)用個(gè)各種外部api魄眉。

RunTime (運(yùn)行環(huán)境)

JS在瀏覽器環(huán)境中運(yùn)行時(shí),BOM和DOM對象提供了很多相關(guān)外部接口(這些接口不是V8引擎提供的)闷袒,供JS運(yùn)行時(shí)調(diào)用坑律,以及JS的事件循環(huán)(Event Loop)和事件隊(duì)列(Callback Queue),把這些稱為RunTime囊骤。在Node.js中晃择,可以把Node的各種庫提供的API稱為RunTime

Call Stack

當(dāng)JavaScript代碼執(zhí)行的時(shí)候,創(chuàng)建執(zhí)行環(huán)境是很重要的淘捡,它可能是下面三種情況中的一種:

全局 code(Global code)——代碼第一次執(zhí)行的默認(rèn)環(huán)境

函數(shù) code(Function code)——執(zhí)行流進(jìn)入函數(shù)體

Eval code(Eval code)——代碼在eval函數(shù)內(nèi)部執(zhí)行

JavaScript代碼首次被載入時(shí)藕各,會(huì)創(chuàng)建一個(gè)全局上下文,當(dāng)調(diào)用一個(gè)函數(shù)時(shí)焦除,會(huì)創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文激况。

在計(jì)算機(jī)系統(tǒng)中棧是一種遵從先進(jìn)后出(FILO)原則的區(qū)域。函數(shù)被調(diào)用時(shí)膘魄,創(chuàng)建一個(gè)新的執(zhí)行環(huán)境乌逐,就會(huì)被加入到執(zhí)行棧頂部,瀏覽器始終執(zhí)行當(dāng)前在棧頂部的執(zhí)行環(huán)境创葡。一旦函數(shù)完成了當(dāng)前的執(zhí)行環(huán)境浙踢,它就會(huì)被彈出棧的頂部, 把控制權(quán)返回給當(dāng)前執(zhí)行環(huán)境的下個(gè)執(zhí)行環(huán)境。

案例:瀏覽器第一次加載你的script灿渴,它默認(rèn)的進(jìn)了全局執(zhí)行環(huán)境洛波,然后main執(zhí)行創(chuàng)建一個(gè)新的執(zhí)行環(huán)境,把它添加到已經(jīng)存在的執(zhí)行棧的頂部骚露,在里面執(zhí)行Student構(gòu)造函數(shù)蹬挤,執(zhí)行流進(jìn)入內(nèi)部函數(shù) 將生成執(zhí)行環(huán)境添加到當(dāng)前棧頂,在Student構(gòu)造函數(shù)里棘幸,又調(diào)用sayHi方法焰扳,再次把sayHi生成執(zhí)行環(huán)境壓入到棧頂。當(dāng)函數(shù)執(zhí)行完一次彈出棧頂。

classStudent{

constructor(age,?name)?{

this.name?=?name;

this.age?=?age;

this.sayName();//?stack?3

}

sayName()?{

console.log(`my?name?is${this.name},?this?year?age?is${this.age}`);

}

}

functionmain(age,?name){

newStudent(age,?name);//?stack?2

}

main(23,'John');//?stack?1

程序運(yùn)行時(shí)吨悍,首先main()函數(shù)的執(zhí)行上下文入棧扫茅,再調(diào)用Student構(gòu)造函數(shù)添加到當(dāng)前棧尾,在Student里再調(diào)用sayName()方法育瓜,添加到此時(shí)棧尾葫隙。最終main方法所在的位置叫棧底,sayName方法所在的位置是棧頂爆雹,層層調(diào)用停蕉,直至整個(gè)調(diào)用棧完成返回結(jié)果,最后再由棧頂依次出棧钙态。

Event Loop & Callback

Event Loop 類似于一個(gè)while(true)的循環(huán)慧起,每執(zhí)行一次循環(huán)體的過程我們成為Tick。每個(gè)Tick的過程就是查看是否有事件待處理册倒,當(dāng)Call Stack里面的調(diào)用棧運(yùn)行完變成空了蚓挤,就取出事件及其相關(guān)的回調(diào)函數(shù)。放到調(diào)用棧中并執(zhí)行它驻子。

調(diào)用棧中遇到DOM操作灿意、ajax請求以及setTimeout等WebAPIs的時(shí)候就會(huì)交給瀏覽器內(nèi)核的其他模塊進(jìn)行處理,webkit內(nèi)核在Javasctipt執(zhí)行引擎之外崇呵,有一個(gè)重要的模塊是webcore模塊缤剧。對于圖中WebAPIs提到的三種API,webcore分別提供了DOM Binding域慷、network荒辕、timer模塊來處理底層實(shí)現(xiàn)。

等到這些模塊處理完這些操作的時(shí)候?qū)⒒卣{(diào)函數(shù)放入任務(wù)隊(duì)列中犹褒,之后等棧中的task執(zhí)行完之后再去執(zhí)行任務(wù)隊(duì)列之中的回調(diào)函數(shù)抵窒。

Javascript有一個(gè)main thread 主進(jìn)程和call-stack(一個(gè)調(diào)用堆棧),在對一個(gè)調(diào)用堆棧中的task處理的時(shí)候叠骑,其他的都要等著李皇。當(dāng)在執(zhí)行過程中遇到一些類似于setTimeout等異步操作的時(shí)候,會(huì)交給瀏覽器的其他模塊(以webkit為例宙枷,是webcore模塊)進(jìn)行處理掉房,當(dāng)?shù)竭_(dá)setTimeout指定的延時(shí)執(zhí)行的時(shí)間之后,task(回調(diào)函數(shù))會(huì)放入到任務(wù)隊(duì)列之中慰丛。

一般不同的異步任務(wù)的回調(diào)函數(shù)會(huì)放入不同的任務(wù)隊(duì)列之中圃阳。等到調(diào)用棧中所有task執(zhí)行完畢之后,接著去執(zhí)行任務(wù)隊(duì)列之中的task(回調(diào)函數(shù))璧帝。

代碼案例:

console.log('Hi');

setTimeout(functioncb1(){

console.log('cb1');

},5000);

console.log('Bye');

以上代碼從上到下 首先執(zhí)行l(wèi)og('Hi') 它是一個(gè)普通方法立即被執(zhí)行,當(dāng)遇到定時(shí)器的時(shí)候富寿,執(zhí)行引擎將其添加到調(diào)用棧睬隶,調(diào)用棧發(fā)現(xiàn)setTimeout是WebAPIs中的API锣夹,將其出棧交給瀏覽器的timer模塊進(jìn)行處理,此時(shí)timer模塊去處理延遲執(zhí)行的函數(shù)苏潜,此時(shí)執(zhí)行l(wèi)og('Bye'),輸出'Bye'银萍,當(dāng)timer模塊中延時(shí)方法規(guī)定的時(shí)間到了之后就將其放入到任務(wù)隊(duì)列之中,此時(shí)調(diào)用棧中的task已經(jīng)全部執(zhí)行完畢恤左。

調(diào)用棧中的task執(zhí)行完畢之后贴唇,執(zhí)行引擎會(huì)接著看執(zhí)行任務(wù)隊(duì)列中是否有需要執(zhí)行的回調(diào)函數(shù)。

Event Loop處理機(jī)制

什么是Event Loop飞袋?

Event Loop(事件循環(huán))是實(shí)現(xiàn)異步的一種機(jī)制戳气,允許 Node.js 執(zhí)行非阻塞 I/O 操作 。

大多數(shù)現(xiàn)代的系統(tǒng)內(nèi)核都是多線程的, 他們在后臺(tái)可以處理多個(gè)同時(shí)執(zhí)行的操作. 當(dāng)其中一個(gè)操作完成時(shí), 系統(tǒng)內(nèi)核會(huì)通知Node.js, 然后與之相關(guān)的回調(diào)函數(shù)會(huì)被加入到 poll隊(duì)列 并且最終被執(zhí)行巧鸭。

注意: 在Windows和Unix/Linux實(shí)現(xiàn)之間存在一點(diǎn)小小的差異, 但對本示例來說這并不重要瓶您,最重要的部分都已列在這里了,實(shí)際上有7或8個(gè)階段, 但我們關(guān)心的和Node.js實(shí)際會(huì)用到的階段都已經(jīng)列在了上面纲仍。

每個(gè)階段都有一個(gè)先進(jìn)先出(FIFO)的隊(duì)列呀袱,里面存放著要執(zhí)行的回調(diào)函數(shù),然而每個(gè)階段都有其特殊之處郑叠,當(dāng)事件循環(huán)進(jìn)入了某個(gè)階段后夜赵,它可以執(zhí)行該階段特有的任意操作,然后進(jìn)行該階段的任務(wù)隊(duì)列中的回調(diào)函數(shù)乡革,一直到隊(duì)列為空或已執(zhí)行回調(diào)的數(shù)量達(dá)到了允許的最大值寇僧,當(dāng)隊(duì)列為空或已執(zhí)行回調(diào)的數(shù)量達(dá)到了允許的最大值時(shí),事件循環(huán)會(huì)進(jìn)入下一個(gè)階段,階段之間會(huì)互相轉(zhuǎn)換署拟,循環(huán)順序并不是完全固定的 婉宰,因?yàn)楹芏嚯A段是由外部的事件觸發(fā)的。

階段概覽

timers(定時(shí)器):此階段執(zhí)行由setTimeout()和setInterval() 調(diào)度的回調(diào)函數(shù)

I/O callbacks(I/O回調(diào)): 此階段會(huì)執(zhí)行幾乎所有的回調(diào)函數(shù), 除了 close callbacks(關(guān)閉回調(diào)) 和 那些由 timers 與 setImmediate() 調(diào)度的回調(diào).

idle(空閑)推穷,prepare(預(yù)備): 此階段只在內(nèi)部調(diào)用

poll(輪詢): 檢索新的I/O事件心包,在恰當(dāng)?shù)臅r(shí)候會(huì)阻塞在這個(gè)階段

check(檢查): setImmediate() 設(shè)置的回調(diào)會(huì)在此階段被調(diào)用

close callbacks(關(guān)閉事件的回調(diào)): 諸如 socket.on('close', ...) 此類的回調(diào)在此階段被調(diào)用

在事件循環(huán)的每次運(yùn)行之間,Node.js會(huì)檢查它是否在等待異步I/O或定時(shí)器, 如果沒有的話就會(huì)自動(dòng)關(guān)閉馒铃。

一次事件循環(huán)就是處理以上幾個(gè)phase的過程蟹腾,此外還有兩個(gè)比較特殊的隊(duì)列Next Ticks Queue和Other Microtasks Queue,那另外兩個(gè)特殊的隊(duì)列是在什么時(shí)候運(yùn)行的呢区宇?

答案: 就是在每個(gè) phase運(yùn)行完后馬上就檢查這兩個(gè)隊(duì)列有無數(shù)據(jù)娃殖,有的話就馬上執(zhí)行這兩個(gè)隊(duì)列中的數(shù)據(jù)直至隊(duì)列為空。當(dāng)這兩個(gè)隊(duì)列都為空時(shí)议谷,event loop 就會(huì)接著執(zhí)行下一個(gè)phase炉爆。

這兩個(gè)隊(duì)列相比,Next Ticks Queue的權(quán)限要比Other Microtasks Queue的權(quán)限要高,因此Next Ticks Queue會(huì)先執(zhí)行芬首。

兩個(gè)比較特殊的隊(duì)列:

Next Ticks Queue: 保存process.nextTick中的回調(diào)函數(shù)

Other Microtasks Queue: 保存promise等microtask中的回調(diào)函數(shù)赴捞。

階段詳情

由于這些操作中的任意一個(gè)都可以調(diào)度更多的操作, 在 poll(輪詢) 階段處理的新事件被系統(tǒng)內(nèi)核加入隊(duì)列, 當(dāng)輪詢事件正在被處理時(shí)新的輪詢事件也可以被加入隊(duì)列. 因此, 長時(shí)間運(yùn)行的回調(diào)函數(shù)可以讓 poll 階段運(yùn)行的時(shí)間比 timer(計(jì)時(shí)器) 的閾值長得多。 看下面timer 和 poll 部分了解更多細(xì)節(jié)郁稍。

timers

給一個(gè)定時(shí)器(setTimeout/setInterval)指定時(shí)間閾值時(shí)赦政,給定的回調(diào)函數(shù)有時(shí)并不是在精確的時(shí)間閾值點(diǎn)執(zhí)行,定時(shí)器的閾值只是說 至少在這個(gè)時(shí)間閾值點(diǎn)執(zhí)行耀怜,然而操作系統(tǒng)調(diào)度或其他回調(diào)的執(zhí)行可能會(huì)延遲定時(shí)器回調(diào)的執(zhí)行恢着。

注意:從技術(shù)來講, poll階段會(huì)控制定時(shí)器何時(shí)被執(zhí)行

constfs?=require('fs');

//?設(shè)定一個(gè)100ms執(zhí)行的定時(shí)器

conststartTime?=Date.now();

setTimeout(()=>{

console.log('timeout延遲執(zhí)行時(shí)間',Date.now()?-?startTime);

console.log('timer');

},100);

//?異步讀取文件?假設(shè)95ms完成讀取任務(wù)

fs.readFile('./1.txt',?(err,?data)?=>?{//?回調(diào)函數(shù)中又耗費(fèi)100毫秒

conststartTime?=Date.now();

while(Date.now()?-?startTime?<200)?{

//?console.log(Date.now()?-?startTime);

}

});

開始事件循環(huán)定時(shí)器被加入到timer中延遲執(zhí)行财破,當(dāng)事件循環(huán)進(jìn)入poll階段掰派,它有一個(gè)隊(duì)列執(zhí)行I/O操作(fs.readFile())還未完成,poll階段將會(huì)阻塞狈究,大約95ms 完成了I/O操作(文件讀韧胩省),將要耗時(shí)10ms才能完成的回調(diào)加入poll隊(duì)列并執(zhí)行抖锥,當(dāng)回調(diào)執(zhí)行完成亿眠,poll Queue為空,此時(shí)poll會(huì)去timer階段查看最近有沒有到期的定時(shí)器磅废,發(fā)現(xiàn)存在一個(gè)已經(jīng)超時(shí)將近195ms的定時(shí)器纳像,并執(zhí)行定時(shí)器回調(diào)。在這個(gè)例子中如果不假設(shè)讀取時(shí)間拯勉,定時(shí)器執(zhí)行的時(shí)間間隔大約為200ms竟趾。

注意: 為了防止 poll 階段阻塞事件循環(huán), libuv(一個(gè)實(shí)現(xiàn)了Node.js事件循環(huán)和Node.js平臺(tái)所有異步行為的C語言庫), 有一個(gè)嚴(yán)格的最大限制(這個(gè)值取決于操作系統(tǒng)), 在超過此限制后就會(huì)停止輪詢.

I/O callbacks

此階段執(zhí)行一些系統(tǒng)操作處理 I/O 異常錯(cuò)誤;,如TCP的errors回調(diào)函數(shù)宫峦。

poll

poll 階段主要有兩個(gè)功能:

1.執(zhí)行時(shí)間閾值已過去的定時(shí)器回調(diào)

2.處理poll隊(duì)列中的事件

當(dāng)事件循環(huán)進(jìn)入poll階段并且?當(dāng)前沒有定時(shí)器時(shí)岔帽,以下兩種情況其中一種會(huì)發(fā)生:

如果poll隊(duì)列不是空的,事件循環(huán)會(huì)遍歷隊(duì)列并同步執(zhí)行里面的回調(diào)函數(shù)导绷,直到隊(duì)列為空或者到達(dá)操作系統(tǒng)的限制(操作系統(tǒng)規(guī)定的連續(xù)調(diào)用回調(diào)函數(shù)的數(shù)量的最大值)

如果poll隊(duì)列是空的犀勒,則以下兩種情況其中一種將發(fā)生:

如果存在被?setImmediate()?調(diào)度的回調(diào),事件循環(huán)會(huì)結(jié)束poll階段并進(jìn)入check階段執(zhí)行那些被?setImmediate()?調(diào)度了的回調(diào)妥曲。

如果沒有任何被?setImmediate()?調(diào)度的回調(diào)贾费,事件循環(huán)會(huì)等待回調(diào)函數(shù)被加入隊(duì)列,一旦回調(diào)函數(shù)加入了隊(duì)列檐盟,就立即執(zhí)行它們褂萧。

一旦poll隊(duì)列變?yōu)榭眨录h(huán)就檢查是否已經(jīng)存在超時(shí)的定時(shí)器葵萎,如果存在导犹,事件循環(huán)將繞回到timers階段執(zhí)行這些定時(shí)器回調(diào)唱凯。

check

此階段如果poll階段變?yōu)榭辙D(zhuǎn)(idle)狀態(tài),如果存在被?setImmediate()?調(diào)度的回調(diào)锡足,事件循環(huán)不會(huì)在poll階段阻塞等待相應(yīng)的I/O事件波丰,而直接去check階段執(zhí)行?setImmediate()?函數(shù)。

close callbacks

如果一個(gè)socket或句柄被突然關(guān)閉(例如 socket.destroy()), 'close'事件會(huì)在此階段被觸發(fā). 否則 'close'事件會(huì)通過 process.nextTick() 被觸發(fā).

setImmediate() vs setTimeout()

setImmediate() 被設(shè)計(jì)為: 一旦當(dāng)前的poll階段完成就執(zhí)行回調(diào)

setTimeout() 調(diào)度一個(gè)回調(diào)在時(shí)間閥值之后被執(zhí)行

這兩種定時(shí)器的執(zhí)行順序可能會(huì)變化, 這取決于他們是在哪個(gè)上下文中被調(diào)用的. 如果兩種定時(shí)器都是從主模塊內(nèi)被調(diào)用的, 那么回調(diào)執(zhí)行的時(shí)機(jī)就受進(jìn)程性能的約束(進(jìn)程也會(huì)受到系統(tǒng)中正在運(yùn)行的其他應(yīng)用程序的影響).

setTimeout(functiontimeout(){

console.log('timeout');

},0);

setImmediate(functionimmediate(){

console.log('immediate');

});

但如果把setImmediate和setTimeout放到了I/O周期中舶得,此時(shí)他們的執(zhí)行順序永遠(yuǎn)都是immediate在前,timeout在后爽蝴。

const?fs?=require('fs');

fs.readFile(__filename,()=>{

setTimeout(()=>{

console.log('timeout');

},0);

setImmediate(()=>{

console.log('immediate');

});

});

相比于 setTimeout(), 使用 setImmediate() 的主要優(yōu)點(diǎn)在于: 只要時(shí)在I/O周期內(nèi), 不管已經(jīng)存在多少個(gè)定時(shí)器, setImmediate()設(shè)置的回調(diào)總是在定時(shí)器回調(diào)之前執(zhí)行沐批。

process.nextTick()

在上面我們提到了Next Ticks Queue特殊的隊(duì)列,在這個(gè)隊(duì)列里主要存放process.nextTick這個(gè)異步函數(shù)蝎亚。從技術(shù)上講該階段并不屬于事件循環(huán)的一部分九孩,不管當(dāng)前事件循環(huán)處于哪個(gè)階段,只要當(dāng)前階段操作完畢后進(jìn)入下個(gè)階段前瞬間執(zhí)行process.nextTick()发框。

這樣一來任何時(shí)候在給定階段調(diào)用process.nextTick()時(shí)躺彬,所有傳入process.nextTick()的回調(diào)都會(huì)在事件循環(huán)繼續(xù)之前被執(zhí)行。由于允許開發(fā)者通過遞歸調(diào)用 process.nextTick() 來阻塞I/O操作, 這也使事件循環(huán)無法到達(dá) poll 階段.

利用process.nextTick函數(shù)梅惯,我們可以對內(nèi)部函數(shù)作異步處理可能出現(xiàn)的異常宪拥,porcess.nextTick(callback, ...args)?允許接收多個(gè)參數(shù),callback后面的參數(shù)會(huì)作為callback的實(shí)參傳遞進(jìn)來铣减,這樣就無需嵌套函數(shù)了她君。

function?apiCall(arg,?callback)?{

if(typeofarg?!=='string')

returnprocess.nextTick(callback,

newTypeError('argument?should?be?string'));

callback.call(this,?arg);

};

apiCall(1,(err)=>{

console.log(err);

});

apiCall('node',(err)=>{

console.log(err);

});

setTimeout() setImmediate() process.nextTick()

setTimeout() 在某個(gè)時(shí)間值過后盡快執(zhí)行回調(diào)函數(shù);

process.nextTick() 在當(dāng)前調(diào)用棧結(jié)束后就立即處理葫哗,這時(shí)也必然是“事件循環(huán)繼續(xù)進(jìn)行之前”

setImmediate() 函數(shù)是在poll階段完成后進(jìn)去check階段時(shí)執(zhí)行

優(yōu)先級(jí)順序從高到低: process.nextTick() > setImmediate() > setTimeout()

注:這里只是多數(shù)情況下缔刹,即輪詢階段(I/O 回調(diào)中)。比如之前比較 setImmediate() 和 setTimeout() 的時(shí)候就區(qū)分了所處階段/上下文劣针。

Macrotask Queue和Microtask Queue

macrotask 和 microtask 這兩個(gè)概念, 表示異步任務(wù)的兩種分類校镐。在掛起任務(wù)時(shí),JS 引擎會(huì)將所有任務(wù)按照類別分到這兩個(gè)隊(duì)列中捺典,首先在 macrotask 的隊(duì)列(這個(gè)隊(duì)列也被叫做 task queue)中取出第一個(gè)任務(wù)鸟廓,執(zhí)行完畢后取出 microtask 隊(duì)列中的所有任務(wù)順序執(zhí)行;之后再取 macrotask 任務(wù)辣苏,周而復(fù)始肝箱,直至兩個(gè)隊(duì)列的任務(wù)都取完。

macrotask(宏任務(wù)稀蟋、大任務(wù)):

script(整體代碼)

setTimeout

setInterval

setImmediate

I/O

UI rendering

microtask(微任務(wù)煌张、小任務(wù)):

promise

Object.observe

process.nextTick

MutationObserver

每個(gè)事件循環(huán)只處理一個(gè)macrotask(大任務(wù)) ,但會(huì)處理完所有microtask(小任務(wù))退客。

參考資料

JS運(yùn)行機(jī)制

Node.JS事件循環(huán)

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

事件循環(huán)

文章鏈接點(diǎn)擊處

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骏融,一起剝皮案震驚了整個(gè)濱河市链嘀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌档玻,老刑警劉巖怀泊,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異误趴,居然都是意外死亡霹琼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門凉当,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枣申,“玉大人,你說我怎么就攤上這事看杭≈姨伲” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵楼雹,是天一觀的道長模孩。 經(jīng)常有香客問我,道長贮缅,這世上最難降的妖魔是什么榨咐? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮携悯,結(jié)果婚禮上祭芦,老公的妹妹穿的比我還像新娘播掷。我一直安慰自己蒂窒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布予借。 她就那樣靜靜地躺著轴或,像睡著了一般昌跌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上照雁,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天蚕愤,我揣著相機(jī)與錄音,去河邊找鬼饺蚊。 笑死萍诱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的污呼。 我是一名探鬼主播裕坊,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼燕酷!你這毒婦竟也來了籍凝?” 一聲冷哼從身側(cè)響起周瞎,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饵蒂,沒想到半個(gè)月后声诸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡退盯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年彼乌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渊迁。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡囤攀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宫纬,到底是詐尸還是另有隱情,我是刑警寧澤膏萧,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布漓骚,位于F島的核電站,受9級(jí)特大地震影響榛泛,放射性物質(zhì)發(fā)生泄漏蝌蹂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一曹锨、第九天 我趴在偏房一處隱蔽的房頂上張望孤个。 院中可真熱鬧,春花似錦沛简、人聲如沸齐鲤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽给郊。三九已至,卻和暖如春捧灰,著一層夾襖步出監(jiān)牢的瞬間淆九,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國打工毛俏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炭庙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓煌寇,卻偏偏與公主長得像焕蹄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子唧席,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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