JS高階--node.JS
大家需要知道nodejs 的出現,允許我們js開發人員使用 js 來開發伺服器。
node.js 特點
-
它是一個 javascript 的執行環境
-
依賴於 V8 引擎進行程式碼解析
-
-
非阻塞 I/O
-
輕量、可伸縮
-
單執行緒:指的是 nodejs 在執行 js 程式碼的時候是單執行緒,並不是說整個 node.js 是單執行緒。
REPL 環境,英語全稱 read-eval-print-loop,翻譯成中文就稱之為“即時執行環境”,在這個環境裡面,可以執行 js 程式碼。
該環境的意義在於測試一些小段的程式碼。一般比較的成熟的語言都提供REPL環境。
模組化
如果想要開發大型應用,必須要有模組化。
最早,js 是沒有模組話的概念的,因為最早 js 的定位就是指令碼語言(玩具語言)。
但是隨著 js 走向伺服器端的開發,那麼必然也是需要模組化。
總結一下模組化的好處:
-
生產效率高
-
維護成本低
接下來我們就需要看一下 node.js 中如何實現模組化,一般來講,分為3類
-
檔案模組
-
核心模組
-
第三方模組
引入模組統一使用 require 方法。
const readline = require('readline-sync');
require(“路徑.副檔名”) // 相對路徑 require('./index.js'); require('../index.js'); // 絕對路徑 require('/src/index.js'); // 一個斜槓 / 代表根目錄
核心模組
核心模組是指 nodejs 內建的模組,比如 fs、os、http、path...
require('fs'); require('path');
第三方模組
require('readline-sync');
CommonJS 規範
之前,我們接觸過一個東西,叫做 ECMAScript,它就是 js 語言的規範。
我們可以這麼說,ECMAScript 是規範,JavaScript 是這個規範的一種實現,例如 adobe 的 flash 裡面使用的 ActionScript 也是一種 ECMAScript 的實現。
ECMAScript 雖然說是 js 的規範,但是它只管客戶端,Commonjs 管的就是瀏覽器端以外的環境。
CommonJS一些著名的實現,node.js、mongodb、couchdb、coffiejs。
CommonJS 中的模組匯入匯出
在 CommonJS 中,通過 module.exports 來匯出一個模組,通過 require 來匯入一個模組。
module.exports 可以將他想象成一個物件。
// index.js // 匯出模組 module.exports.name = 'xiejie'; module.exports.sayHello = function(){ console.log('Hello'); } // index2.js // 匯入模組 let obj = require('./index.js'); console.log(obj.name); // xiejie obj.sayHello(); // Hello
為了讓開發人員使用起來更加方便,nodejs 還提供了一個 exports 的物件,它是指向 module.exports 的。
// index.js // 匯出模組 exports.name = 'xiejie'; exports.sayHello = function(){ console.log('Hello'); } // index2.js // 匯入模組 let obj = require('./index.js'); console.log(obj.name); // xiejie obj.sayHello(); // Hello
但是兩者之間是有區別,上面我們在介紹 exports 的時候,我們有講過, exports 是指向 module.exports 的,也就是說,最終,匯出的是 module.exports
// index.js module.exports = 'F71'; // index2.js let obj = require('./index.js'); console.log(obj); // F71 // index.js exports = 'F71'; // index2.js let obj = require('./index.js'); console.log(obj); // {}
nodejs 的兩大特點
nodejs 有兩個最大的特點:非同步無阻塞和事件驅動。
1. 非同步無阻塞
傳統的同步程式碼,如果在它的執行緒中遇到磁碟讀寫、傳送請求,就會阻塞後面的程式碼。像 Java 這種語言,採用的是多執行緒的方式來解決這個問題。
js 採用的非同步無阻塞的方式來解決這個問題,遇到非同步程式碼,會交給非同步模組。
下面是同步處理阻塞IO的示例:
const fs = require('fs'); console.log('開始寫入檔案'); // 如果要捕獲錯誤,使用 try...catch try { // 書寫嘗試執行的程式碼 const content = fs.readFileSync('./test111.txt'); console.log(content.toString()); } catch (err) { console.log(err); } console.log('結束寫入檔案');
非同步的方式:
const fs = require('fs'); console.log('開始寫入檔案'); fs.readFile('./test.txt',function(err,data){ if(err) throw err; console.log(data.toString()); }) console.log('結束寫入檔案');
通過上面的例子,我們可以看出,在 node中,由於採用非同步的方式來處理阻塞程式碼,所以在回到函式中,採用錯誤優先原則。
-
事件驅動
這裡其實就是兩個知識點:EventEmitter,事件驅動模式,EventEmitter 是 node 裡面為我們提供的一個模組,允許我們自定義事件。
之前我們學過事件,例如 click,mouseenter,mousemove 這些。
例如:
const EventEmitter = require('events').EventEmitter; const event = new EventEmitter(); // 手動定義一個事件 event.on('F71',function(){ console.log('你觸發了這個事件'); }) // 手動來觸發 setTimeout(function(){ event.emit('F71'); },2000);
nodejs 系統架構
nodejs 的架構如下圖:
該圖展示了整個 Node 的執行原理,從左到右,從上到下,整個 Node 被分為了 4 層,分別是應用層、V8 引擎層、Node API層和LIBUV層。
應用層:即 JavaScript 互動層,常見的就是 Node 的模組,比如http
,fs
。
V8 引擎層:即利用 V8 引擎來解析 JavaScript 語法,進而和下層 API 互動。
NodeAPI 層:為上層模組提供系統呼叫,一般是由 C 語言來實現,和作業系統進行互動。
LIBUV 層:是跨平臺的底層封裝,實現了事件迴圈、檔案操作等,是 Node 實現非同步的核心。
無論是 Linux 平臺還是 Windows 平臺,Node 內部都是通過執行緒池來完成非同步 I/O 操作的,而 LIBUV 針對不同平臺的差異性實現了統一呼叫。
當我們使用 Node.js 的時候,會在 JavaScript 中觸發一些命令呼叫方法,這些方法會被包裝成一個物件,放入執行緒池,然後前面的方法就返回了,繼續執行下面的 JavaScript 程式碼。
執行緒池中採用多執行緒的方式執行,執行完的物件放入事件迴圈佇列。
事件迴圈佇列採用類似 while(true) 這種迴圈的方式,不斷的檢視是否有事件,並且讀取是否包含回撥,由於前面回撥函式被包裝到物件中,這裡直接呼叫執行就可以了。
因此,Node.js 的單執行緒僅僅是指 JavaScript 執行在單執行緒中,而並非整個 Node.js 執行環境是單執行緒。