最近在整理筆記,翻出了以前鋼接觸node時的一些學習代碼。現在再看當時的自己,真是天真的可笑。不過所謂人生,也許正是成長和領悟的過程吧。這些筆記就當作紀念,勿忘初心吧。
1. 取命令行參數
當我們在終端中使用node執行程序的時候,我們可以通過一個process的全局變量來了解命令行的詳細情況。
假設我們需要一個終端程序用來計算一系列數字的和。如果你之前寫過bash,就知道我們應該寫 一個類似add.sh的程序,然后使用bash add.sh 1 2 3來計算1,2和3的合。在bash中,我們可以輕松的取出該命令行的后續參數。
使用node可以實現同樣的命令行函數。我們可以通過process全局變量來了解命令行的詳情。process.argv則包含了命令行輸入的所有參數。如果我們將如下語句寫入一個add.js 中:
console.log(process.argv);
當我們執行node add.js 1 2 3的時候,該程序便會打印一個包含所有輸入命令行參數的數組:
[ 'node', '/path/to/your/program.js', '1', '2', '3' ]
于是我們就可以輕松取出傳入該程序需要的參數,然后求合了:
var sum = 0;
for (var i = 2; i < process.argv.length; i++) {*
sum += Number(process.argv[i]);*
}
console.log(sum);
這里Number將string顯式轉換為數字,否則+會變成string連接。
2. 同步文件操作
這里同步操作指的是等到文件讀取完了再執行后續指令,后面我們還要看到異步操作。
在node中進行文件操作都需要fs模塊,該模塊有一個方法readFileSync用來同步讀取文件。注意該方法的返回值是一個特殊的Buffer對象,并不是直接的string。這里多加一層Buffer抽象主要是為了解決文件的編碼問題,畢竟不是所有的文件都是可以轉換為string的文本。
但如果我們只讀取文本文件,則可以使用buffer.toString()將其轉換為string,然后就可以方便的使用默認的string方法進行操作了。
下面我們來寫一個簡單的命令行程序來計算文本文件中的換行符的數量。使用方法為node newline.js path-to-file
。
命令行的最后一個參數是文件的路徑,結合前面的知識,我們知道取出這個路徑的可以利用process.argv:
var path = process.argv[2];
然后就可以讀取文件了:
var content = fs.readFileSync(path);
再轉換成string,再使用string的split方法按換行符分割為array。這里需要注意,由于最后一行是沒有換行的,所以得到的數組長度必須減一。
var numberOfNewline = content.toString().split('\n').length - 1;
3. 異步文件操作
如果只用同步方法,那node其實和其他后端技術是差不多的。但node最吸引人的地方就是天生的異步特性,也就是說在文件讀取這樣耗時較長的操作時,我們不需到等到讀取完成就可以先做其他事情;文件操作完成后,node會用某種方式通知我們,然后再針對操作結果執行下一步指令。node的通知方式可以有多種,但一開始我們先從最老,也是最基本的callback開始吧。
跟上一個例子類似,異步文件讀取依然需要從process.argv中取得命令行傳入的文件路徑。不同的時我們這里使用fs.readFile來處理讀文件操作。上一個例子我們直接將path傳入readFileSync,然后等文件讀完了再做后續操作。異步的readFile則不同,因為不需要等待就能繼續執行,因此需要先定義一個通知讀取完成,并處理讀取結果的callback:
fs.readFile(path, options, callback)
這里的options可以指定文件的編碼方式,如果這里傳入utf8就不再需要前面的toString方法了。這里的callback是一個標準的js函數,接受兩個參數err和data(node的慣例一般都是err在前,數據再后)。于是整個程序變成:
var fs = require('fs');
fs.readFile(process.argv[2], function(err, data) {
if (err) throw err;
var nbOfNewline = data.toString().split('\n').length - 1;
console.log(nbOfNewline);
});
4. 文件夾操作
node也提供了針對文件夾的操作。這里以讀取文件夾為例,演示異步情況下得讀取情況。
fs.readdir是fs模塊中提供的用于讀取目錄的異步方法,接受目錄路徑和callback函數為參數。
fs.readdir(path, callback)
形式和readFile類似,這里的callback依然接受兩個參數,一個err和一個文件夾內所有文件名的array。
fs.readdir(dir, function(err, files) {
if (err) throw err;
//對files進行操作
});
5. 模塊化
node實現模塊化的方式CommonJS已經漸漸取代AMD成為標準的模塊化形式了,連ES6中的原生模塊化語法也大量參考了這種實現方法。
這種實現方式的基本思想是將一個模塊定義在一個函數中,然后將該函數暴露給module.exports對象,隨后就可以在需要引用該模塊的地方使用require引入了。
在寫模塊的時候需要注意一些node的慣例寫法。比如在寫foo(callback)形式的模塊時,錯誤處理需要符合以下形式:
function bar(callback) {
foo(function (err, data) {
//出錯,提前返回callback
if (err) return callback(err);
// 無錯誤,執行callback
callback(null, data);
});
};
注意callback的執行方式,出錯時提前返回callback和錯誤信息,無錯誤則callback的第一參數為null,第二個為實際數據。這也符合我們前面常見的readDir(err, files)的形式,定義相同的callback:
function callback(err, files) {
if (err) throw err;
//no error, files processing
}
6. HTTP請求
說了半天好像完全沒有提到node主要是用來處理后臺服務請求的,這里我們就先來看最基本的http請求。
node中處理所有的http請求都需要引入http模塊。如果你稍微了解一點http的話,會發現node的http方法還是很直觀的。下面讓我們從get這個最基礎的請求講起。
http.get(url, callback)用來處理get請求,第一個參數是請求的url,第二個是請求的詳細處理函數。按常規來說callback應該有兩個參數:err和數據,但http的callback比較特殊,只有一個response參數。get在node中是作為stream來處理的,也就是說node不是整個讀取完所有數據在進行處理,而是按塊(chunk)讀取并傳給response對象的。于是我們不能簡單的只用一個參數來引用所有數據,而必須對數據讀取的事情進行監聽,以便在每個塊讀取完成后及時進行處理。
最重要的三個stream事件是data,error和end,分別表示一塊數據準備好了,讀取出錯和整個數據讀取完成。注冊監聽事件和js中監聽DOM事件類似:
response.on(‘data’, callback(data) {});
于是一個最基礎的http get處理可以寫成:
http.get(url, function(response) {
response.setEncoding('utf8');
response.on('data', console.log);
response.on('error', console.error);
});
這里用setEncoding(‘utf8’)來制定數據編碼,方便console演示。
7. HTTP數據集合處理
你可能會說,我不想依次處理chunk,有沒有辦法等到數據集合全部讀取完成后,再一次性進行處理呢?
當然可以,一個簡單的做法就是監聽end事件,在數據讀取完成觸發end后再對前面的接收的數據塊依次處理。還有更簡潔的做法,需要一個外部模塊來幫助我們完成這個任務。
response.pipe可以不停歇地將數據寫入指定的目標,我們使用一個外部模塊來作為接受目標來處理這些數據。一般比較流行的有bl和concat-stream兩個模塊,這里以bl為例:
var bl = require('bl');
var http = require('http');
http.get(process.argv[2], function(response) {
response.pipe(bl(function(err, data) {
if (err) console.error(err);
var str = data.toString();
console.log(str.length);
console.log(str);
}));
});
只要簡單使用 response.pipe(bl(function(err, data) {})就可以完成數據集合的處理了。