
一、概述
Node.js? is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
Node.js是一個基于Google Chrome的V8引擎的JavaScript運行環境,它允許使用JavaScript語言編寫服務器端代碼。Node.js具有事件驅動和I/O非阻塞兩大特點,輕量而高效。
二、安裝與運行
從官網下載最新版本的Node.js,直接安裝。安裝時選擇全部組件,并添加到環境變量Path中。
運行命令:node -v
查看是否安裝成功。
運行命令:node
進入到node.js的交互環境,可以輸入任意JavaScript語句,并查看運行結果。
運行命令:node <*.js>
直接執行js文件里的內容。
運行命令:node --use_strict <*.js>
為js文件開啟嚴格模式。
安裝Node.js的時候,會自動安裝包管理工具npm。
運行命令:npm -v
查看npm的版本。
命令行模式會一次性執行js文件,中間沒有交互;交互模式則是每一行單獨執行,可以進行交互。
如果不想使用集成的IDE,可以使用VS Code編輯器進行Node.js的開發和調試。具體操作和配置可以參照VS Code的官網教程。
Node.js Applications with VS Code
三、模塊
在Node.js環境中,一個js文件就稱之為一個模塊。使用模塊提高了代碼的可維護性以及可重用性,還避免了函數名和變量名沖突。
當在模塊中定義一個對象(對象、函數、數組等)時,可以輸出這個對象以供其他模塊使用:
module.exports = object_name;
其他模塊要想使用這個對象,可以在模塊中引入該對象:
var variable_name = require('/path/to/the/module/module_name');
Tips:如果不指定路徑,Node會按照內置模塊、全局模塊、當前模塊的順序進行查找。
1、基本模塊
在瀏覽器中,JavaScript有一個全局對象window;而在Node.js環境中,也有一個全局對象global。由于JavaScript代碼既能在瀏覽器執行,也能在Node環境下執行,可以通過全局對象名稱來判斷JavaScript代碼是在哪個環境下執行的。
process也是Node.js提供的一個對象,表示當前Node.js進程,進程本身的事件由process來處理。
2、常用內置模塊
fs模塊是文件系統模塊,負責文件的讀寫操作,提供了同步和異步兩種方法。大部分作為服務端的代碼,必須使用異步代碼。但是在服務器啟動時讀取配置文件,或結束時寫入狀態等操作,因為這些代碼只執行一次,所以可以使用同步方法。
讀文本文件:
var fs = require('fs');
fs.readFile('input.txt', 'utf-8', function (err, data) {
if (err) {
console.log(err);
} else {
console.log(data);
}
});
其中,readFile函數的第一個參數是文件名,第二個參數是文件編碼,第三個參數是一個回調函數。回調函數的第一個參數代表錯誤信息,第二個參數代表返回結果。
讀二進制文件:
var fs = require('fs');
fs.readFile('input.png', function (err, data) {
if (err) {
console.log(err);
} else {
console.log(data);
console.log(data.length + ' bytes');
}
});
其中,不需要文件編碼,并且回調函數的data參數將返回一個Buffer對象。Buffer對象是一個包含零個或任意個字節的數組。
寫文件:
var fs = require('fs');
var data = 'Hello world';
fs.writeFile('output.txt', data, function (err) {
if (err) {
console.log(err);
} else {
console.log('success');
}
});
其中,writeFile函數的第一個參數是文件名,第二個參數是要寫入的數據,第三個參數是回調函數。
讀取文件的相關信息:
fs.stat('input.txt', function (err, stat) {
...
});
stream是一個只能在服務端使用的模塊,用來支持流數據結構,流中的數據是有序的。流是一個對象,使用時只需關注流的事件即可。data事件表示流的數據可以讀取,end事件表示流沒有數據可以讀取,error事件表示出現錯誤。
讀文件:
var fs = require('fs');
var rs = fs.createReadStream('input.txt', 'utf-8');
rs.on('data', function (chunk) {
console.log('DATA:');
console.log(chunk);
});
rs.on('end', function () {
console.log('END');
});
rs.on('error', function (err) {
console.log('ERROR: ' + err);
});
寫文件:
var fs = require('fs');
var ws1 = fs.createWriteStream('output1.txt', 'utf-8');
ws1.write('使用Stream寫入文本數據...\n');
ws1.write('END.');
ws1.end();
var ws2 = fs.createWriteStream('output2.txt');
ws2.write(new Buffer('使用Stream寫入二進制數據...\n', 'utf-8'));
ws2.write(new Buffer('END.', 'utf-8'));
ws2.end();
可以使用管道操作pipe將讀取流和寫入流串聯起來:
var fs = require('fs');
var rs = fs.createReadStream('input.txt');
var ws = fs.createWriteStream('output.txt');
rs.pipe(ws);
默認情況下,當讀取流的數據讀取完畢,會觸發end事件,并自動關閉寫入流。如果不想關閉,需要傳遞額外的參數。
readable.pipe(writable, { end: false });
http模塊是web應用最常用的一個模塊,提供了request和response對象。request對象封裝HTTP請求,調用request對象的屬性和方法,可以獲取HTTP請求的所有信息;response對象封裝HTTP響應,調用response對象的方法,可以把HTTP響應返回給瀏覽器。
var http = require('http');
var server = http.createServer(function (request, response) {
// 獲取HTTP請求的method和url
console.log(request.method + ':' + request.url);
// 將HTTP響應的內容寫入response
response.writeHead(200, {'Content-Type': 'text/html'});
response.end('<h1>Hello world!</h1>');
});
// 監聽8080端口
server.listen(8080);
url模塊可以用來將URL字符串解析成一個Url對象,從而獲取想要的信息。
var url = require('url');
console.log(url.parse('http://user:pass@host.com:8080/path/to/file?query=string#hash'));
path模塊可以用來處理本地文件目錄。
var path = require('path');
// 解析當前目錄
var workDir = path.resolve('.');
// 組合完整的文件路徑
var filePath = path.join(workDir, 'pub', 'index.html');
crypto模塊提供通用的加密和哈希算法。
MD5哈希算法用來給任意數據一個簽名,通常用一個十六進制字符串表示:
const crypto = require('crypto');
const hash = crypto.createHash('md5');
hash.update('Hello, world!');
console.log(hash.digest('hex'));
Hmac哈希算法可以利用MD5等算法,另外還需要一個密鑰:
const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', 'secret-key');
hmac.update('Hello, world!');
console.log(hmac.digest('hex'));
AES是一種常用的對稱加密算法,加密與解密使用同一個密鑰。crypto模塊提供了AES支持,但需要自行封裝。DiffieHellman算法是一種密鑰交換協議,可以讓雙方自行協商一個密鑰。crypto模塊還可以處理數字證書。
四、Web開發
使用Node.js開發Web服務器端,有幾點好處:
- 前后端統一使用JavaScript,不需要切換語言
- 異步處理大大提高了運行速度
針對Node.js,出現了很多后端相關的Web框架、ORM框架、模板引擎、測試框架、構建工具等。
1、Web框架Express/koa
Express是第一代最流行的Web服務端框架,它對Node.js的http進行了封裝。雖然Express的API非常簡單,但是由于是基于ES5,要實現異步,只能使用回調。如果異步嵌套層次過多,代碼很難理解。
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
koa1基于ES6,使用generator實現異步。雖然使用generator在寫法上比回調簡單,但其本意并不是用于異步。真正設計用來實現異步的是Promise,但Promise的寫法過于復雜。
var koa = require('koa');
var app = koa();
app.use('/test', function *() {
yield doReadFile1();
var data = yield doReadFile2();
this.body = data;
});
app.listen(3000);
koa2基于ES7,使用Promise和關鍵字async
配合實現異步,同時兼容generator的寫法。
app.use(async (ctx, next) => {
await next();
var data = await doReadFile();
ctx.response.type = 'text/plain';
ctx.response.body = data;
});
2、ORM框架Sequelize
ORM技術是將關系型數據庫的表結構映射到對象上,Sequelize返回Promise對象,可以更好地進行異步處理。
ES6:
Pet.findAll()
.then(function (pets) {
for (let pet in pets) {
console.log(`${pet.id}: ${pet.name}`);
}
}).catch(function (err) {
// error
});
ES7:
(async () => {
var pets = await Pet.findAll();
})();
要想使用Sequelize操作數據庫,首先需要創建一個Sequelize實例。然后定義數據模型Model,使用Sequelize映射數據庫表。這樣就可以調用實例的相應方法來對數據庫進行操作。
Sequelize操作數據庫的一般步驟:
- 通過Model對象的findAll()方法獲取實例
- 如果要更新實例,先對實例屬性進行賦值,然后調用save()方法
- 如果要刪除實例,直接調用destroy()方法
3、模板引擎Jade/Pug
Node.js默認使用Jade作為模板引擎(現改名為Pug)。Jade是Node.js的一個模塊,jade文件可以被預編譯為.js文件,也可以被編譯為目標html代碼。
4、測試框架Mocha
Mocha是JavaScript的單元測試框架,既可以在瀏覽器環境運行,也可以在Node.js的環境運行。
特點:
- 既可以測試簡單的JavaScript函數,又可以測試異步代碼
- 既可以自動運行所有測試,也可以只運行特定的測試
- 可以支持before、after、beforeEach和afterEach來編寫初始化代碼
5、構建工具Webpack
6、WebSocket協議
WebSocket利用HTTP協議建立連接,在瀏覽器和服務器之間建立雙向通信的通道,服務器可以主動向瀏覽器發送消息,而不需要通過瀏覽器發送請求。
首先,WebSocket連接必須由瀏覽器發起,因為請求協議是一個標準的HTTP請求。
GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13
與普通HTTP請求的區別:
- GET請求的地址以
ws://
開頭 - 請求頭
Upgrade: websocket
和Connection: Upgrade
表示連接將被轉換為WebSocket連接 -
Sec-WebSocket-Key
用于標識這個連接 -
Sec-WebSocket-Version
指定WebSocket的協議版本
然后,如果服務器接受請求,會返回響應:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string
其中,響應碼101表示將切換協議,切換后的協議通過Upgrade來指定。
在Node.js中,最常用的WebSocket模塊是ws。創建一個WebSocket的服務器實例:
const WebSocket = require('ws');
const WebSocketServer = WebSocket.Server;
const wss = new WebSocketServer({
port: 3000
});
如果有WebSocket請求接入,wss對象可以響應connection事件來處理這個WebSocket。對于每個WebSocket連接,都要對它綁定某些事件方法來處理不同的事件。
參考文章:
廖雪峰:Node.js教程