Node.js 是一個基於 Chrome V8 引擎的JavaScript運行環境(runtime),Node不是一門語言,而是讓js運行在後端的運行時,而且不包括javascript全集,由於在服務端中不包含DOM和BOM. Node也提供了一些新的模塊例如http,fs模塊等。Node.js 使用了事件驅動、非阻塞式 I/O 的模型,使其輕量又高效而且Node.js 的包管理器 npm,是全球最大的開源庫生態系統。javascript
先來張圖看看node是如何工做的 java
Libuv 是 Node.js 關鍵的一個組成部分,它爲上層的 Node.js 提供了統一的 API 調用,使其不用考慮平臺差距,隱藏了底層實現。它是一個對開發者友好的工具集,包含定時器,非阻塞的網絡 I/O,異步文件系統訪問,子進程等功能. 它封裝了 Libev、Libeio 以及 IOCP,保證了跨平臺的通用性.因此實際上,Node.js 雖說是用的 Javascript,但只是在開發時使用 Javascript 的語法來編寫程序。真正的執行過程仍是由 V8 將 Javascript 解釋,而後由 C/C++ 來執行真正的系統調用,因此並不須要過度擔憂 Javascript 執行效率的問題.node
在任務調用中又可分爲宏任務和微任務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
在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支持一些基礎命令以下:
在Node.js中,使用console對象表明控制檯(在操做系統中表現爲一個操做系統指定的字符界面,好比 Window中的命令提示窗口,如下列出一些基本用法:
┌───────────────────────┐
┌─>│ 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') |
└───────────────────────┘
複製代碼
事件循環除了維護這些觀察者隊列,還維護了一個 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方法屬於微任務,它指定的任務老是發生在全部異步任務以前。
function Fn() {
this.arrs;
process.nextTick(() => {
this.arrs();
})
}
Fn.prototype.then = function() {
this.arrs = function() { console.log(1); }
}
let fn = new Fn();
fn.then();
複製代碼
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。
V8 提供了一個強大的調試器,能夠經過 TCP 協議從外部訪問。Nodejs提供了一個內建調試器來幫助開發者調試應用程序。想要開啓調試器咱們須要在代碼中加入debugger標籤,當Nodejs執行到debugger標籤時會自動暫停(debugger標籤至關於在代碼中開啓一個斷點)
node inspect main.js
複製代碼
固然如今更流行的方式是在瀏覽器中進行調試,node瀏覽器調試能夠經過chrome瀏覽器進行調試
node --inspect-brk main.js
複製代碼
打開chrome 訪問 chrome://inspect便可開始調試
另外各個編輯器也會有各自的方法能夠配置本身的調試器來作調試