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

本周琢磨一下js運(yùn)行機(jī)制的問題,發(fā)現(xiàn)了很多有意思的東西,在此拋個(gè)磚,如有不對(duì)的地方歡迎指正~

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

  • js作為瀏覽器腳本語(yǔ)言,它的主要用途是與用戶互動(dòng),以及操作DOM,因此js是單線程,也避免了同時(shí)操作同一個(gè)DOM的矛盾問題;
  • 為了利用多核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è)過程是循環(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)
  1. 整體的script(作為第一個(gè)宏任務(wù))開始執(zhí)行的時(shí)候,會(huì)把所有代碼分為兩部分:“同步任務(wù)”、“異步任務(wù)”;
  2. 同步任務(wù)會(huì)直接進(jìn)入主線程依次執(zhí)行;
  3. 異步任務(wù)會(huì)再分為宏任務(wù)和微任務(wù);
  4. 宏任務(wù)進(jìn)入到Event Table中,并在里面注冊(cè)回調(diào)函數(shù),每當(dāng)指定的事件完成時(shí),Event Table會(huì)將這個(gè)函數(shù)移到Event Queue中;
  5. 微任務(wù)也會(huì)進(jìn)入到另一個(gè)Event Table中,并在里面注冊(cè)回調(diào)函數(shù),每當(dāng)指定的事件完成時(shí),Event Table會(huì)將這個(gè)函數(shù)移到Event Queue中;
  6. 當(dāng)主線程內(nèi)的任務(wù)執(zhí)行完畢,主線程為空時(shí),會(huì)檢查微任務(wù)的Event Queue,如果有任務(wù),就全部執(zhí)行,如果沒有就執(zhí)行下一個(gè)宏任務(wù);
  7. 上述過程會(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í)行完畢,沒有微任務(wù),那么執(zhí)行第二個(gè)宏任務(wù)setTimeout,打印2;
5.結(jié)果:1,3,2

2.第二個(gè)例子
setTimeout(function(){
    console.log(1)
});

new Promise(function(resolve){
    console.log(2);
    for(var i = 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í)行過程中,發(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è)例子
function add(x, y) {
  console.log(1)
  setTimeout(function() { // timer1
    console.log(2)
  }, 1000)
}
add();

setTimeout(function() { // timer2
  console.log(3)
})

new Promise(function(resolve) {
  console.log(4)
  setTimeout(function() { // timer3
    console.log(5)
  }, 100)
  for(var i = 0; i < 100; i++) {
    i == 99 && resolve()
  }
}).then(function() {
  setTimeout(function() { // timer4
    console.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.沒有微任務(wù),第二次Event Loop結(jié)束;
15.取出timer4執(zhí)行,console.log(6)同步任務(wù),直接執(zhí)行,打印6;
16.沒有微任務(wù),第三次Event Loop結(jié)束;
17.取出timer3執(zhí)行,console.log(5)同步任務(wù),直接執(zhí)行,打印5;
18.沒有微任務(wù),第四次Event Loop結(jié)束;
19.取出timer1執(zhí)行,console.log(2)同步任務(wù),直接執(zhí)行,打印2;
20.沒有微任務(wù),也沒有宏任務(wù),第五次Event Loop結(jié)束;
21.結(jié)果:1,4,8,7,3,6,5,2

第五個(gè)例子
setTimeout(function() { // timer1
  console.log(1);
  setTimeout(function() {  // timer3
    console.log(2);
  })
}, 0);
setTimeout(function() {  // timer2
  console.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.沒有微任務(wù),第一次Event Loop結(jié)束;
4.取出timer1,console.log(1)同步任務(wù),直接執(zhí)行,打印1;
5.timer1里面的setTimeout是異步任務(wù)且宏函數(shù),記做timer3放到宏函數(shù)隊(duì)列;
6.沒有微任務(wù),第二次Event Loop結(jié)束;
7.取出timer2,console.log(3)同步任務(wù),直接執(zhí)行,打印3;
8.沒有微任務(wù),第三次Event Loop結(jié)束;
9.取出timer3,console.log(2)同步任務(wù),直接執(zhí)行,打印2;
10.沒有微任務(wù),也沒有宏任務(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

?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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