《Node.js開發指南》讀書筆記

Creabine發表於2016-08-16

繼續學學node.js。翻開書首先被驚到=。=:作者BYVoid是清華大學2010級本科……同樣是2010級本科,我真是無語凝噎,大學浪費了好多時間。不過過去的已經過去了,接下來好好努力提高才好,加油。

在這之前,我已經看了一些其他的資料,對Node.js有了一些基本的瞭解。寫了一篇部落格 ,之後看了一本只有42頁的入門書《Node入門》,這本書雖然很短,但是卻是一本很不錯的書。

接下來開始看這本《Node.js開發指南》,準備之後再看看《深入淺出Node.js》逐步來學習node。

下面開始筆記:

第一章 Node.js簡介

npm:node package manager

非同步I/O

事件驅動
Node.js程式在同一時刻智慧處理一個事件,完成後立即進入事件迴圈檢查並處理後面的事件。

第二章 安裝和配置Node.js

Microsoft Windows系統上安裝Node.js:官網下載安裝即可。這樣安裝的node.js還自帶npm,可以直接使用。

第三章 Node.js快速入門

1.執行node.js程式的基本方法就是執行node script.js 其中script.js是指令碼的檔名。

2.還可以使用node -e"要執行的js程式碼" 來執行js。

3.執行無引數的node將會啟動一個JavaScript的互動式shell

4.使用node.js的http模組可以輕鬆建立伺服器

小技巧——使用supervisor
開發時會發現,修改node.js檔案後,必須重啟node.js伺服器才生效。這是因為node.js只有在第一次引用某部分時才會去解析指令碼檔案,以後都會直接訪問記憶體,避免重複載入。node.js這種設計有利於提高效能,卻不利於開發除錯。
supervisor可以幫助時實現這個功能,它會見識你對程式碼的改動並自動重啟node.js。使用方法很簡單:首先npm install -g supervisor 安裝;接下來使用supervisor命令啟動檔案,例如:supervisor index.js
這樣就可以用supervisor這個小工具解決開發中的除錯問題。

非同步式I/O(非阻塞I/O)與事件式程式設計

為了處理非同步 I/O,執行緒必須有事件迴圈,不斷地檢查有沒有未處理的事件,依次予
以處理。

回撥函式

//readfile.js

var fs = require('fs');
fs.readFile('file.text','UTF-8',function(err,data){
    if(err){
        console.error(err);
    }else{
        console.log(data);
    }
});
console.log('end');

執行結果如下:
end
Content of the file.text.
由於是非同步所以先輸出end,當讀取到檔案之後觸發匿名回撥函式,再輸出data

如果不用匿名函式,也可以將其定義在外邊,例如:

//readfilecallback.js
functon readFileCallBack(err,data){
    if(err){
        console.error(err);
    }else{
        console.log(data);
    }
}

var fs = require('fs');
fs.readFile('file.txt','utf-8',readFileCallBack);
console.log('end');

Node.js也提供了同步檔案的API:

//readFilesync.js

var fs = require('fs');
var data = fa.readFileSync('file.text','utf-8');
console.log(data);
console.log('end');

執行結果:
Content of the file.
end
這次是同步方式讀取檔案,所以按順序輸出了data和end

事件

Node.js所有的非同步I/O操作在完成時候、都會傳送一個事件到事件佇列。在開發者看來,事件由EventEmitter物件提供。下例來說明該物件的用法:

//event.js

var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();

event.on('some_event',function(){
    console.log('some_event occured.');
});

setTimeout(function(){
    event.emit('some_event');
},1000);

執行上例,1秒後輸出some_event occured。原理是event物件註冊了事件some_event 的第一個監聽器,然後我們在1000毫秒後向event物件傳送事件some_event,此時會呼叫some_event的監聽器。

Node.js程式在執行時會不斷檢查是否有活動的、可供檢測了事件監聽器,值到檢測不到時才退出事件迴圈,程式結束。Node.js的時間迴圈對開發者不可見。

模組和包

模組(Module)和包(Package)是Node.js最重要的支柱。我們經常把 Node.js 的模組和包相提並論,因為模組和包是沒有本質區別的,兩個概念也時常混用。如果要辨析,那麼可以把包理解成是實現了某個功能模組的集合,用於釋出和維護。對使用者來說,模組和包的區別是透明的,因此經常不作區分。

模組是 Node.js 應用程式的基本組成部分,檔案和模組是一一對應的。換言之,一個Node.js 檔案就是一個模組,這個檔案可能是 JavaScript 程式碼、JSON 或者編譯過的C/C++ 擴充套件。
Node.js 提供了 exports 和 require 兩個物件,其中 exports 是模組公開的介面,require 用於從外部獲取一個模組的介面,即所獲取模組的 exports 物件。

例子:
module.js檔案:

//module.js

var name;
//設定匯出物件的方法
exports.setName = function(thyName){
    name = thyName;
};

exports.sayHello = function(){
    console.log('Hello' + name);
}

/*
當你想要直接匯出sayHello物件的時候,也可以這樣:
module.exports = Hello;
這樣的話,使用var a = require('./module');得到的就是Hello物件了。
否則要使用:var a = require('./module').sayHello;

*/

getmodule.js檔案:

//getmodule.js

var myModule = require('./module');

//設定名字為Creabine
myModule.setName('Creabine');
//設定名字為Creabine_1
myModule.setName('Creabine_1');

myModule.sayHello();

執行node getmodule.je,結果是:
Hello Creabine_1
這裡要注意,輸出的是Creabine_1。因為require不會重複載入模組,只會載入一次,所以設定兩次名字的時候,Creabine_1會覆蓋掉之前的Creabine。最終輸出的是最後設定的名字

事實上,exports本身僅僅是一個普通的空物件,即{ },它專門用來宣告介面,本質上是通過它為模組閉包的內部建立了一個有限的訪問介面。因為它沒有任何特殊的地方,所以可以用其他東西來代替。可以使用:module.exports來覆蓋exports。
不可以通過對 exports 直接賦值代替對 module.exports 賦值。exports 實際上只是一個和 module.exports 指向同一個物件的變數,它本身會在模組執行結束後釋放,但 module 不會,因此只能通過指定module.exports 來改變訪問介面。

Node.js 的包是一個目錄,其中包含一個 JSON 格式的包說明檔案 package.json。

Node.js 在呼叫某個包時,會首先檢查包中 package.json 檔案的 main 欄位,將其作為包的介面模組,如果 package.json 或 main 欄位不存在,會嘗試尋找 index.js 或 index.node作為包的介面。

package.json檔案包含的內容:

  • name:包的名稱
  • description:包的簡要描述
  • version:版本號
  • keywords:關鍵字陣列
  • maintainers:維護者陣列
  • contributors:貢獻者陣列
  • bugs:提交bug地址
  • licenses:許可證陣列
  • repositories:倉庫託管地址陣列
  • dependencies:包的依賴。
  • main:包的介面模組,如果沒有則預設是index.js或index.node

npm

使用npm安裝包的命令: npm [install/i] [package_name]
安裝一個模組的時候還會自動解析其依賴的模組並安裝。

在使用 npm 安裝包的時候,有兩種模式:本地模式和全域性模式。預設情況下我們使用 npm
install命令就是採用本地模式,即把包安裝到當前目錄的 node_modules 子目錄下Node.js的 require 在載入模組時會嘗試搜尋 node_modules 子目錄,因此使用 npm 本地模式安裝
的包可以直接被引用。

npm 還有另一種不同的安裝模式被成為全域性模式,使用方法為:npm [install/i] -g [package_name]

這裡寫圖片描述

使用 npm init 命令初始化一個包,生成一個符合規範的package.json。

可以使用npm adduser新增使用者,輸入賬號密碼郵箱,即可建立賬號。完成後可以使用npm whoami檢測是否已經取得了賬號。接下來在package.json所在目錄下執行npm publish,就可以釋出自己製作的包了。可以在http://search.npmjs.org/ 找到自己釋出的包,並且在其他計算機上使用npm install [package_name] 來安裝他。如果要更新包,只要修改package.json檔案中的version欄位,然後再npm publish即可。還可以使用npm unpublish來取消釋出。

除錯

命令列除錯

遠端除錯
在命令列下使用:

//使用這兩個語句之一,可以開啟除錯伺服器。預設埠是5858
node --debug[=port] script.js
node --debug-brk[=port] script.js

Eclipse除錯Node.js
詳見書P57

使用node-inspector除錯Node.js
大部分基於 Node.js 的應用都是執行在瀏覽器中的,例如強大的除錯工具 node-inspector。node-inspector 是一個完全基於 Node.js 的開源線上除錯工具,提供了強大的除錯功能和友好的使用者介面,它的使用方法十分簡便。

首先,使用 npm install -g node-inspector 命令安裝 node-inspector,然後在終
端中通過 node –debug-brk=5858 debug.js 命令連線你要除錯的指令碼的除錯伺服器,
啟動 node-inspector:

第四章 Node.js核心模組

全域性物件
JavaScript 中有一個特殊的物件,稱為全域性物件(Global Object),它及其所有屬性都可
以在程式的任何地方訪問,即全域性變數。在瀏覽器 JavaScript 中,通常 window 是全域性物件,而 Node.js 中的全域性物件是 global,所有全域性變數(除了 global 本身以外)都是 global物件的屬性。
我們在 Node.js 中能夠直接訪問到物件通常都是 global 的屬性,如 console、process
等,下面逐一介紹。

global 最根本的作用是作為全域性變數的宿主。

process

console

常用工具util

事件驅動events
events 是 Node.js 最重要的模組,沒有“之一”,原因是 Node.js 本身架構就是事件式
的,而它提供了唯一的介面,所以堪稱 Node.js 事件程式設計的基石。events 模組不僅用於使用者程式碼與 Node.js 下層事件迴圈的互動,還幾乎被所有的模組依賴。

events 模組只提供了一個物件: events.EventEmitter。EventEmitter 的核心就是事件發射與事件監聽器功能的封裝。EventEmitter 的每個事件由一個事件名和若干個引數組成,事件名是一個字串,通常表達一定的語義。對於每個事件,EventEmitter 支援若干個事件監聽器。當事件發射時,註冊到這個事件的事件監聽器被依次呼叫,事件引數作為回撥函式引數傳遞。

例子:

//引入events模組
var events  = require('events');
//發射器物件
var emitter = new events.EventEmitter();
//emitter為someEvent事件註冊了兩個事件監聽器。
emitter.on('someEvent',function(arg1,arg2){
    console.log('listener1',arg1,arg2);
});
emitter.on('someEvent',function(arg1,arg2){
    console.log('listener2',arg1,arg2);
});
//發射一個sonmeEvent事件
emitter.emit('someEvent','Creabine',1991);

執行結果是如下。執行結果中可以看到兩個事件監聽器回撥函式被先後呼叫。
listener1 Creabine 1991
listener2 Creabine 1991

以上是EventEmitter最簡單的用法,接下來介紹一下它的常用API:

  • EventEmitter.on(event,listener) 為指定事件註冊一個監聽器,接受一個字串event和一個回撥函式listener
  • EventEmitter.once(event,listener) 為指定事件註冊一個單次監聽器,即監聽器最多隻會觸發一次,出發後立即解除該監聽器。
  • EventEmitter.removeListener(event,listener) 移除指定事件的某個監聽器,listener必須是該事件已經註冊過的監聽器。
  • EventEmitter.removeAllListener([event]) 移除所有事件的所有監聽器。如果指定event,則移除指定事件的所有監聽器。

error事件

EventEmitter 定義了一個特殊的事件 error,它包含了“錯誤”的語義,我們在遇到異常的時候通常會發射 error 事件。當 error 被髮射時,EventEmitter 規定如果沒有響應的監聽器,Node.js 會把它當作異常,退出程式並列印呼叫棧。我們一般要為會發射 error事件的物件設定監聽器,避免遇到錯誤後整個程式崩潰。

整合EventEmitter
大多數時候我們不會直接使用 EventEmitter,而是在物件中繼承它。包括 fs、net、http 在內的,只要是支援事件響應的核心模組都是 EventEmitter 的子類。

為什麼要這樣做呢?原因有兩點。首先,具有某個實體功能的物件實現事件符合語義,事件的監聽和發射應該是一個物件的方法。其次 JavaScript 的物件機制是基於原型的,支援部分多重繼承,繼承 EventEmitter 不會打亂物件原有的繼承關係。

檔案系統fs

fs 模組是檔案操作的封裝,它提供了檔案的讀取、寫入、更名、刪除、遍歷目錄、連結等 POSIX 檔案系統操作。與其他模組不同的是,fs 模組中所有的操作都提供了非同步的和同步的兩個版本, 例如讀取檔案內容的函式有非同步的 fs.readFile() 和同步的fs.readFileSync()。

fs.readFile(filename,[encoding],[callback(err,data)])

fs.readFile是最簡單的讀取檔案的函式。它接受一個必選引數 filename,表示要讀取的檔名。第二個引數 encoding是可選的,表示檔案的字元編碼。callback 是回撥函式,用於接收檔案的內容。如果不指定 encoding,則 callback 就是第二個引數。回撥函式提供兩個引數 err 和 data,err 表示有沒有錯誤發生,data 是檔案內容。

如果指定了 encoding,data 是一個解析後的字串,否則 data 將會是以 Buffer 形式表示的二進位制資料。

當讀取檔案出現錯誤時,err 將會是 Error 物件。

fs.readFileSync(filename,[encoding])

fs.readFileSync(filename, [encoding])是 fs.readFile 同步的版本。它接受的引數和 fs.readFile 相同,而讀取到的檔案內容會以函式返回值的形式返回。如果有錯誤發生,fs 將會丟擲異常,你需要使用 try 和 catch 捕捉並處理異常。

與同步 I/O 函式不同,Node.js 中非同步函式大多沒有返回值。

fs.open(path,flags,[mode],[callback(err,fd)])
path是檔案路徑,flags的值為:
 r :以讀取模式開啟檔案。
 r+ :以讀寫模式開啟檔案。
 w :以寫入模式開啟檔案,如果檔案不存在則建立。
 w+ :以讀寫模式開啟檔案,如果檔案不存在則建立。
 a :以追加模式開啟檔案,如果檔案不存在則建立。
 a+ :以讀取追加模式開啟檔案,如果檔案不存在則建立。
mode 引數用於建立檔案時給檔案指定許可權,預設是 0666①。回撥函式將會傳遞一個文
件描述符 fd。

此外fs還有許多其他函式。

HTTP伺服器與客戶端

Node.js 標準庫提供了 http 模組,其中封裝了一個高效的 HTTP 伺服器和一個簡易的HTTP 客戶端。http.Server 是一個基於事件的 HTTP 伺服器,它的核心由 Node.js 下層 C++部分實現,而介面由 JavaScript 封裝,兼顧了高效能與簡易性。http.request 則是一個HTTP 客戶端工具,用於向 HTTP 伺服器發起請求,例如實現 Pingback①或者內容抓取。

HTTP伺服器

http.Server 是 http 模組中的 HTTP 伺服器物件,用 Node.js 做的所有基於 HTTP 協議的系統,如網站、社交應用甚至代理伺服器,都是基於 http.Server 實現的。它提供了一套封裝級別很低的 API,僅僅是流控制和簡單的訊息解析,所有的高層功能都要通過它的介面來實現。

簡單的http伺服器例如:

//app.js

var http = require('http');

http.createServer(function(req,res){
    //響應程式碼200(表示請求成功),制定相應頭為'Content-Type':'text/html'
    res.write(200,'Content-Type':'text/html');
    //寫入響應體
    res.write('<h1>Node.js</h1>');
    //結束併傳送
    res.end('<p>Hello World</p>');
    //呼叫listen函式,啟動伺服器並監聽3000埠
}).listen(3000);

console.log('HTTP Server is listening at 3000');

http.server的事件

http.Server 是一個基於事件的 HTTP 伺服器,所有的請求都被封裝為獨立的事件,開發者只需要對它的事件編寫響應函式即可實現 HTTP 伺服器的所有功能。它繼承自EventEmitter,提供了以下幾個事件。

  • request:當客戶端請求到來時,該事件被觸發。提供兩個引數req和res,res,分別是http.ServerRequest 和 http.ServerResponse 的例項,表示請求和響應資訊。
  • connection:當TCP連線建時,該事件被觸發,提供一個引數socket,為net.Socket 的例項。connection 事件的粒度要大於 request,因為客戶端在Keep-Alive 模式下可能會在同一個連線內傳送多次請求。
  • close:當伺服器關閉時,該事件被觸發。注意不是在使用者連線斷開時。

除此之外還有 checkContinue、upgrade、clientError 事件,通常我們不需要關心,只有在實現複雜的 HTTP 伺服器的時候才會用到。

在這些事件中, 最常用的就是 request 了, 因此 http 提供了一個捷徑:http.createServer([requestListener]) , 功能是建立一個 HTTP 伺服器並將requestListener 作為 request 事件的監聽函式,這也是我們前面例子中使用的方法。事實上它顯式的實現方法是:

//httpserver.js

var http = require('http');

var server = new http.Server();
server.on('request', function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Node.js</h1>');
res.end('<p>Hello World</p>');
});
server.listen(3000);

console.log("HTTP server is listening at port 3000.");

http.ServerRequest
http.ServerRequest 是 HTTP 請求的資訊,是後端開發者最關注的內容。它一般由http.Server 的 request 事件傳送,作為第一個引數傳遞,通常簡稱 request 或 req。

HTTP 請求一般可以分為兩部分:請求頭(Request Header)和請求體(Requset Body)。以上內容由於長度較短都可以在請求頭解析完成後立即讀取。而請求體可能相對較長,需要一定的時間傳輸,因此 http.ServerRequest 提供了以下3個事件用於控制請求體傳輸:

  • data:當請求體資料到來時,該事件被觸發。該事件提供一個引數 chunk,表示接收到的資料。如果該事件沒有被監聽,那麼請求體將會被拋棄。該事件可能會被呼叫多次。
  • end:當請求體資料傳輸完成時,該事件被觸發,此後將不會再有資料到來。
  • close:使用者當前請求結束時,該事件被觸發。不同於 end,如果使用者強制終止了傳輸,也還是呼叫close。

http.ServerResponse
http.ServerResponse 是返回給客戶端的資訊,決定了使用者最終能看到的結果。它也是由 http.Server 的 request 事件傳送的,作為第二個引數傳遞,一般簡稱為response 或 res。

http.ServerResponse 有三個重要的成員函式,用於返回響應頭、響應內容以及結束請求。

  • response.writeHead(statusCode, [headers]):向請求的客戶端傳送響應頭。statusCode 是 HTTP 狀態碼,如 200 (請求成功)、404 (未找到)等。headers是一個類似關聯陣列的物件,表示響應頭的每個屬性。該函式在一個請求內最多隻能呼叫一次,如果不呼叫,則會自動生成一個響應頭。
  • response.write(data, [encoding]):向請求的客戶端傳送響應內容。data 是一個 Buffer 或字串,表示要傳送的內容。如果 data 是字串,那麼需要指定encoding 來說明它的編碼方式,預設是 utf-8。在 response.end 呼叫之前,response.write 可以被多次呼叫。
  • response.end([data], [encoding]):結束響應,告知客戶端所有傳送已經完成。當所有要返回的內容傳送完畢的時候,該函式 必須 被呼叫一次。它接受兩個可選引數,意義和 response.write 相同。如果不呼叫該函式,客戶端將永遠處於等待狀態。

82-84

第五章 使用Node.js進行Web開發

Node.js實現網站,目的是實現動態網頁,也就是說由伺服器動態生成HTML頁面。之所以要這麼做,是因為靜態HTML的可擴充套件性非常有限,無法與使用者有效互動。同時如果有大量相似內容,例如產品介紹頁面,那麼1000個產品就要1000個靜態的HTML頁面,維護它們簡直是災難,隱刺動態生成HTML頁面的技術應運而生。

MVC(Model-View-Controller,模型-檢視-控制器)是一種軟體設計模式,把一個複雜的工程分解為三個層面:模型,檢視和控制器。其中:

  • 模型是物件及其資料結構的實現,通常包含資料庫操作。
  • 檢視表示使用者介面,在網站中通常就是HTML的組織結構。
  • 控制器用於處理使用者請求和陣列劉,複雜模型,將輸出傳遞給檢視。

Node.js的好處是它內建了http模組,本身就可以作為伺服器,效能可靠,可以直接應用到生產環境。

Node.js的http模組提供的是底層介面,比起PHP這種封裝好的高階介面,使用起來複雜很多,所以我們使用node官方推薦的Express框架。

首先安裝Express:本身只要區域性安裝即可,但是我們需要使用快速開始工具,所以我們全域性安裝:

npm install express -g
//安裝完之後發現執行express --help無效,這是因為express在4.0版本後需要安裝the executable(執行器express-generator)才能執行express命令,所以再:
npm install express-generator -g

接下來的部分估計有點老了,我按照書中的操作,但是生成的檔案內容並不一致。這本書就先看到這裡,找找別的練習做一做。

相關文章