js運行機制詳解(Event Loop)

本周琢磨一下js運行機制的問題令宿,發(fā)現(xiàn)了很多有意思的東西刽脖,在此拋個磚,如有不對的地方歡迎指正~

1.基礎(chǔ)知識

  • js作為瀏覽器腳本語言执隧,它的主要用途是與用戶互動揩抡,以及操作DOM,因此js是單線程镀琉,也避免了同時操作同一個DOM的矛盾問題峦嗤;
  • 為了利用多核CPU的計算能力,H5的Web Worker實現(xiàn)的“多線程”實際上指的是“多子線程”屋摔,完全受控于主線程烁设,且不允許操作DOM;
  • js引擎存在monitoring process進程钓试,會持續(xù)不斷的檢查主線程執(zhí)行棧是否為空装黑,一旦為空,就會去Event Queue那里檢查是否有等待被調(diào)用的函數(shù)弓熏。這個過程是循環(huán)不斷的恋谭,所以整個的這種運行機制又稱為Event Loop(事件循環(huán))
  • 所有同步任務都在主線程上執(zhí)行,形成一個執(zhí)行棧(execution context stack)挽鞠;
  • 如果在微任務執(zhí)行期間微任務隊列加入了新的微任務疚颊,會將新的微任務加入隊列尾部,之后也會被執(zhí)行信认;

2.js中的異步操作

  • setTimeOut
  • setInterval
  • ajax
  • promise
  • I/O

3.同步任務 or 異步任務

  • 同步任務(synchronous):在主線程上排隊執(zhí)行的任務串稀,只有前一個任務執(zhí)行完畢,才能執(zhí)行后一個任務狮杨;
  • 異步任務(asynchronous):不進入主線程母截、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程橄教,某個異步任務可以執(zhí)行了清寇,該任務才會進入主線程執(zhí)行喘漏。

4.宏任務 or 微任務

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

  • 宏任務(macro-task):整體代碼script华烟、setTimeOut翩迈、setInterval
  • 微任務(mincro-task):promise.then、promise.nextTick(node)

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

Event Loop循環(huán)
  1. 整體的script(作為第一個宏任務)開始執(zhí)行的時候盔夜,會把所有代碼分為兩部分:“同步任務”负饲、“異步任務”;
  2. 同步任務會直接進入主線程依次執(zhí)行喂链;
  3. 異步任務會再分為宏任務和微任務返十;
  4. 宏任務進入到Event Table中,并在里面注冊回調(diào)函數(shù)椭微,每當指定的事件完成時洞坑,Event Table會將這個函數(shù)移到Event Queue中;
  5. 微任務也會進入到另一個Event Table中蝇率,并在里面注冊回調(diào)函數(shù)迟杂,每當指定的事件完成時,Event Table會將這個函數(shù)移到Event Queue中本慕;
  6. 當主線程內(nèi)的任務執(zhí)行完畢排拷,主線程為空時,會檢查微任務的Event Queue锅尘,如果有任務监氢,就全部執(zhí)行,如果沒有就執(zhí)行下一個宏任務鉴象;
  7. 上述過程會不斷重復,這就是Event Loop事件循環(huán)何鸡;

6.代碼示例

1.第一個例子
console.log(1)

setTimeout(function(){
  console.log(2)
},0)

console.log(3)

執(zhí)行結(jié)果:
// 1 3 2

分析:
1.console.log(1)是同步任務纺弊,直接打印1;
2.setTimeout是異步任務骡男,且是宏函數(shù)淆游,放到宏函數(shù)隊列中,等待下次Event Loop才會執(zhí)行隔盛;
3.console.log(3)是同步任務犹菱,直接打印3;
4.主線程執(zhí)行完畢吮炕,沒有微任務腊脱,那么執(zhí)行第二個宏任務setTimeout,打印2龙亲;
5.結(jié)果:1陕凹,3悍抑,2

2.第二個例子
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ù)隊列中记靡;
2.new Promise是同步任務,直接執(zhí)行团驱,打印2摸吠,并執(zhí)行for循環(huán);
3.promise.then是微任務店茶,放到微任務隊列中蜕便;
4.console.log(4)同步任務,直接執(zhí)行贩幻,打印4轿腺;
5.此時主線程任務執(zhí)行完畢,檢查微任務隊列中丛楚,有promise.then族壳,執(zhí)行微任務,打印3趣些;
6.微任務執(zhí)行完畢仿荆,第一次循環(huán)結(jié)束;從宏任務隊列中取出第一個宏任務到主線程執(zhí)行坏平,打印1拢操;
7.結(jié)果:2,4舶替,3令境,1

第三個例子
console.log(1);

setTimeout(function() {
  console.log(2);
}, 0);

Promise.resolve().then(function() {
  console.log(3);
}).then(function() {
  console.log('4.我是新增的微任務');
});

console.log(5);

執(zhí)行結(jié)果:
// 1,5顾瞪,3舔庶,4.我是新增的微任務,2

分析:
1.console.log(1)是同步任務陈醒,直接執(zhí)行惕橙,打印1;
2.setTimeout是異步钉跷,且是宏函數(shù)弥鹦,放到宏函數(shù)隊列中;
3.Promise.resolve().then是微任務爷辙,放到微任務隊列中惶凝;
4.console.log(5)是同步任務吼虎,直接執(zhí)行,打印5苍鲜;
5.此時主線程任務執(zhí)行完畢思灰,檢查微任務隊列中,有Promise.resolve().then混滔,執(zhí)行微任務洒疚,打印3;
6.此時發(fā)現(xiàn)第二個.then任務坯屿,屬于微任務油湖,添加到微任務隊列,并執(zhí)行领跛,打印4.我是新增的微任務乏德;
7.這里強調(diào)一下,微任務執(zhí)行過程中吠昭,發(fā)現(xiàn)新的微任務喊括,會把這個新的微任務添加到隊列中,微任務隊列依次執(zhí)行完畢后矢棚,才會執(zhí)行下一個循環(huán)郑什;
8.微任務執(zhí)行完畢,第一次循環(huán)結(jié)束蒲肋;取出宏任務隊列中的第一個宏任務setTimeout到主線程執(zhí)行蘑拯,打印2;
9.結(jié)果:1兜粘,5申窘,3,4.我是新增的微任務孔轴,2

第四個例子
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()是同步任務帽氓,直接執(zhí)行趣斤,打印1;
2.add()里面的setTimeout是異步任務且宏函數(shù)黎休,記做timer1放到宏函數(shù)隊列浓领;
3.add()下面的setTimeout是異步任務且宏函數(shù)玉凯,記做timer2放到宏函數(shù)隊列;
4.new Promise是同步任務联贩,直接執(zhí)行漫仆,打印4;
5.Promise里面的setTimeout是異步任務且宏函數(shù)泪幌,記做timer3放到宏函數(shù)隊列盲厌;
6.Promise里面的for循環(huán),同步任務祸泪,執(zhí)行代碼趋艘;
7.Promise.then是微任務急鳄,放到微任務隊列;
8.console.log(8)是同步任務,直接執(zhí)行揍移,打印8;
9.此時主線程任務執(zhí)行完畢幢妄,檢查微任務隊列中着裹,有Promise.then,執(zhí)行微任務品嚣,發(fā)現(xiàn)有setTimeout是異步任務且宏函數(shù)炕倘,記做timer4放到宏函數(shù)隊列;
10.微任務隊列中的console.log(7)是同步任務翰撑,直接執(zhí)行罩旋,打印7;
11.微任務執(zhí)行完畢眶诈,第一次循環(huán)結(jié)束涨醋;
12.檢查宏任務Event Table,里面有timer1逝撬、timer2浴骂、timer3、timer4宪潮,四個定時器宏任務溯警,按照定時器延遲時間得到可以執(zhí)行的順序,即Event Queue:timer2狡相、timer4梯轻、timer3、timer1尽棕,取出排在第一個的timer2喳挑;
13.取出timer2執(zhí)行,console.log(3)同步任務,直接執(zhí)行伊诵,打印3单绑;
14.沒有微任務,第二次Event Loop結(jié)束曹宴;
15.取出timer4執(zhí)行搂橙,console.log(6)同步任務,直接執(zhí)行笛坦,打印6份氧;
16.沒有微任務,第三次Event Loop結(jié)束弯屈;
17.取出timer3執(zhí)行蜗帜,console.log(5)同步任務,直接執(zhí)行资厉,打印5厅缺;
18.沒有微任務,第四次Event Loop結(jié)束宴偿;
19.取出timer1執(zhí)行湘捎,console.log(2)同步任務,直接執(zhí)行窄刘,打印2窥妇;
20.沒有微任務,也沒有宏任務娩践,第五次Event Loop結(jié)束活翩;
21.結(jié)果:1,4翻伺,8材泄,7,3吨岭,6拉宗,5,2

第五個例子
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.第一個setTimeout是異步任務且宏函數(shù),記做timer1放到宏函數(shù)隊列急灭;
2.第三個setTimeout是異步任務且宏函數(shù)姐浮,記做timer2放到宏函數(shù)隊列;
3.沒有微任務化戳,第一次Event Loop結(jié)束单料;
4.取出timer1,console.log(1)同步任務点楼,直接執(zhí)行扫尖,打印1;
5.timer1里面的setTimeout是異步任務且宏函數(shù)掠廓,記做timer3放到宏函數(shù)隊列换怖;
6.沒有微任務,第二次Event Loop結(jié)束蟀瞧;
7.取出timer2沉颂,console.log(3)同步任務,直接執(zhí)行悦污,打印3铸屉;
8.沒有微任務,第三次Event Loop結(jié)束切端;
9.取出timer3彻坛,console.log(2)同步任務,直接執(zhí)行踏枣,打印2昌屉;
10.沒有微任務,也沒有宏任務茵瀑,第四次Event Loop結(jié)束间驮;
11.結(jié)果:1,3马昨,2

7.參考文章:

1.ssssyoki《這一次竞帽,徹底弄懂 JavaScript 執(zhí)行機制》
https://juejin.im/post/59e85eebf265da430d571f89#heading-9
2.阮一峰《JavaScript 運行機制詳解:再談Event Loop》
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
3.ziwei3749 《深入理解JS引擎的執(zhí)行機制》
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)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鸿捧,隨后出現(xiàn)的幾起案子抢呆,更是在濱河造成了極大的恐慌,老刑警劉巖笛谦,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抱虐,死亡現(xiàn)場離奇詭異,居然都是意外死亡饥脑,警方通過查閱死者的電腦和手機恳邀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灶轰,“玉大人谣沸,你說我怎么就攤上這事∷癫” “怎么了乳附?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵内地,是天一觀的道長。 經(jīng)常有香客問我赋除,道長阱缓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任举农,我火速辦了婚禮荆针,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颁糟。我一直安慰自己航背,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布棱貌。 她就那樣靜靜地躺著玖媚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪婚脱。 梳的紋絲不亂的頭發(fā)上最盅,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音起惕,去河邊找鬼涡贱。 笑死,一個胖子當著我的面吹牛惹想,可吹牛的內(nèi)容都是我干的问词。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嘀粱,長吁一口氣:“原來是場噩夢啊……” “哼激挪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起锋叨,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤垄分,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后娃磺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體薄湿,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年偷卧,在試婚紗的時候發(fā)現(xiàn)自己被綠了豺瘤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡听诸,死狀恐怖坐求,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晌梨,我是刑警寧澤桥嗤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布须妻,位于F島的核電站,受9級特大地震影響泛领,放射性物質(zhì)發(fā)生泄漏荒吏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一师逸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧豆混,春花似錦篓像、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鸵鸥,卻和暖如春奠滑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背妒穴。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工宋税, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人讼油。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓杰赛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親矮台。 傳聞我的和親對象是個殘疾皇子乏屯,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

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