NODE基礎總結(1) —— Eventloop

Overview

Node.js 是一個基於 Chrome V8 引擎的JavaScript運行環境(runtime),Node不是一門語言,而是讓js運行在後端的運行時,而且不包括javascript全集,由於在服務端中不包含DOM和BOM. Node也提供了一些新的模塊例如http,fs模塊等。Node.js 使用了事件驅動、非阻塞式 I/O 的模型,使其輕量又高效而且Node.js 的包管理器 npm,是全球最大的開源庫生態系統。javascript

先來張圖看看node是如何工做的 java

  1. 咱們寫的js代碼會交給v8引擎進行處理
  2. 代碼中可能會調用Node API,Node會交給libuv庫處理
  3. libuv經過阻塞i/o和多線程實現了異步io
  4. 經過事件驅動的方式,將結果放到事件隊列中,最終交給咱們的應用

Libuv 是 Node.js 關鍵的一個組成部分,它爲上層的 Node.js 提供了統一的 API 調用,使其不用考慮平臺差距,隱藏了底層實現。它是一個對開發者友好的工具集,包含定時器,非阻塞的網絡 I/O,異步文件系統訪問,子進程等功能. 它封裝了 Libev、Libeio 以及 IOCP,保證了跨平臺的通用性.因此實際上,Node.js 雖說是用的 Javascript,但只是在開發時使用 Javascript 的語法來編寫程序。真正的執行過程仍是由 V8 將 Javascript 解釋,而後由 C/C++ 來執行真正的系統調用,因此並不須要過度擔憂 Javascript 執行效率的問題.node

上圖涉及到了 Libuv 自己的一個設計理念,事件循環(Event Loop)。從這裏,咱們能夠看到,咱們其實對 Node.js 的單線程一直有個誤會。事實上,它的單線程指的是自身 Javascript 運行環境的單線程,Node.js 並無給 Javascript 執行時建立新線程的能力,最終的實際操做,仍是經過 Libuv 以及它的事件循環來執行的。這也就是爲何 Javascript 一個單線程的語言,能在 Node.js 裏面實現異步操做的緣由,二者並不衝突。

在任務調用中又可分爲宏任務和微任務chrome

macro-task(宏任務): setTimeout, setInterval, setImmediate, I/Onpm

micro-task(微任務): process.nextTick, 原生Promise(有些實現的promise將then方法放到了宏任務中), MutationObserver後端

那宏任務與微任務對於執行時有什麼影響嗎,在瀏覽器與node中這二者是有區別的promise

console.log(1);
setTimeout(function() {
    console.log(2);
    Promise.resolve(1).then(function() {
        console.log('promise');
    })
})

setTimeout(function(){
    console.log(3);
})
複製代碼

以上述代碼爲例,在瀏覽器中,先默認走棧 console.log(1), 接着走第一個setTimeout,將promise微任務放到隊列中,執行微任務,微任務執行完再走宏任務,因此瀏覽器執行時,只要一碰到微任務隊列中有任務就會先去執行微任務再回來執行宏任務.這裏的輸出結果就會是 1 -> 2 -> promise -> 3瀏覽器

然而在node中,會先將宏任務隊列中的任務執行完以後再去查看微任務隊列並執行。這裏的輸出結果就會是 1 -> 2 -> 3 -> promisebash

REPL

在Node.js中爲了使開發者方便測試JavaScript代碼,提供了一個名爲REPL的可交互式運行環境。開發者能夠在該運行環境中輸入任何JavaScript表達式,當用戶按下回車鍵後,REPL運行環境將顯示該表達式的運行結果. 在命令行容器中輸入node命令並按下回車鍵,便可進入REPL運行環境.網絡

在代碼中咱們也可使用repl模塊來幫咱們建立一個repl上下文

let repl = require('repl');

let context = repl.start().context;
context.zfpx = 'zfpx';
context.age = 9;
複製代碼

repl支持一些基礎命令以下:

  1. .break 退出當前命令
  2. .clear 清除REPL運行環境上下文對象中保存的全部變量與函數
  3. .exit 退出REPL運行環境
  4. .save 把輸入的全部表達式保存到一個文件中
  5. .load 把全部的表達式加載到REPL運行環境中
  6. .help 查看幫助命令

Console

在Node.js中,使用console對象表明控制檯(在操做系統中表現爲一個操做系統指定的字符界面,好比 Window中的命令提示窗口,如下列出一些基本用法:

  • console.log
  • console.info
  • console.error
  • console.warn
  • console.dir
  • console.time
  • console.timeEnd
  • console.trace
  • console.assert

Node Event Loop

┌───────────────────────┐
┌─>│     timers(計時器)     │
|  |   執行setTimeout以及   |
|  |   setInterval的回調。  |
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     |
│  | 處理網絡、流、tcp的錯誤 |
|  | callback              |
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
|  |     node內部使用       |
│  └──────────┬────────────┘      
│  ┌──────────┴────────────┐       ┌───────────────┐ 
│  │       poll(輪詢)      │       │   incoming:   │
|  | 執行poll中的i/o隊列    | <─────┤  connections, │
|  | 檢查定時器是否到時      |       │   data, etc.  |     
│  └──────────┬────────────┘       └───────────────┘    
│  ┌──────────┴────────────┐      
│  │      check(檢查)      │
|  | 存放setImmediate回調   |
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    |
   │ 關閉的回調例如          |
   | sockect.on('close')   |
   └───────────────────────┘
複製代碼

上面的圖中描述了整個node事件循環的流程。能夠看到第一張圖中羅列出了多個階段,每一個階段維護這一個觀察者隊列

  • timers 階段: 這個階段執行setTimeout(callback) 和 setInterval(callback)預約的callback;
  • I/O callbacks 階段: 執行除了close事件的callbacks、被timers(定時器,setTimeout、setInterval等)設定的callbacks、setImmediate()設定的callbacks以外的callbacks;
  • idle, prepare 階段: 僅node內部使用;
  • poll 階段: 獲取新的I/O事件, 適當的條件下node將阻塞在這裏;
  • check 階段: 執行setImmediate() 設定的callbacks;
  • close callbacks 階段: 好比socket.on(‘close’, callback)的callback會在這個階段執行。

事件循環除了維護這些觀察者隊列,還維護了一個 time 字段,在初始化時會被賦值爲0,每次循環都會更新這個值。全部與時間相關的操做,都會和這個值進行比較,來決定是否執行。

在圖中,與 timer 相關的過程以下:

更新當前循環的 time 字段,即當前循環下的「如今」;

檢查循環中是否還有須要處理的任務(handlers/requests),若是沒有就沒必要循環了,便是否 alive。

檢查註冊過的 timer,若是某一個 timer 中指定的時間落後於當前時間了,說明該 timer 已到期,因而執行其對應的回調函數;

執行一次 I/O polling(即阻塞住線程,等待 I/O 事件發生),若是在下一個 timer 到期時尚未任何 I/O 完成,則中止等待,執行下一個 timer 的回調。若是發生了 I/O 事件,則執行對應的回調;因爲執行回調的時間裏可能又有 timer 到期了,這裏要再次檢查 timer 並執行回調。

process.nextTick

process.nextTick方法屬於微任務,它指定的任務老是發生在全部異步任務以前。

function Fn() {
    this.arrs;
    process.nextTick(() => {
        this.arrs();
   })
}
Fn.prototype.then = function() {
    this.arrs = function() { console.log(1); }
}
let fn = new Fn();
fn.then();
複製代碼

setTimeout 和 setImmediate

setImmediate在poll階段完成時執行,即check階段

setTimeout在poll階段爲空閒時,且設定時間到達後執行, 但其在timer階段執行

其兩者的調用順序取決於當前event loop的上下文,若是他們在異步i/o callback以外調用,其執行前後順序是不肯定的。

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

setImmediate(function immediate() {
  console.log('immediate');
});

$ node timeout_vs_immediate.js
timeout
immediate

$ node timeout_vs_immediate.js
immediate
timeout
複製代碼

這是由於後一個事件進入的時候,事件環可能處於不一樣的階段致使結果的不肯定。當咱們給了事件環肯定的上下文,事件的前後就能肯定了。

var fs = require('fs')

fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0)
  setImmediate(() => {
    console.log('immediate');
  })
})
$ node timeout_vs_immediate.js
immediate
timeout
複製代碼

這是由於fs.readFile的callback執行完後,程序設定了timer 和 setImmediate,所以poll階段不會被阻塞進而進入check階段先執行setImmediate,後進入timer階段執行setTimeout。

Debugger

V8 提供了一個強大的調試器,能夠經過 TCP 協議從外部訪問。Nodejs提供了一個內建調試器來幫助開發者調試應用程序。想要開啓調試器咱們須要在代碼中加入debugger標籤,當Nodejs執行到debugger標籤時會自動暫停(debugger標籤至關於在代碼中開啓一個斷點)

node inspect main.js
複製代碼

固然如今更流行的方式是在瀏覽器中進行調試,node瀏覽器調試能夠經過chrome瀏覽器進行調試

node --inspect-brk main.js
複製代碼

打開chrome 訪問 chrome://inspect便可開始調試

另外各個編輯器也會有各自的方法能夠配置本身的調試器來作調試

相關文章
相關標籤/搜索