Express 實戰(二):Node.js 基礎

BigNerdCoding發表於2017-09-16

在上一篇文章中,我們簡單的介紹了 Node.js 。瞭解到它基於 JavaScript、天生非同步、擁有大量的第三方類庫。本文將會在之前的基礎上,對 Node.js 進行更深入的介紹。其中主要內容包括:

  • Node 的安裝
  • 如何使用第三方模組生態
  • 第三方模組的安裝
  • 一些簡單的使用示例
  • 開發過程中的一些建議和技巧

在此之前,我假設你已經掌握了 JavaScript 基礎知識並且熟悉一些基本的命令列操作。另外,不要臆想通過這一章就全面掌握 Node。但是如果你有心的話,可以去閱讀 Node.js 實戰

安裝Node

JavaScript 世界的一大特點就是它選擇性非常多,Node 的安裝也不例外。

可以在官方下載頁面找到各種版本的原始碼和安裝包檔案。建議你使用與自己作業系統對應的安裝包進行安裝。當然,你也可用使用 apt-get、Homebrew 等包管理器進行安裝,如果你係統有的話。具體詳見官方的包管理工具的安裝指南

如果你使用的是 Mac 或者 Linux 的話,那麼我極力推薦你使用 NVM 來安裝。Window 系統上的對應程式是 NVMW。這些版本管理工具,讓你可以在不同版本間進行自由切換。例如,你可以在嘗試新版本的特性時,同時在系統中保留一份穩定版。另外,NVM 無需系統管理許可權同時解除安裝也非常容易。而安裝過程也只需在終端執行一行命令。

現在,請在你係統中安裝好 Node。

執行你的第一個Node指令碼

安裝完成後,先動手寫個 "Hello World" 來檢驗一些。在新建的 helloworld.js 中加入一下程式碼:

console.log("Hello, World!");複製程式碼

程式碼中主要就是使用 console.log 來列印字串 "Hello,world!",相信對於前端程式設計師來說並不會感到陌生。下面我們使用 node helloworld.js 執行程式碼。如果一切正常的話,會出現如下輸出:

02_01
02_01

模組的使用

在大多數程式語言中,我們都會對程式碼進行拆分,然後在使用的時候將這些檔案引入其中。例如,C 和 C++ 中的 include,Python 的 import ,Ruby 和 PHP 中的 require。而另外一些語言,如 C# 是在編譯時完成跨檔案引用的。

很長一段時間內,JavaScript 官方並不支援模組機制。所以社群中有人就編寫了 RequireJS 這種工具來解決依賴項匯入的問題。但是,大多數時候還是通過 \

標籤來進行檔案匯入。而Node 通過實現名為 CommonJS 的標準模組,完美的解決了模組匯入問題。

模組系統部分主要有三大主要內容:內建模組的引入,第三方模組引入,個人私有模組引入。下面,將會對這些內容逐一介紹。

引入內建模組

Node 已經內建了很多實用模組,例如,檔案系統模組 fs,工具函式模組 util

在 Node 編寫的 Web 應用中,最常見的任務當屬 URL 解析了。瀏覽器通過特定的 URL 來請求伺服器上對應的資源。例如,訪問主頁、訪問關於頁面 的網路請求。這些 URL 都以字串的形式存在,我們需要對其進行解析然後獲取更多的資訊。這裡我們通過對 URL 進行解析來介紹如何引入內建模組。

內建的 url 模組中暴露的方法不多,不過其中有一個 parse 函式非常有用。它能從 URL 字串中提取到類似域名和路徑等有益資訊。

這裡我們使用 require 來實現模組匯入,該命令與之前提到的 Include、Import 的作用一致。通過將模組名作為引數,該命令就能成功的返回對應的模組。大多數情況下,該返回的物件是一個 object 物件,但有時也可能會是字串、數字、或者函式。下面是引入改模組的示例程式碼:

var url = require("url");   
var parsedURL = url.parse("http://www.example.com/profile?name=barry");  

console.log(parsedURL.protocol);  // "http:"
console.log(parsedURL.host);       // "www.example.com"
console.log(parsedURL.query);     // "name=barry複製程式碼

在上面的程式碼中,通過 require("url") 返回一個模組物件,然後就可以像使用其他物件一樣呼叫物件的方法。將這段程式碼儲存到 url-test.js 中並使執行 node url-test.js 命令,你就會看到協議名,域名、查詢條件。

另外,絕大多數時候我們在引入模組的時候會用一個同名的變數來接受返回的模組物件。例如,上面就使用 url 來介紹 require("url") 的返回值。當然,你完全可以不遵循上面的規則。如果你想的話,你也可以這麼幹:

var theURLModule = require("url");   
var parsedURL = theURLModule.parse("http://www.example.com/profile?name=barry");複製程式碼

儲存變數名和模組名一致只是一個統一風格增加可讀性的寬鬆約定,而不是什麼強制規範。

使用 npm 和 package.json 引入第三方模組

Node 的內建模組遠遠不能滿足日常開發需要,所以引入第三方模組是一個必須要掌握的技能。

首先,我們需要了解 package.json 檔案。所有的 Node 專案都單獨存放在一個資料夾中,而專案如果使用了第三方模組,那麼其中必定存在一個名為 package.json 的檔案。package.json 中的內容非常的簡單,一般其中定義了專案名稱、版本號、作者,已經專案的外部依賴項。

在新建的 Node 工程資料夾中,將下面的內容複製到 package.json 中。

{
  "name": "my-fun-project",   
  "author": "Evan Hahn",      
  "private": true,            
  "version": "0.2.0",         
  "dependencies": {}          
}複製程式碼

其實,在進行 Node 安裝時實際上還安裝了另一個程式:npm 。通常 npm 都被稱為 Node 包管理器,而這也是它最大的特色。假設,現在需要在應用中匯入一個小型的標準模版系統 Mustache。它能將模版字串轉化為真正的字串,請看程式碼:

// Returns "Hello, Nicholas Cage!"
Mustache.render("Hello, {{first}} {{last}}!", {
  first: "Nicholas",
  last: "Cage"
});

// Returns "Hello, Sheryl Sandberg!"
Mustache.render("Hello, {{first}} {{last}}!", {
  first: "Sheryl",
  last: "Sandberg"
});複製程式碼

現在,假設你想通過 Mustache 模組來編寫一個簡單的 Node 應用來歡迎 Nicolas Cage。

首先,在工程資料夾的根目錄裡執行 npm install mustache --save 。該命令會新建一個 node_modules 資料夾並將 Mustache 儲存到資料夾下。 --save 引數將會把該模組新增到 pakage.json 檔案中。此時 pakage.json 資料夾大致如下,其中 Mustache 會使用最新的版本。

{
  "name": "my-fun-project",
  "author": "Evan Hahn",
  "private": true,
  "version": "0.2.0",
  "dependencies": {
    "mustache": "^2.0.0"  #A
  }
}複製程式碼

如果你沒有使用 --save 選項的話,雖然也會建立 node_modules 資料夾將把 Mustache 模組儲存到同名子目錄下,但是 pakage.json 將不會發生任何變化。這裡之所以將這些依賴關係儲存到 package.json 是為了方便其他開發者在得到工程後直接使用 npm install 完成所有依賴項的安裝。另一個原因是 Node 專案在進行程式碼管理時通常都會忽略 node_modules 資料夾而只保留 package.json。

安裝完成後接下來就是使用了:

var Mustache = require("mustache");  
var result = Mustache.render("Hi, {{first}} {{last}}!", {
  first: "Nicolas",
  last: "Cage"
});
console.log(result);複製程式碼

儲存程式碼到 mustache-test.js 中並執行 node mustache-test.js 命令。然後你將會看見 Hi,Nicolas Cage! 。

就是這樣簡單,這些依賴項安裝完成後,你可以像使用內建模組一樣進行呼叫。node_modules 中模組引入的工作直接交給 Node 就行了,你無需擔心。

當然你可以手動新增工程依賴項,並且你還可以指定依賴項的版本。

npm init
除了安裝依賴項之外,npm 還能完成其他任務。例如,自動生成 package.json 而不是通過手動編輯的方式。在一個新工程的資料夾中可以通過 npm init 來配置工程名、作者、版本等資訊,然後 npm 就會自定生成對應的 package.json 檔案。這種自動化過程可以節約開發者的時間。

實現私有模組

前面都是介紹如何使用他人開發好的模組,接下來你將會學到如何去開發一個私有模組。假設現在需要隨機返回 0 ~ 100 之間的整數。在不引入其他模組的情況下,程式碼大致如下:

var MAX = 100;
function randomInteger()  {
    return Math.floor( (Math.random() * MAX) );
}複製程式碼

這可能與你在瀏覽器環境下程式碼差不多,並沒有什麼特別之處。但是在 Node 中,我們還需要暴露一個變數給外部使用。這樣當其他程式在通過 require 進行引入的時候就能獲得該變數。此例中,我們暴露函式 randomInteger 並將程式碼儲存到 random-integer.js 檔案中。

var MAX = 100;
function randomInteger()  {
    return Math.floor( (Math.random() * MAX) );
}

module.exports = randomInteger;複製程式碼

最後一行程式碼對於 Node 初學者來說可能感覺有點陌生。每個模組只能暴露一個變數,而且必須通過 module.exports 設定。本例中只暴露了一個函式變數,所以 MAX 就作為模組私有變數無法被其他檔案所訪問。

module.exports 可以暴露任何變數,雖然本例中是一個函式,但是通常都會是一個物件。當然,你可以暴露字串或者陣列。

接下來我們就來使用一下這個新模組。在 random-integer.js 同一目錄下,新建一個 print-three-random-integers.js 並複製下面的程式碼:

var randomInt = require("./random-integer");  #A
console.log(randomInt());  // 12
console.log(randomInt());  // 77
console.log(randomInt());  // 8複製程式碼

除了需要通過點語法指定相對路徑之外,其餘部分與前面幾乎一摸一樣。通過 node print-three-random-integers.js 命令,我們可以檢查程式的執行效果。不出意外的話,將會有三個 0 ~ 100 之間的隨機數會被列印出來。

如果你嘗試執行 node random-integer.js 的話,你還發現並沒有任何事情發生。雖然,我們暴露了模組中的函式,但是改函式並不會執行更不會列印任何輸出。

注意,這裡只涉及了私有模組在工程中的使用。如果你希望將自己的模組釋出出去供其他人使用的話,可以去我的個人站點檢視相關內容。

以上部分就是 Node 模組系統的簡單入門。

Node:非同步的世界

在第一章中,我用 “烤鬆餅” 的例子簡單的介紹了 Node 中的非同步特性。其中的關鍵點就是,你無法同時做兩件事哪怕它們是同時發生的。雖然,在烘焙過程中我可以健身,但是,烤箱畢竟只是個外部事物。

02_02
02_02

Node 的非同步工作原理與此類似,例如,你通過瀏覽器請求 Node 伺服器上的一張小貓圖片。因為該圖片資源太大,所以在進行磁碟讀寫的時候你可以抽身去處理其他事情。此時,這個磁碟就相當於一個外部資源,我們可以直接處理第二個請求而無需掛起等待費時操作結束。

Express 中主要有兩個外部資源:

  1. 涉及檔案系統。例如,磁碟檔案的讀寫。
  2. 涉及網路處理。例如,接受請求、傳送響應。

在 Node 程式碼中,這些非同步都是通過回撥進行處理的。其工作原理和在 Web 頁面傳送 AJAX 請求一樣。在傳送請求時你會附帶一個回撥函式,當請求處理完成後你的回撥將會被執行。

例如,現在你正在硬碟上讀取檔案 myfile.txt 。當讀取結束後,你希望能夠列印出其中字母 X 出現的次數,程式碼如下:

var fs = require("fs");  

var options = { encoding: "utf-8" };                      
fs.readFile("myfile.txt", options, function(err, data) {  
  if (err) {                                
    console.error("Error reading file!");   
    return;                                 
  }                                        

  console.log(data.match(/x/gi).length + " letter X's");  
});複製程式碼

下面我們一步步解釋這些程式碼:

首先,我們匯入 Node 自帶的檔案系統模組。該模組主要處理檔案相關內容,其中大多數都是檔案讀寫功能。本例使用的其中的 readFile 方法。

接下來,我們需要設定 fs.readFile 方法中的引數,第一個是檔名,第二個就是會回撥函式。並且在讀取結束後執行回撥函式。

在 Node 中大多數回撥函式都會設定錯誤資訊 error 作為第一個引數。正常情況下該引數等於 null ,如果出現錯誤則該引數會儲存錯誤資訊。雖然有時候這些錯誤資訊並不會導致程式終止執行,但是多數情形下我們都需要對錯誤做出響應,例如,丟擲異常並跳出回撥函式。這也是 Node 中最常見的回撥實踐。

最後,當一切正常時我們使用正規表示式匹配字母 X 並列印其數量。

下面我們就來做個測試。這裡,我們在上面程式碼的結束加上一段,那麼會發生什麼事情呢?

var fs = require("fs");  

var options = { encoding: "utf-8" };                      
fs.readFile("myfile.txt", options, function(err, data) {  
  if (err) {                                
    console.error("Error reading file!");   
    return;                                 
  }                                        

  console.log(data.match(/x/gi).length + " letter X's");  
});

console.log("Hello World!");複製程式碼

非同步檔案讀取時非同步操作,所以這裡先列印出來的是 " Hello world! ",然後才是非同步函式中的列印操作。

這就是非同步模式強大的地方。當一個外部裝置在處理費時操作時,你可以繼續執行其他程式碼。在 Web 應用中這意味著相同的時間可以處理更多的請求。

注意:如果你想了解更多 JavaScript 非同步的內容的話,你可以去油管上檢視這個視訊。視訊中的講解同時適用於 Node 和瀏覽器環境。

用 Node 構建 Web 服務:http 模組

只有理解了上面那些概念,你才能更好的掌握 Node 內建的 HTTP 模組。而該模組對 Express 框架來說又是最重要的模組之一。Node 和 Express 能夠構建 Web 服務正是依賴於這個模組中的功能。

Node 的 HTTP 模組有很多特性(比如,向其他伺服器傳送網路請求),不過我們將要使用的是其中一個名為 http.createServer 的方法。該方法通過其回撥函式來處理每一次的網路請求,並且進行響應。下面程式碼中我們將所有的響應都設定為了 "hello world" (可以儲存到 myserver.js 中)。

var http = require("http");           

function requestHandler(request, response) {             
  console.log("In comes a request to: " + request.url);  
  response.end("Hello, world!");                         
}                                                        

var server = http.createServer(requestHandler);  
server.listen(3000);複製程式碼

上面的程式碼由 4 個部分構成。

首先,我們引入 HTTP 模組並將其儲存到變數 http 中。這與之前 URL 模組的操作一致。

接著,定義了一個請求處理函式 requestHandler 。教程中的幾乎所有的程式碼要麼是請求處理函式要麼是呼叫處理函式。該函式有兩個引數,request 表示請求物件,而 response 則表示響應物件。request 中包含 URL 路徑、user-agent 等資訊。而通過呼叫 response 物件方法 Node 會將響應資訊打包好併傳送給請求者。

餘下的程式碼則是指定內建的 HTTP 服務在請求是執行的處理函式以及服務監聽的埠號。

對於 HTTPS 來說,我們則可以使用自帶的 HTTPS 模組。除了需要配置 SSL 證照,其餘的過程都一樣。如果你瞭解 HTTPS 的話那麼後期從 HTTP 切換到 HTTPS 兩分鐘就能搞定。即使你不瞭解,也不必太過擔心。

如果你將程式碼儲存到 myserver.js 並執行 node myserver.js 拉起服務。那麼,此時你在瀏覽器中訪問 http://localhost:3000 ,你就會看到:

02_03
02_03

你可能也注意到了,每當你發起請求的時候終端控制檯都會列印一些資訊。當你嘗試訪問不同 URL 時,雖然控制檯列印的資訊不同但是得到的響應卻都是 “Hello, world!”。控制檯列印的資訊類似於:

02_04
02_04

請注意上面列印的 URL 資訊中並不包含 localhost:3000。雖然看起來顯得不那麼直觀,但是反過來這也是對的。畢竟使用相對路徑,我們無需修改就能在任何電腦上部署 Node 應用。

而 URL 解析的程式碼大致如下:

function requestHandler(req, res) {
    if (req.url === "/") {
        res.end("Welcome to the homepage!");
    } else if (req.url === "/about") {
        res.end("Welcome to the about page!");
    } else {
        res.end("Error! File not found.");
    }
}複製程式碼

所有的請求 URL 都可以在這個函式裡面完成處理。這樣做對於簡單的應用來說確實非常簡單,但是當應用規模變大之後該函式就會變的臃腫不利於維護。這也是 Express 框架出現的重要原因。

##總結
本文主要內容:

  • Node 的安裝
  • 模組系統的使用
  • package.json 檔案的介紹
  • 通過 package.json 安裝第三放模組依賴項
  • Node 中的非同步程式設計概念。
  • 簡單 HTTP 服務應用的建立。

原文地址

相關文章