Javascript 運行機制詳解,Event Loop

單線程

javascript爲何是單線程語言,緣由在於若是是多線程,當一個線程對DOM節點作添加內容操做的時候,另外一個線程要刪除這個DOM節點,這個時候,瀏覽器應該怎麼選擇,這就形成了混亂,爲了解決這類問題,在一開始的時候,javascript就採用單線程模式。javascript

在後面H5出的web worker標準的時候,看似是多線程,實際上是在一個主線程來控制其餘線程,並且不能操做DOM,因此本質仍是單線程java

任務隊列

任務能夠分爲兩種,一種爲同步,另外一種爲異步(具備回調函數)。以下圖:

全部的同步任務都在主線程上執行,造成一個執行棧 stack。當全部同步任務執行完畢後,它會去執行microtask queue中的異步任務(nextTick,Promise),將他們所有執行。主線程以外還有一個任務隊列task queue,當有異步任務(DOM,AJAX,setTimeout,setImmediate)有結果的時候,就在任務隊列裏放一個事件,一旦執行棧和microtask queue任務執行完畢,系統就會讀取任務隊列,將取出排在最前面的事件加入執行棧執行,這種機制就是任務隊列。node

Event Loop

主線程在任務隊列中讀取事件,這個過程是循環不斷地,因此這種運行機制叫作Event Loop(事件循環)web

nextTick、setImmediate、setTimeout

nextTick是在執行棧同步代碼結束以後,下一次Event Loop(任務隊列)執行以前。當全部同步任務執行完,會在queue中執行nextTick,不管nextTick有多少層回調,都會執行完畢後再去任務隊列,因此會形成一直停留在當前執行棧,沒法執行任務隊列,請看下面代碼瀏覽器

process.nextTick(function () {
    console.log('nextTick1');
    process.nextTick(function (){console.log('nextTick2')});
});
  
setTimeout(function timeout() {
    console.log('setTimeout');
}, 0)

執行完畢後輸出nextTick一、nextTick二、setTimeout,緣由是nextTick是在當前執行棧末尾執行,而setTimeout是在下次任務隊列在執行bash

setImmediate方法是在Event Loop(任務隊列)末尾,也就是下一次Event Loop時執行。
setTimeout方法是按照執行時間,放入任務隊列,有時快與setImmediate有時慢。請看如下代碼多線程

setImmediate(function () {
    console.log('setImmediate1');
    setImmediate(function (){console.log('setImmediate2')});
});
  
setTimeout(function timeout() {
    console.log('setTimeout');
}, 0);

這段代碼執行完多是setImmediate一、setTimeout、setImmediate2,也多是setTimeout、setImmediate一、setImmediate2,緣由是setTimeout和setImmediate1都是在下次Event Loop中觸發,因此前後不肯定,可是setImmediate2確定是最後,由於他是在setImmediate1任務隊列以後,也就是下下次Event Loop執行異步

Node.js的Event Loop

Node.js也是單線程的Event Loop可是和瀏覽器有些區別,如圖所示,socket

1.先經過Chrom V8引擎解析Javascript腳本函數

2.解析完畢後調用Node API

3.LIBUV庫負責Node API的執行,將不一樣任務分配給不一樣的線程,造成一個Event Loop(任務隊列)

4.最後Chrom V8引擎將結果返回給用戶

Node.js Event Loop原理

node.js的特色是事件驅動,非阻塞單線程。當應用程序須要I/O操做的時候,線程並不會阻塞,而是把I/O操做交給底層庫(LIBUV)。此時node線程會去處理其餘任務,當底層庫處理完I/O操做後,會將主動權交還給Node線程,因此Event Loop的用處是調度線程,例如:當底層庫處理I/O操做後調度Node線程處理後續工做,因此雖然node是單線程,可是底層庫處理操做依然是多線程

Node Event Loop的事件處理機制

┌───────────────────────┐

┌─>│        timers         │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

│  │     I/O callbacks     │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

│  │     idle, prepare     │

│  └──────────┬────────────┘      ┌───────────────┐

│  ┌──────────┴────────────┐      │   incoming:   │

│  │         poll          │<─────┤  connections, │

│  └──────────┬────────────┘      │   data, etc.  │

│  ┌──────────┴────────────┐      └───────────────┘

│  │        check          │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

└──┤    close callbacks    │

   └───────────────────────┘

上面處理階段都是按照先進先出的規則執行回調函數,按順序執行,直到隊列爲空或是該階段執行的回調函數達到該階段所容許一次執行回調函數的最大限制後,纔會將操做權移交給下一階段。

  • timers: 用來檢查setTimeout()和setInterval()定時器是否到期,若是到期則執行它,不然下一階段
  • I/O callbacks: 用來處理timers階段、setImmediate、和TCP他們的異常回調函數或者error
  • idle, prepare: nodejs內部函數調用,在循環被I/O阻塞以前prepare回調就會當即調用
  • poll: 用來監聽fd的事件的,好比socket的可讀,可寫,文件的可讀可等等
  • check: setImmediate()函數只會在這個階段執行
  • close callbacks: 執行一些諸如關閉事件的回調函數,如socket.on('close', ...)

具體分析,看下圖:

1.當setTimeout時間最小,讀取文件不存在的時候

如圖所示,分別是nextTick、readFile、setTimeout、setImmediate,然而如今並無1.txt和2.txt文件,輸出結果是next Tick、setTimeout、readFile、setImmediate,在event loop中先判斷的是timeers,最早出書next Tick由於process.nextTick的實現是基於v8 MicroTask(是在當前js call stack 中沒有可執行代碼纔會執行的隊列,低於js call stack 代碼,但高於事件循環,不屬於Event Loop,上面javascript的Event Loop介紹過了,因此最早輸出。而後開始走Event Loop,第一階段是timers,判斷setTimeout到期,因此輸出setTimeout,進入下一階段,poll將I/O操做權交出,新線程操做,可是並無相關讀取文件,因此直接返回回調函數,因此到處readFile,最後到check階段,輸出setImmediate

2.當setTimeout時間最小,讀取文件存在的時候

如圖所示,分別是nextTick、setTimeout、setImmediate、readFile,此次readFile在最後面,是由於文件存在,執行到poll階段的時候,執行I/O操做,node線程開始執行check階段,當交出的I/O操做結束後,返回給Event Loop因此再執行readFile的回調函數,因此他在最後面

3.當setTimeout時間爲100毫秒,讀取文件不存在的時候
如圖所示,分別是nextTick、readFile、setImmediate、setTimeout,它和1不一樣的地方是setTimeout排在最後了,這是由於在執行timers的時候,setTimeout沒有到期,因此直接執行下一階段,當執行完poll的時候,會去執行查看定時器有沒有到期,若是沒有下一次Event Loop再次查看,知道定時器到期,因此他在最後面

4.當setTimeout時間爲100毫秒,讀取文件存在的時候

如圖所示,分別是nextTick、setImmediate、readFile、setTimeout,它和2的區別是setTimeout在最後,緣由和3同樣。

總結一下

1.javascript和node.js都是單線程,可是node底層是多線程操做

2.Event Loop —— 任務隊列

3.當同時設置nextTick, setImmediate, setTimeout時必定是nextTick先執行,nextTick不屬於Event LOop,它屬於v8的micro tasks,而且會阻塞Event Loop

4.setImmediate,setTimeout屬於Event Loop可是,直接階段不一樣

相關文章
相關標籤/搜索