js運(yùn)行機(jī)制詳解(Event Loop)

1.基礎(chǔ)知識(shí)

\bullet ?js作為瀏覽器腳本語(yǔ)言,它的主要用途是與用戶互動(dòng),以及操作DOM,因此js是單線程,也避免了同時(shí)操作同一個(gè)DOM的矛盾問(wèn)題;

\bullet 為了利用多核CPU的計(jì)算能力,H5的Web Worker實(shí)現(xiàn)的“多線程”實(shí)際上指的是“多子線程”,完全受控于主線程,且不允許操作DOM;

js引擎存在monitoring process進(jìn)程,會(huì)持續(xù)不斷的檢查主線程執(zhí)行棧是否為空,一旦為空,就會(huì)去Event Queue那里檢查是否有等待被調(diào)用的函數(shù)。這個(gè)過(guò)程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為Event Loop(事件循環(huán))

所有同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧(execution context stack);

如果在微任務(wù)執(zhí)行期間微任務(wù)隊(duì)列加入了新的微任務(wù),會(huì)將新的微任務(wù)加入隊(duì)列尾部,之后也會(huì)被執(zhí)行;

2.js中的異步操作

setTimeOut

setInterval

ajax

promise

I/O

3.同步任務(wù) or 異步任務(wù)

同步任務(wù)(synchronous):在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù);

異步任務(wù)(asynchronous):不進(jìn)入主線程、而進(jìn)入"任務(wù)隊(duì)列"(task queue)的任務(wù),只有"任務(wù)隊(duì)列"通知主線程,某個(gè)異步任務(wù)可以執(zhí)行了,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行。

4.宏任務(wù) or 微任務(wù)

這里需要注意的是new Promise是會(huì)進(jìn)入到主線程中立刻執(zhí)行,而promise.then則屬于微任務(wù)

宏任務(wù)(macro-task):整體代碼script、setTimeOut、setInterval

微任務(wù)(mincro-task):promise.then、promise.nextTick(node)

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

Event Loop循環(huán)

整體的script(作為第一個(gè)宏任務(wù))開(kāi)始執(zhí)行的時(shí)候,會(huì)把所有代碼分為兩部分:“同步任務(wù)”、“異步任務(wù)”;

同步任務(wù)會(huì)直接進(jìn)入主線程依次執(zhí)行;

異步任務(wù)會(huì)再分為宏任務(wù)和微任務(wù);

宏任務(wù)進(jìn)入到Event Table中,并在里面注冊(cè)回調(diào)函數(shù),每當(dāng)指定的事件完成時(shí),Event Table會(huì)將這個(gè)函數(shù)移到Event Queue中;

微任務(wù)也會(huì)進(jìn)入到另一個(gè)Event Table中,并在里面注冊(cè)回調(diào)函數(shù),每當(dāng)指定的事件完成時(shí),Event Table會(huì)將這個(gè)函數(shù)移到Event Queue中;

當(dāng)主線程內(nèi)的任務(wù)執(zhí)行完畢,主線程為空時(shí),會(huì)檢查微任務(wù)的Event Queue,如果有任務(wù),就全部執(zhí)行,如果沒(méi)有就執(zhí)行下一個(gè)宏任務(wù);

上述過(guò)程會(huì)不斷重復(fù),這就是Event Loop事件循環(huán);

6.代碼示例

1.第一個(gè)例子

console.log(1)setTimeout(function(){console.log(2)},0)console.log(3)執(zhí)行結(jié)果:// 1 3 2

分析:

1.console.log(1)是同步任務(wù),直接打印1;

2.setTimeout是異步任務(wù),且是宏函數(shù),放到宏函數(shù)隊(duì)列中,等待下次Event Loop才會(huì)執(zhí)行;

3.console.log(3)是同步任務(wù),直接打印3;

4.主線程執(zhí)行完畢,沒(méi)有微任務(wù),那么執(zhí)行第二個(gè)宏任務(wù)setTimeout,打印2;

5.結(jié)果:1,3,2

2.第二個(gè)例子

setTimeout(function(){console.log(1)});newPromise(function(resolve){console.log(2);for(vari =0; i <10000; i++){? ? ? ? i ==9999&& resolve();? ? }}).then(function(){console.log(3)});console.log(4);執(zhí)行結(jié)果:// 2, 4, 3, 1

分析:

1.setTimeout是異步,且是宏函數(shù),放到宏函數(shù)隊(duì)列中;

2.new Promise是同步任務(wù),直接執(zhí)行,打印2,并執(zhí)行for循環(huán);

3.promise.then是微任務(wù),放到微任務(wù)隊(duì)列中;

4.console.log(4)同步任務(wù),直接執(zhí)行,打印4;

5.此時(shí)主線程任務(wù)執(zhí)行完畢,檢查微任務(wù)隊(duì)列中,有promise.then,執(zhí)行微任務(wù),打印3;

6.微任務(wù)執(zhí)行完畢,第一次循環(huán)結(jié)束;從宏任務(wù)隊(duì)列中取出第一個(gè)宏任務(wù)到主線程執(zhí)行,打印1;

7.結(jié)果:2,4,3,1

第三個(gè)例子

console.log(1);setTimeout(function(){console.log(2);},0);Promise.resolve().then(function(){console.log(3);}).then(function(){console.log('4.我是新增的微任務(wù)');});console.log(5);執(zhí)行結(jié)果:// 1,5,3,4.我是新增的微任務(wù),2

分析:

1.console.log(1)是同步任務(wù),直接執(zhí)行,打印1;

2.setTimeout是異步,且是宏函數(shù),放到宏函數(shù)隊(duì)列中;

3.Promise.resolve().then是微任務(wù),放到微任務(wù)隊(duì)列中;

4.console.log(5)是同步任務(wù),直接執(zhí)行,打印5;

5.此時(shí)主線程任務(wù)執(zhí)行完畢,檢查微任務(wù)隊(duì)列中,有Promise.resolve().then,執(zhí)行微任務(wù),打印3;

6.此時(shí)發(fā)現(xiàn)第二個(gè).then任務(wù),屬于微任務(wù),添加到微任務(wù)隊(duì)列,并執(zhí)行,打印4.我是新增的微任務(wù);

7.這里強(qiáng)調(diào)一下,微任務(wù)執(zhí)行過(guò)程中,發(fā)現(xiàn)新的微任務(wù),會(huì)把這個(gè)新的微任務(wù)添加到隊(duì)列中,微任務(wù)隊(duì)列依次執(zhí)行完畢后,才會(huì)執(zhí)行下一個(gè)循環(huán);

8.微任務(wù)執(zhí)行完畢,第一次循環(huán)結(jié)束;取出宏任務(wù)隊(duì)列中的第一個(gè)宏任務(wù)setTimeout到主線程執(zhí)行,打印2;

9.結(jié)果:1,5,3,4.我是新增的微任務(wù),2

第四個(gè)例子

functionadd(x, y){console.log(1)? setTimeout(function(){// timer1console.log(2)? },1000)}add();setTimeout(function(){// timer2console.log(3)})newPromise(function(resolve){console.log(4)? setTimeout(function(){// timer3console.log(5)? },100)for(vari =0; i <100; i++) {? ? i ==99&& resolve()? }}).then(function(){? setTimeout(function(){// timer4console.log(6)? },0)console.log(7)})console.log(8)執(zhí)行結(jié)果//1,4,8,7,3,6,5,2

分析:

1.add()是同步任務(wù),直接執(zhí)行,打印1;

2.add()里面的setTimeout是異步任務(wù)且宏函數(shù),記做timer1放到宏函數(shù)隊(duì)列;

3.add()下面的setTimeout是異步任務(wù)且宏函數(shù),記做timer2放到宏函數(shù)隊(duì)列;

4.new Promise是同步任務(wù),直接執(zhí)行,打印4;

5.Promise里面的setTimeout是異步任務(wù)且宏函數(shù),記做timer3放到宏函數(shù)隊(duì)列;

6.Promise里面的for循環(huán),同步任務(wù),執(zhí)行代碼;

7.Promise.then是微任務(wù),放到微任務(wù)隊(duì)列;

8.console.log(8)是同步任務(wù),直接執(zhí)行,打印8;

9.此時(shí)主線程任務(wù)執(zhí)行完畢,檢查微任務(wù)隊(duì)列中,有Promise.then,執(zhí)行微任務(wù),發(fā)現(xiàn)有setTimeout是異步任務(wù)且宏函數(shù),記做timer4放到宏函數(shù)隊(duì)列;

10.微任務(wù)隊(duì)列中的console.log(7)是同步任務(wù),直接執(zhí)行,打印7;

11.微任務(wù)執(zhí)行完畢,第一次循環(huán)結(jié)束;

12.檢查宏任務(wù)Event Table,里面有timer1、timer2、timer3、timer4,四個(gè)定時(shí)器宏任務(wù),按照定時(shí)器延遲時(shí)間得到可以執(zhí)行的順序,即Event Queue:timer2、timer4、timer3、timer1,取出排在第一個(gè)的timer2;

13.取出timer2執(zhí)行,console.log(3)同步任務(wù),直接執(zhí)行,打印3;

14.沒(méi)有微任務(wù),第二次Event Loop結(jié)束;

15.取出timer4執(zhí)行,console.log(6)同步任務(wù),直接執(zhí)行,打印6;

16.沒(méi)有微任務(wù),第三次Event Loop結(jié)束;

17.取出timer3執(zhí)行,console.log(5)同步任務(wù),直接執(zhí)行,打印5;

18.沒(méi)有微任務(wù),第四次Event Loop結(jié)束;

19.取出timer1執(zhí)行,console.log(2)同步任務(wù),直接執(zhí)行,打印2;

20.沒(méi)有微任務(wù),也沒(méi)有宏任務(wù),第五次Event Loop結(jié)束;

21.結(jié)果:1,4,8,7,3,6,5,2

第五個(gè)例子

setTimeout(function(){// timer1console.log(1);? setTimeout(function(){// timer3console.log(2);? })},0);setTimeout(function(){// timer2console.log(3);},0);執(zhí)行結(jié)果//1,3,2

分析:

1.第一個(gè)setTimeout是異步任務(wù)且宏函數(shù),記做timer1放到宏函數(shù)隊(duì)列;

2.第三個(gè)setTimeout是異步任務(wù)且宏函數(shù),記做timer2放到宏函數(shù)隊(duì)列;

3.沒(méi)有微任務(wù),第一次Event Loop結(jié)束;

4.取出timer1,console.log(1)同步任務(wù),直接執(zhí)行,打印1;

5.timer1里面的setTimeout是異步任務(wù)且宏函數(shù),記做timer3放到宏函數(shù)隊(duì)列;

6.沒(méi)有微任務(wù),第二次Event Loop結(jié)束;

7.取出timer2,console.log(3)同步任務(wù),直接執(zhí)行,打印3;

8.沒(méi)有微任務(wù),第三次Event Loop結(jié)束;

9.取出timer3,console.log(2)同步任務(wù),直接執(zhí)行,打印2;

10.沒(méi)有微任務(wù),也沒(méi)有宏任務(wù),第四次Event Loop結(jié)束;

11.結(jié)果:1,3,2

7.參考文章:

1.ssssyoki《這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制》

https://juejin.im/post/59e85eebf265da430d571f89#heading-9

2.阮一峰《JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop》

http://www.ruanyifeng.com/blog/2014/10/event-loop.html

3.ziwei3749 《深入理解JS引擎的執(zhí)行機(jī)制》

https://segmentfault.com/a/1190000012806637

4.Jake《Tasks, microtasks, queues and schedules》

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/?utm_source=html5weekly

作者:StarryLake

鏈接:http://www.itdecent.cn/p/e06e86ef2595

來(lái)源:簡(jiǎn)書

簡(jiǎn)書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容