首頁 >web前端 >js教程 >瀏覽器與NodeJS的EventLoop異同以及部分機制

瀏覽器與NodeJS的EventLoop異同以及部分機制

不言
不言原創
2018-07-07 17:23:572052瀏覽

這篇文章主要介紹了關於瀏覽器與NodeJS的EventLoop異同以及部分機制,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下

瀏覽器與NodeJS的EventLoop異同,以及部分機制

javascript 是一門單線程的腳本語言,雖然是單線程但是有很多異步的API來幫助開發者解決線程的阻塞問題。例如:onClick 註冊的回呼函數、必不可少的ajax等等...但是 javascript 運行環境是如何做到單線程卻又不是一​​直阻塞線程等待各種非同步操作完成才繼續執行操作的呢?
答案是:event loop

1.event loop 的规范是在HTML5中规定的。
2.event loop 是 javascript 运行环境(手动加粗) 的机制。
3.浏览器实现的event loop 与 NodeJS 实现的event loop 是有异同的。
HTML5 定義event loop 規範連結 https://www.w3.org/TR/html5/w...


#一瀏覽器的event loop

1.簡單了解

event loop 即事件循環,它到底是什麼結構呢?阮一峰老師的部落格有一張圖,雖然很直白、明了但是少了一些東西不能全面的將 event loop 整體循環機制展示出來。先來看圖:
瀏覽器與NodeJS的EventLoop異同以及部分機制

圖片非筆者原創,來自阮一峰博客,在此說明,侵刪。

從圖中我們可以得到資訊是:

1.javascript 引擎執行javascript 是單執行緒的,因為只有一個stack 裡面有各種正在執行、等待執行的事件。
2.有一些 webAPI 將執行時產生的 callback 放入一個隊列,即 “事件隊列”。
3.在event loop 迴圈中不停的將「事件佇列」裡等待執行的事件,並推入 javascript 執行堆疊。

這就是事件循環簡化的機制,為什麼要說簡化呢?因為在循環中還做了很多沒有提及的操作、規則。

我就不舉栗子了,但是我要打個比方。

瀏覽器與NodeJS的EventLoop異同以及部分機制

瀏覽器與NodeJS的EventLoop異同以及部分機制

就說一個老生常談的問題(文章編輯不便,直接一行了,換行黨你倒是來打我啊!)

setTimeout(e=>{ console.log(1) },0);

new Promise((res,rej)=>{ res() }).then(e=>{ console.log(2) });

同樣都是javascript 中提供的非同步API,同樣都是直接執行( 開發者所希望的,雖然會因為阻塞導致延時,防止槓精),但是不論這倆行程式碼誰上、誰下,輸出都會是2 1。因為這裡涉及 event loop 中 macro taskmicro task 的執行順序、規則。


2.整體流程
回到剛才說那張流程圖不夠完善的問題上,現在來一張完整的、全面的 event loop 流程圖。
瀏覽器與NodeJS的EventLoop異同以及部分機制

圖片非筆者原創,來secrets of javascript ninja,在此說明,侵刪。

這是一個event loop 完整的流程圖,從圖中我們看到了許多剛才未提及的名詞,從頭到尾的梳理一遍(從上至下):

1.讀取Macrotask queue 中任務。有兩種情況
  • 任務佇列空,向下執行

  • 任務佇列不為空,將最先進入的一個(手動文章加粗)任務推入javascript 執行棧,向下執行

#2.讀取Microtask queue 中任務。有兩種情況
  • 任務佇列空,向下執行

  • 任務佇列不為空,將最先進入的一個任務推入javascript 執行堆疊,並且再次重複此操作(手動文章加粗),直到Microtask queue 為空。直白的說:將此任務隊列按照先後順序將所有任務推入javascript 執行棧,向下執行

3.根據本次循環耗時(手動文章加粗)判斷是否需要、是否可以更新UI 【 後面會提一下這個循環時間問題】
  • 不需要,重複第一步

  • 需要,向下執行

#4.更新UI,UI rendering,同時阻塞javascript 執行。並且繼續重複第一步。

以上便是一整個event loop 流程,從流程中我們可以看到有兩個“任務隊列”,這兩個隊列實例化到javascript 中的API 便是

Macrotask queue --> setTimeout || setInterval || javascript代码

Microtask queue --> Promise.then()

至此一個完整的event loop 流程便完全說完了。

3.实例解析
什么鬼?这么复杂? 弄懂?不存在的
瀏覽器與NodeJS的EventLoop異同以及部分機制

现在回到刚才提到的 “老生常谈的问题” 从实例的角度来说明一下问题。我们假设这个 javascript 文件叫做 "main.js"
"main.js"中的代码(+ 为自定义标记)

+1 console.log(1);

+2 setTimeout(e=>{ console.log(2); },0)

+3 setTimeout(e=>{ console.log(3); },0)

+4 new Promise((resolve,reject)=>{ console.log(4); resolve();})
.then(e=>{ console.log(5); })

+5 setTimeout(e=>{ console.log(6);

  +6 new Promise((resolve,reject)=>{ console.log(7); resolve(); })
     .then(e=>{ console.log(8);})
})

那么这个执行顺序是怎样呢?从头带尾梳理一遍(词穷,全文只要是流程统一是“从头到尾梳理一遍”)

macrotask: javascript 代码,所有同步代码执行。输出:1 4。注册 +4 到 microtask。 注册+2 +3 +5 到 macrotask。
microtask: 执行 +4 输出:5
macrotask: 执行 +2。 输出 2
microtask:
macrotask: 执行 +3。 输出 3
microtask:
macrotask: 执行 +5。 输出 6 7。 注册 +6 到 microtask。
microtask: 输出 8

所以总体输出的顺序为:1 4 5 2 3 6 7 8

如果这个输出与你所想相同,那么基本就没有问题了。
那么如果不对或者有问题怎么办?
瀏覽器與NodeJS的EventLoop異同以及部分機制

PS: 前面提到 【本次循环耗时】这个问题,这里我也不是非常清楚,望大牛指点。浏览器一般渲染页面60/S,以达到每秒60帧(60 fps),所以大概16ms一次,既然有了时间我们不经就会问?前面的任务处理耽误了则么办?因为javascript线程与UI线程互斥,某些任务导致 javascript引擎 坑了队友,自然而然没法在16ms的节点上到达这一步,从secrets of javascript ninja中了解到,一般会摒弃这次渲染,等待下一次循环。( 如有问题请指正! )

浏览器中的 event loop 到此结束,下面说说 NodeJS 的 event loop



二 NodeJS的event loop

NodeJS 的 event loop 也是有 Macrotask queue 与 Microtask queue 的。只不过 NodeJS 的略有不同。那么主要说说不同在哪里。

NodeJS中 Macrotask queue 与 Microtask queue 实例化到API为:

Macrotask queue --> script(主程序代码),setImmediate, I/O,setTimeout, setInterval

Microtask queue --> process.nextTick, Promise

1.Macrotask queue 不同之处

上面说到了浏览器 event loop 的 Macrotask queue 在每次循环中只会读取一个任务,NodeJS 中 Macrotask queue 会一次性读取完毕( 同阶段的执行完毕,后面会说到Macrotask queue 分为 6个阶段 ),然后向下读取Microtask。

注意: 这一条与 NodeJS版本有很大关系,在看 深入浅出NodeJS 这一本书时( 看的版本很旧,不知是否有修订版,如有请告知。 ),提到的 setImmediate 每次循环只会执行一次,并且给出的示例在 v8.9.1 版本跑时已不符合书中所写。书中示例如下(+ 为自定义标记,原文中没有):
+1 process.nextTick(function () {
       console.log('nextTick执行1');
   });

+2 process.nextTick(function () {
       console.log('nextTick执行2');
   });

+3 setImmediate(function () {
       console.log('setImmediateჽ执行1');

    +4 process.nextTick(function () {
           console.log('强势插入');
       });
   });

+5 setImmediate(function () {
       console.log('setImmediateჽ执行2');
   });

+6 console.log('正常执行');

正常执行
nextTick执行1
nextTick执行2
setImmediate执行1
强势插入
setImmediateჽ执行2

在 v8.9.1 中截图如下
瀏覽器與NodeJS的EventLoop異同以及部分機制

从图片中可以看到,至少在 v8.9.1 版本中 Macrotask queue 会直接全部执行。按照惯例从头到尾的梳理一遍

macrotask: javascript 代码,所有同步代码执行。输出:正常执行。注册 +3 +5 到 Macrotask。执行process.nextTick(),最终输出:正常执行, nextTick执行1, nextTick执行2。
  **microtask: 无
macrotask: 执行 +3 +5。 输出:setImmediate执行1, setImmediateჽ执行2。 执行process.nextTick(),最终输出:setImmediate执行1, setImmediateჽ执行2,强势插入。
microtask:

所以最终输出为:正常执行, nextTick执行1, nextTick执行2,setImmediate执行1, setImmediateჽ执行2,强势插入。


2.process.nextTick(),setImmediates,以及event loop的6个阶段

NodeJS 中 Macrotask queue会分为 6 个阶段,每个阶段的作用如下(process.nextTick()在6个阶段结束的时候都会执行):

timers:执行setTimeout() 和 setInterval()中到期的callback。

I/O callbacks:上一轮循环中有少数的I/Ocallback会被延迟到这一轮的这一阶段执行

idle, prepare:仅内部使用

poll:最为重要的阶段,执行I/O callback,在适当的条件下会阻塞在这个阶段

check:执行setImmediate的callback

close callbacks:执行close事件的callback,例如socket.on("close",func)
注:此6个阶段非笔者原创来自 https://cnodejs.org/topic/5a9...,文章从底层C代码分析NodeJS event loop。这里做只做简单整合。侵删。

在了解了这六个阶段后,我们可以发现定时器系列在NodeJS event loop中 Macrotask queue 读取顺序为:

1. setTimeout(fun,0) setInterval(fun,0) 
2. setImmediate

空口无凭,在实例中了解。的代码奉上( 代码较长,分为三段,方便阅读,避免滚动。 ):

+1 process.nextTick(function(){
    console.log("1");
});
+2 process.nextTick(function(){
    console.log("2");
    +3 setImmediate(function(){
        console.log("3");
    });
    +4 process.nextTick(function(){
        console.log("4");
    });
});

+5 setImmediate(function(){
    console.log("5");
    +6 process.nextTick(function(){
        console.log("6");
    });
    +7 setImmediate(function(){
        console.log("7");
    });
});
+8 setTimeout(e=>{
    console.log(8);
    +9 new Promise((resolve,reject)=>{
        console.log(8+"promise");
        resolve();
    }).then(e=>{
        console.log(8+"promise+then");
    })
},0)

+10 setTimeout(e=>{ console.log(9); },0)

+11 setImmediate(function(){
    console.log("10");
    +12 process.nextTick(function(){
        console.log("11");
    });
    +13 process.nextTick(function(){
        console.log("12");
    });
    +14 setImmediate(function(){
        console.log("13");
    });
});
console.log("14");

+15 new Promise((resolve,reject)=>{
    console.log(15);
    resolve();
}).then(e=>{
    console.log(16);
})

这么复杂的异步嵌套在一起是不是很头疼呢?
我!不!看!了!

瀏覽器與NodeJS的EventLoop異同以及部分機制

最後一遍梳理,最多、最全的一次梳理。自古以來從頭到尾的梳理一遍

macrotask: javascript 程式碼,所有同步程式碼執行。輸出:14。執行process.nextTick(),最終輸出:14,15, 1, 2, 4。 註冊 3 5 8 11 到 Macrotask。註冊 15 到 Microtask。
microtask: 執行 15 輸出 16
macrotask: 執行 8 10 輸出 8, 8promise, 9。 註冊 9 到 Microtask。
microtask: 執行 9 輸出 8promise then
macrotask: 執行 5 11 3 輸出 5, 10, 3。註冊 7 14 到 macrotask。執行process.nextTick(),最終輸出:5 10 3 6 11 12。
microtask:
macrotask: 執行 7 14。輸出:7,13
microtask:

由此最中全部的輸出為:14,15,1,2,4 ,8,8promise,9,8promise then,5,10,3,6,11,12,7,13



##三結束

到此結束了。瀏覽器的、NodeJS 的 event loop 已全部分析完成,過程中引用:阮一峰博客,知乎,CSDN部分文章內容,侵刪。

最近在了解部分底層知識,收穫豐富。其中包括 for of.... 等等各種奇奇怪怪的問題,有時間再寫吧。

以上就是本文的全部內容,希望對大家的學習有所幫助,更多相關內容請關注PHP中文網!

相關推薦:

利用javascript判斷瀏覽器類型

#用Node提供靜態檔案服務

JS瀏覽器事件循環機制#

以上是瀏覽器與NodeJS的EventLoop異同以及部分機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn