Node.js Event Loop與瀏覽器 Event Loop(事件環)

要理解Event Loop首先要學習幾個概念,下面就經過這些概念來一步步解析Event Loopnode

一:進程與線程

進程是操做系統分配資源和調度任務的基本單位,線程是創建在進程上的一次程序運行單位,一個進程上能夠有多個線程。ajax

1.1 爲何JavaScript是單線程?

JavaScript語言的一大特色就是單線程,也就是說,同一個時間只能作一件事。那麼,爲何JavaScript不能有多個線程呢?這樣能提升效率啊。瀏覽器

JavaScript的單線程,與它的用途有關。做爲瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操做DOM。這決定了它只能是單線程,不然會帶來很複雜的同步問題。好比,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另外一個線程刪除了這個節點,這時瀏覽器應該以哪一個線程爲準?bash

因此,爲了不復雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特徵,未來也不會改變。網絡

爲了利用多核CPU的計算能力,HTML5提出Web Worker標準,容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。因此,這個新標準並無改變JavaScript單線程的本質。異步

這裏所謂的單線程指的是主線程是單線程的,因此在Node中主線程依舊是單線程的。async

1.2 其餘線程

  • 瀏覽器事件觸發線程(用來控制事件循環,存放setTimeout、瀏覽器事件、ajax的回調函數)
  • 定時觸發器線程(setTimeout定時器所在線程)
  • 異步HTTP請求線程(ajax請求線程)

單線程特色是節約了內存,而且不須要在切換執行上下文。並且單線程不須要管鎖的問題,這裏簡單說下鎖的概念。例以下課了你們都要去上廁所,廁所就一個,至關於全部人都要訪問同一個資源。那麼先進去的就要上鎖。而對於node來講。下課了就一我的去廁所,因此免除了鎖的問題!函數

二:隊列和棧

2.1 棧內存or堆內存

堆和棧這兩個字咱們已經接觸多不少次,那麼具體是什麼存在棧中什麼存在堆中呢?就拿JavaScript中的變量來講:oop

  • 首先JavaScript中的變量分爲基本類型和引用類型。
  • 基本類型就是保存在棧內存中的簡單數據段,而引用類型指的是那些保存在堆內存中的對象。

一、基本類型學習

基本類型有Undefined、Null、Boolean、Number 和String。這些類型在內存中分別佔有固定大小的空間,他們的值保存在棧空間,咱們經過按值來訪問的。

二、引用類型

引用類型,值大小不固定,棧內存中存放地址指向堆內存中的對象。是按引用訪問的。以下圖所示:棧內存中存放的只是該對象的訪問地址,在堆內存中爲這個值分配空間。因爲這種值的大小不固定,所以不能把它們保存到棧內存中。但內存地址大小的固定的,所以能夠將內存地址保存在棧內存中。 這樣,當查詢引用類型的變量時, 先從棧中讀取內存地址, 而後再經過地址找到堆中的值。對於這種,咱們把它叫作按引用訪問。

2.2 任務隊列

單線程就意味着,全部任務須要排隊,前一個任務結束,纔會執行後一個任務。若是前一個任務耗時很長,後一個任務就不得不一直等着。

若是排隊是由於計算量大,CPU忙不過來,倒也算了,可是不少時候CPU是閒着的,由於IO設備(輸入輸出設備)很慢(好比Ajax操做從網絡讀取數據),不得不等着結果出來,再往下執行。

JavaScript語言的設計者意識到,這時主線程徹底能夠無論IO設備,掛起處於等待中的任務,先運行排在後面的任務。等到IO設備返回告終果,再回過頭,把掛起的任務繼續執行下去。

因而,全部任務能夠分紅兩種,一種是同步任務(synchronous),另外一種是異步任務(asynchronous)。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。

具體來講,異步執行的運行機制以下。(同步執行也是如此,由於它能夠被視爲沒有異步任務的異步執行。)

(1)全部同步任務都在主線程上執行,造成一個執行棧(execution context stack)。
 (2)主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
 (3)一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。
 (4)主線程不斷重複上面的第三步。
複製代碼

三:瀏覽器中的Event Loop

主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,因此整個的這種運行機制又稱爲Event Loop(事件循環)。

爲了更好地理解Event Loop,請看下圖

上圖中,主線程運行的時候,產生堆(heap)和棧(stack),棧中的代碼調用各類外部API,它們在"任務隊列"中加入各類事件(click,load,done)。只要棧中的代碼執行完畢,主線程就會去讀取"任務隊列",依次執行那些事件所對應的回調函數。

  • 全部同步任務都在主線程上執行,造成一個執行棧
  • 主線程以外,還存在一個任務隊列。只要異步任務有了運行結果,就在任務隊列之中放置一個事件。
  • 一旦執行棧中的全部同步任務執行完畢,系統就會讀取任務隊列,將隊列中的事件放到執行棧中依次執行
  • 主線程從任務隊列中讀取事件,這個過程是循環不斷的

整個的這種運行機制又稱爲Event Loop(事件循環)

四:Node.js的Event Loop

Node.js也是單線程的Event Loop,可是它的運行機制不一樣於瀏覽器環境。

請看下面的示意圖

根據上圖,Node.js的運行機制以下。

  • 咱們寫的js代碼會交給v8引擎進行處理
  • 代碼中可能會調用nodeApi,node會交給libuv庫處理
  • libuv庫負責Node API的執行。它將不一樣的任務分配給不一樣的線程,造成一個Event Loop(事件循環),以異步的方式將任務的執行結果返回給V8引擎。
  • 經過事件驅動的方式,將結果放到事件隊列中,最終交給咱們的應用。

除了setTimeout和setInterval這兩個方法,Node.js還提供了另外兩個與"任務隊列"有關的方法:process.nextTick和setImmediate。

process.nextTick方法能夠在當前"執行棧"的尾部----下一次Event Loop(主線程讀取"任務隊列")以前----觸發回調函數。也就是說,它指定的任務老是發生在全部異步任務以前。setImmediate方法則是在當前"任務隊列"的尾部添加事件,也就是說,它指定的任務老是在下一次Event Loop時執行,這與setTimeout(fn, 0)很像

process.nextTick(function A() {
  console.log(1);
  process.nextTick(function B(){console.log(2);});
});

setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0)
// 1
// 2
// TIMEOUT FIRED
複製代碼

上面代碼中,因爲process.nextTick方法指定的回調函數,老是在當前"執行棧"的尾部觸發,因此不只函數A比setTimeout指定的回調函數timeout先執行,並且函數B也比timeout先執行。這說明,若是有多個process.nextTick語句(無論它們是否嵌套),將所有在當前"執行棧"執行。

相關文章
相關標籤/搜索