Node.js 教程(學習筆記,持續更新)

Node.js 教程

Node.js 安裝配置

Node.js 建立第一個應用

使用Node.js時,咱們不只僅在實現一個應用,同時還實現了整個HTTP服務器。事實上,咱們的Web應用以及對應的Web服務器基本上是同樣的。html

在咱們建立Node.js第一個"Hello, World!"應用前,讓咱們先了解下Node.js應用是由哪幾部分組成的:node

  1. 引入 required 模塊: 咱們可使用 require 指令來載入 Node.js 模塊
  2. 建立服務器: 服務器能夠監聽客戶端的請求,相似於Apache 、Nginx等HTTP服務器。
  3. 接收請求與響應請求: 服務器很容易建立,客戶端可使用瀏覽器或終端發送HTTP請求,服務器接收請求後返回響應數據。

建立 Node.js 應用web

步驟1、引入required 模塊

咱們使用require指令來載入http模塊,並將實例化的HTTP賦值給變量http,實例以下:正則表達式

var http = require("http");
複製代碼
步驟2、建立服務器

接下來咱們使用http.createServer()方法建立服務器,並使用listen方法綁定8888端口。 函數經過request, response參數來接收和響應數據。shell

實例以下,在你項目的根目錄下建立一個叫server.js的文件,並寫入如下代碼:express

var http = require('http');

http.createServer(function(request,response) {
 // 發送 HTTP 頭部
 // HTTP 狀態值:200 :OK
 // 內容類型:text/plain
 response.writeHead(200, {'Content-type':'text/plain'});

 // 發送響應數據 "Hello World"
 response.end('Hello World\n');
}).listen(8888);

// 終端打印以下信息
console.log('Server running at http://127.0.0.1:8888/');
複製代碼

Node.js NPM 使用介紹

NPM是隨同NodeJS一塊兒安裝的包管理工具,能解決NodeJS代碼部署上的不少問題,常見的使用場景有如下幾種:npm

容許用戶從NPM服務器下載別人編寫的第三方包到本地使用。 容許用戶從NPM服務器下載並安裝別人編寫的命令行程序到本地使用。 容許用戶將本身編寫的包或命令行程序上傳到NPM服務器供別人使用。編程

因爲新版的nodejs已經集成了npm,因此以前npm也一併安裝好了。一樣能夠經過輸入json

"npm -v"bootstrap

來測試是否成功安裝。命令以下,出現版本提示表示安裝成功:

$ npm -v
6.4.1
複製代碼

若是你安裝的是舊版本的 npm,能夠很容易得經過 npm 命令來升級,命令以下:

$ sudo npm install npm -g
/usr/local/bin/npm -> /usr/local/lib/node_modules/npm/bin/npm-cli.js
npm@2.14.2 /usr/local/lib/node_modules/npm
複製代碼

若是是 Window 系統使用如下命令便可:

npm install npm -g
複製代碼

全局安裝與本地安裝 npm 的包安裝分爲本地安裝(local)、全局安裝(global)兩種,從敲的命令行來看,差異只是有沒有-g而已,好比

npm install express          # 本地安裝
npm install express -g   # 全局安裝
複製代碼

若是出現如下錯誤:

$ npm config set proxy null
複製代碼
本地安裝
  • i. 將安裝包放在 ./node_modules 下(運行 npm 命令時所在的目錄),若是沒有 node_modules 目錄,會在當前執行 npm 命令的目錄下生成 node_modules 目錄。
  • i. 能夠經過 require() 來引入本地安裝的包。
全局安裝
  • i. 將安裝包放在 /usr/local 下或者你 node 的安裝目錄。
  • i. 能夠直接在命令行裏使用。

若是你但願具有二者功能,則須要在兩個地方安裝它或使用 npm link。

NPM 應用

NPM創建了一個NodeJS生態圈,NodeJS開發者和用戶能夠在裏邊互通有無。如下介紹NPM應用的三種場景:

下載第三方包

咱們可使用如下命令來下載第三方包。

$ npm install argv
...
argv@0.0.2 node_modules\argv
複製代碼

下載好以後,argv包就放在了工程目錄下的node_modules目錄中,所以在代碼中只須要經過require('argv')的方式就好,無需指定第三方包路徑。

以上命令默認下載最新版第三方包,若是想要下載指定版本的話,能夠在包名後邊加上@,例如經過如下命令可下載0.0.1版的argv。

$ npm install argv@0.0.1
...
argv@0.0.1 node_modules\argv
複製代碼

NPM對package.json的字段作了擴展,容許在其中申明第三方包依賴。所以,上邊例子中的package.json能夠改寫以下:

{
    "name": "node-echo",
    "main": "./lib/echo.js",
    "dependencies": {
        "argv": "0.0.2"
    }
}
複製代碼

這樣處理後,在工程目錄下就可使用npm install命令批量安裝第三方包了。

更重要的是,當之後node-echo也上傳到了NPM服務器,別人下載這個包時,NPM會根據包中申明的第三方包依賴自動下載進一步依賴的第三方包。

例如,使用npm install node-echo命令時,NPM會自動建立如下目錄結構。

- project/
    - node_modules/
        - node-echo/
            - node_modules/
                + argv/
            ...
    ...
複製代碼

如此一來,用戶只需關心本身直接使用的第三方包,不須要本身去解決全部包的依賴關係。

安裝命令行程序

從NPM服務上下載安裝一個命令行程序的方法與第三方包相似。

例如上例中的node-echo提供了命令行使用方式,只要node-echo本身配置好了相關的package.json字段,對於用戶而言,只須要使用如下命令安裝程序。

$ npm install node-echo -g
複製代碼

參數中的-g表示全局安裝,所以node-echo會默認安裝到如下位置,而且NPM會自動建立好Linux系統下須要的軟鏈文件或Windows系統下須要的.cmd文件。

- /usr/local/               # Linux系統下
    - lib/node_modules/
        + node-echo/
        ...
    - bin/
        node-echo
        ...
    ...

- %APPDATA%\npm\            # Windows系統下
    - node_modules\
        + node-echo\
        ...
    node-echo.cmd
    ...
複製代碼
發佈代碼

第一次使用NPM發佈代碼前須要註冊一個帳號。終端下運行npm adduser,以後按照提示作便可。

帳號註冊完成後,接着咱們須要編輯package.json文件,加入NPM必需的字段。接着上邊node-echo的例子,package.json裏必要的字段以下。

{
    "name": "node-echo",           # 包名,在NPM服務器上需要保持惟一
    "version": "1.0.0",            # 當前版本號
    "dependencies": {              # 第三方包依賴,須要指定包名和版本號
        "argv": "0.0.2"
      },
    "main": "./lib/echo.js",       # 入口模塊位置
    "bin" : {
        "node-echo": "./bin/node-echo"      # 命令行程序名和主模塊位置
    }
}
複製代碼

以後,咱們就能夠在package.json所在目錄下運行npm publish發佈代碼了。

版本號

使用NPM下載和發佈代碼時都會接觸到版本號。NPM使用語義版本號來管理代碼,這裏簡單介紹一下。

語義版本號分爲X.Y.Z三位,分別表明主版本號、次版本號和補丁版本號。當代碼變動時,版本號按如下原則更新。

  • 若是隻是修復bug,須要更新Z位。
  • 若是是新增了功能,可是向下兼容,須要更新Y位。
  • 若是有大變更,向下不兼容,須要更新X位。

版本號有了這個保證後,在申明第三方包依賴時,除了可依賴於一個固定版本號外,還可依賴於某個範圍的版本號。例如"argv": "0.0.x"表示依賴於0.0.x系列的最新版argv。

NPM支持的全部版本號範圍指定方式能夠查看官方文檔

NPM 經常使用命令

除了能夠在npmjs.org/doc/查看官方文檔外,這裏再介紹一些NPM經常使用命令。 NPM提供了不少命令,例如install和publish,使用npm help可查看全部命令。

  • NPM提供了不少命令,例如install和publish,使用npm help可查看全部命令。
  • 使用npm help 可查看某條命令的詳細幫助,例如npm help install。
  • 在package.json所在目錄下使用npm install . -g可先在本地安裝當前命令行程序,可用於發佈前的本地測試。
  • 使用npm update 能夠把當前目錄下node_modules子目錄裏邊的對應模塊更新至最新版本。
  • 使用npm update -g能夠把全局安裝的對應命令行程序更新至最新版。
  • 使用npm cache clear能夠清空NPM本地緩存,用於對付使用相同版本號發佈新版本代碼的人。
  • 使用npm unpublish @能夠撤銷發佈本身發佈過的某個版本代碼。

Node.js REPL(交互式解釋器)

Node.js REPL(交互式解釋器)

Node.js REPL(Read Eval Print Loop:交互式解釋器) 表示一個電腦的環境,相似 Window 系統的終端或 Unix/Linux shell,咱們能夠在終端中輸入命令,並接收系統的響應。

REPL 的交互式的編程環境能夠實時的驗證你所編寫的代碼,很是適合於驗證 Node.js 和 JavaScript 的相關 API。

Node 自帶了交互式解釋器,能夠執行如下任務:

  • 讀取 - 讀取用戶輸入,解析輸入了Javascript 數據結構並存儲在內存中。
  • 執行 - 執行輸入的數據結構
  • 打印 - 輸出結果
  • 循環 - 循環操做以上步驟直到用戶兩次按下 ctrl-c 按鈕退出。

Node 的交互式解釋器能夠很好的調試 Javascript 代碼。

開始學習 REPL

咱們能夠輸入如下命令來啓動 Node 的終端:

$ node
>
複製代碼

這時咱們就能夠在 > 後輸入簡單的表達式,並按下回車鍵來計算結果。

簡單的表達式運算

接下來讓咱們在 Node.js REPL 的命令行窗口中執行簡單的數學運算:

$ node
> 1 +4
5
> 5 / 2
2.5
> 3 * 6
18
> 4 - 1
3
> 1 + ( 2 * 3 ) - 4
3
>
複製代碼
使用變量

你能夠將數據存儲在變量中,並在你須要的使用它。

變量聲明須要使用 var 關鍵字,若是沒有使用 var 關鍵字變量會直接打印出來。

使用 var 關鍵字的變量可使用 console.log() 來輸出變量。

$ node
> x = 10
10
> var y = 10
undefined
> x + y
20
> console.log("Hello World")
Hello World
undefined
> console.log("www.w3cschool.cn")
www.w3cschool.cn
undefined
複製代碼
多行表達式
$ node
> var x = 0
undefined
> do {
... x++;
... console.log("x: " + x);
... } while ( x < 5 );
x: 1
x: 2
x: 3
x: 4
x: 5
undefined
>
複製代碼

... 三個點的符號是系統自動生成的,你回車換行後便可。Node 會自動檢測是否爲連續的表達式。

下劃線(_)變量

你可使用下劃線(_)獲取表達式的運算結果:

$ node
> var x = 10
undefined
> var y = 20
undefined
> x + y
30
> var sum = _
undefined
> console.log(sum)
30
undefined
>
複製代碼
REPL 命令
  • ctrl + c - 退出當前終端。
  • ctrl + c 按下兩次 - 退出 Node REPL。
  • ctrl + d - 退出 Node REPL.
  • 向上/向下 鍵 - 查看輸入的歷史命令
  • tab 鍵 - 列出當前命令
  • .help - 列出使用命令
  • .break - 退出多行表達式
  • .clear - 退出多行表達式
  • .save filename - 保存當前的 Node REPL 會話到指定文件
  • .load filename - 載入當前 Node REPL 會話的文件內容。
中止 REPL

前面咱們已經提到按下兩次 ctrl + c 建就能退出 REPL:

$ node
>
(^C again to quit)
>
複製代碼

Node.js 回調函數

Node.js 回調函數

Node.js 異步編程的直接體現就是回調。

異步編程依託於回調來實現,但不能說使用了回調後程序就異步化了。

回調函數在完成任務後就會被調用,Node 使用了大量的回調函數,Node 全部 API 都支持回調函數。

例如,咱們能夠一邊讀取文件,一邊執行其餘命令,在文件讀取完成後,咱們將文件內容做爲回調函數的參數返回。這樣在執行代碼時就沒有阻塞或等待文件 I/O 操做。這就大大提升了 Node.js 的性能,能夠處理大量的併發請求。

阻塞代碼實例

建立一個文件 input.txt ,內容以下:

W3Cschool教程官網地址:www.w3cschool.cn
複製代碼

建立 main.js文件,代碼以下:

var fs = require("fs");

var data = fs.readFileSync('input.txt');

console.log(data.toString());
console.log("程序執行結束!");
複製代碼

以上代碼執行結果以下:

$ node main.js
W3Cschool教程官網地址:www.w3cschool.cn

程序執行結束!
複製代碼
非阻塞代碼實例

建立一個文件 input.txt ,內容以下:

W3Cschool教程官網地址:www.w3cschool.cn
複製代碼

建立 main.js文件,代碼以下:

var fs = require("fs");

fs.readFile('input.txt',function (err,data) {
   if (err) return console.log.error(err);
  console.log(data.toString());
});

console.log("程序執行結束!");
複製代碼

以上代碼執行結果以下:

$ node main.js
程序執行結束!
W3Cschool教程官網地址:www.w3cschool.cn
複製代碼

以上兩個實例咱們瞭解了阻塞與非阻塞調用的不一樣。第一個實例在文件讀取完後才執行完程序。 第二個實例咱們呢不須要等待文件讀取完,這樣就能夠在讀取文件時同時執行接下來的代碼,大大提升了程序的性能。

所以,阻塞按是按順序執行的,而非阻塞是不須要按順序的,因此若是須要處理回調函數的參數,咱們就須要寫在回調函數內。

Node.js 事件循環

Node.js 事件循環

Node.js 是單進程單線程應用程序,可是經過事件和回調支持併發,因此性能很是高。

Node.js 的每個 API 都是異步的,並做爲一個獨立線程運行,使用異步函數調用,並處理併發。

Node.js 基本上全部的事件機制都是用設計模式中觀察者模式實現。

Node.js 單線程相似進入一個while(true)的事件循環,直到沒有事件觀察者退出,每一個異步事件都生成一個事件觀察者,若是有事件發生就調用該回調函數.

事件驅動程序

Node.js 使用事件驅動模型,當web server接收到請求,就把它關閉而後進行處理,而後去服務下一個web請求。

當這個請求完成,它被放回處理隊列,當到達隊列開頭,這個結果被返回給用戶。

這個模型很是高效可擴展性很是強,由於webserver一直接受請求而不等待任何讀寫操做。(這也被稱之爲非阻塞式IO或者事件驅動IO)

在事件驅動模型中,會生成一個主循環來監聽事件,當檢測到事件時觸發回調函數。

整個事件驅動的流程就是這麼實現的,很是簡潔。有點相似於觀察者模式,事件至關於一個主題(Subject),而全部註冊到這個事件上的處理函數至關於觀察者(Observer)。

Node.js 有多個內置的事件,咱們能夠經過引入 events 模塊,並經過實例化 EventEmitter 類來綁定和監聽事件,以下實例:

// 引入 events 模塊
var events = require('events');
// 建立 eventEmitter 對象
var eventEmitter = new events.EventEmitter();
複製代碼

如下程序綁定事件處理程序:

// 綁定事件及事件的處理程序
eventEmitter.on('eventName', eventHandler);
複製代碼

咱們能夠經過程序觸發事件:

// 觸發事件
eventEmitter.emit('eventName');
複製代碼
實例

建立 main.js,代碼以下所示:

var fs = require("fs");

fs.readFile('input.txt',function (err,data) {
   if (err) return console.log.error(err);
  console.log("===== 我打印的是 data 的內容====",data.toString());
});

console.log("程序執行結束!");

console.log("========================== 下面要執行 events 模塊的內容了=========================");

// 引入 events 模塊
var events = require('events');
// 建立 eventEmitter 對象
var eventEmitter = new events.EventEmitter();

// 建立事件處理程序
var connectHandler = function connected() {
 console.log('鏈接成功。');

// 觸發 data_received 事件
eventEmitter.emit('data_received');
}

// 綁定 connection 事件處理程序
eventEmitter.on('connection',connectHandler);

// 使用匿名函數綁定 data_received 事件
eventEmitter.on('data_received',function(){
 console.log('數據鏈接成功。');
});

// 觸發 connection 事件
eventEmitter.emit('connection');
console.log('程序執行完畢。');
複製代碼

接下來讓咱們執行以上代碼:

程序執行結束!
===================== 下面要執行 events 模塊的內容了=====================
鏈接成功。
數據鏈接成功。
程序執行完畢。
===== 我打印的是 data 的內容==== W3Cschool教程官網地址:www.w3cschool.cn
複製代碼
Node 應用程序是如何工做的?

Node 應用程序中,執行異步操做的函數將回調函數做爲最後一個參數, 回調函數接收錯誤對象做爲第一個參數。

接下來讓咱們來從新看下前面的實例,建立一個 input.txt ,文件內容以下:

W3Cschool教程官網地址:www.w3cschool.cn
複製代碼

建立 main.js 文件,代碼以下:

var fs = require("fs");

fs.readFile('input.txt', function (err, data) {
   if (err){
      console.log(err.stack);
      return;
   }
   console.log(data.toString());
});
console.log("程序執行完畢");
複製代碼

以上程序中 fs.readFile() 是異步函數用於讀取文件。 若是在讀取文件過程當中發生錯誤,錯誤 err 對象就會輸出錯誤信息。

若是沒發生錯誤,readFile 跳過 err 對象的輸出,文件內容就經過回調函數輸出。

執行以上代碼,執行結果以下:

程序執行完畢
W3Cschool教程官網地址:www.w3cschool.cn
複製代碼

接下來咱們刪除 input.txt 文件,執行結果以下所示:

程序執行完畢
Error: ENOENT, open 'input.txt'
複製代碼

Node.js 事件

Node.js 事件

Node.js 全部的異步 I/O 操做在完成時都會發送一個事件到事件隊列

Node.js 裏面的許多對象都會分發事件:一個net.Server對象會在每次有新鏈接時分發一個事件, 一個fs.readStream對象會在文件被打開的時候發出一個事件。 全部這些產生事件的對象都是 events.EventEmitter 的實例。 你能夠經過require("events");來訪問該模塊。

下面咱們用一個簡單的例子說明 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 的一個監聽器,而後咱們經過 setTimeout 在1000毫秒之後向 event 對象發送事件 some_event,此時會調用 some_event 的監聽器。

EventEmitter介紹

events 模塊只提供了一個對象: events.EventEmitter。EventEmitter 的核心就 是事件發射與事件監聽器功能的封裝。

EventEmitter 的每一個事件由一個事件名和若干個參 數組成,事件名是一個字符串,一般表達必定的語義。對於每一個事件,EventEmitter 支持 若干個事件監聽器。

當事件發射時,註冊到這個事件的事件監聽器被依次調用,事件參數做 爲回調函數參數傳遞。

讓咱們如下面的例子解釋這個過程:

/**
 * EventEmitter 介紹
 */
var events = require('events');
var emitter = new events.EventEmitter();
emitter.on('someEvent',function (arg1,arg2){
    console.log('listener1',arg1,arg2);
});
emitter.on('someEvent',function (arg1,arg2){
    console.log('listener2',arg1,arg2);
});
emitter.emit('someEvent','byvoid',1991);
複製代碼

運行的結果是:

listener1 byvoid 1991
listener2 byvoid 1991
複製代碼

以上例子中,emitter 爲事件 someEvent 註冊了兩個事件監聽器,而後發射了 someEvent 事件。運行結果中能夠看到兩個事件監聽器回調函數被前後調用。 這就是EventEmitter最簡單的用法。

EventEmitter經常使用的API

EventEmitter.on(event, listener)、emitter.addListener(event, listener) 爲指定事件註冊一個監聽器,接收一個字符串 event 和一個回調函數 listener。

server.on('connection', function (stream) {
  console.log('someone connected!');
});
複製代碼

EventEmitter.emit(event, arg1, arg2, ...) 發射 event 事件,傳遞若干可選參數到事件監聽器的參數表。

EventEmitter.once(event, listener) 爲指定事件註冊一個單次監聽器,即 監聽器最多隻會觸發一次,觸發後馬上解除該監聽器。

server.once('connection', function (stream) {
  console.log('Ah, we have our first user!');
});
複製代碼

EventEmitter.removeListener(event, listener) 移除指定事件的某個監聽器,listener 必須是該事件已經註冊過的監聽器。

var callback = function(stream) {
  console.log('someone connected!');
};
server.on('connection', callback);
// ...
server.removeListener('connection', callback);
複製代碼

EventEmitter.removeAllListeners(event) 移除全部事件的全部監聽器, 若是指定 event,則移除指定事件的全部監聽器。


error 事件

EventEmitter 定義了一個特殊的事件 error,它包含了"錯誤"的語義,咱們在遇到 異常的時候一般會發射 error 事件。

當 error 被髮射時,EventEmitter 規定若是沒有響 應的監聽器,Node.js 會把它看成異常,退出程序並打印調用棧。

咱們通常要爲會發射 error 事件的對象設置監聽器,避免遇到錯誤後整個程序崩潰。例如:

var events = require('events');
var emitter = new events.EventEmitter();
emitter.emit('error');
複製代碼

運行時會顯示如下錯誤:

node.js:201
throw e; // process.nextTick error, or 'error' event on first tick

Error: Uncaught, unspecified 'error' event.
at EventEmitter.emit (events.js:50:15)
at Object. (/home/byvoid/error.js:5:9)
at Module._compile (module.js:441:26)
at Object..js (module.js:459:10)
at Module.load (module.js:348:31)
at Function._load (module.js:308:12)
at Array.0 (module.js:479:10)
at EventEmitter._tickCallback (node.js:192:40)
複製代碼

繼承 EventEmitter

大多數時候咱們不會直接使用 EventEmitter,而是在對象中繼承它。包括 fs、net、 http 在內的,只要是支持事件響應的核心模塊都是 EventEmitter 的子類。

爲何要這樣作呢?緣由有兩點:

首先,具備某個實體功能的對象實現事件符合語義, 事件的監聽和發射應該是一個對象的方法。

其次JavaScript 的對象機制是基於原型的,支持 部分多重繼承,繼承 EventEmitter 不會打亂對象原有的繼承關係。

Node.js Buffer(緩衝區)

Node.js Buffer(緩衝區)

JavaScript 語言自身只有字符串數據類型?這樣理解能夠嗎?

但在處理像TCP流文件流時,必須使用到二進制數據。所以在 Node.js 中,定義了一個 Buffer 類,該類用來建立一個專門存放二進制數據的緩存區

在 Node.js 中,Buffer 類是隨 Node 內核一塊兒發佈的核心庫。Buffer 庫 爲 Node.js 帶來了一種存儲原始數據的方法,可讓 Node.js 處理二進制數據,每當須要在 Node.js 中處理I/O操做中移動的數據時,就有可能使用 Buffer 庫。原始數據存儲在 Buffer 類的實例中。一個 Buffer 相似於一個整數數組,但它對應於 V8 堆內存以外的一塊原始內存。

建立 Buffer 類

Node Buffer 類能夠經過多種方式來建立。

方法一

建立長度爲 10 字節的 Buffer 實例:

var buf = new Buffer(10);
複製代碼
方法二

經過給定的數組建立 Buffer 實例:

var buf = new Buffer([10,20,30,40,50]);
複製代碼
方法三

經過一個字符串建立 Buffer 實例:

var buf = new Buffer("www.w3cschool.cn","utf-8");
複製代碼

utf-8 是默認的編碼方式,此外它一樣支持如下編碼:"ascii", "utf8", "utf16le", "ucs2", "base64" 和 "hex"。

寫入緩衝區
語法

寫入 Node 緩衝區的語法以下所示:

buf.write(string[, offset[, length]][, encoding])
複製代碼
參數

參數描述以下:

  • string - 寫入緩衝區的字符串。
  • offset - 緩衝區開始寫入的索引值,默認爲 0 。
  • length - 寫入的字節數,默認爲 buffer.length
  • encoding - 使用的編碼。默認爲 'utf8' 。
返回值

返回實際寫入的大小。若是 buffer 空間不足,則只會寫入部分字符串。

實例
buf = new Buffer(256);
len = buf.write("www.w3cschool.cn");

console.log("寫入字節數:"+ len);
複製代碼

執行以上代碼,輸出結果爲:

$node main.js
寫入字節數 : 16
複製代碼
從緩衝區讀取數據
語法

讀取 Node 緩衝區數據的語法以下所示:

buf.toString([encoding[,start[,end]]])
複製代碼
參數

參數描述以下:

  • encoding - 使用的編碼。默認爲 'utf8' 。
  • start - 指定開始讀取的索引位置,默認爲 0。
  • end - 結束位置,默認爲緩衝區的末尾。
返回值

解碼緩衝區數據並使用指定的編碼返回字符串

實例
buf = new Buffer(26);
for (var i = 0 ; i < 26 ; i++) {
  buf[i] = i + 97;
}

console.log( buf.toString('ascii'));       // 輸出: abcdefghijklmnopqrstuvwxyz
console.log( buf.toString('ascii',0,5));   // 輸出: abcde
console.log( buf.toString('utf8',0,5));    // 輸出: abcde
console.log( buf.toString(undefined,0,5)); // 使用 'utf8' 編碼, 並輸出: abcde
複製代碼

執行以上代碼,輸出結果爲:

$ node main.js
abcdefghijklmnopqrstuvwxyz
abcde
abcde
abcde
複製代碼
將 Buffer 轉換爲 JSON 對象
語法

將 Node Buffer 轉換爲 JSON 對象的函數語法格式以下:

buf.toJSON();
複製代碼
返回值

返回 JSON 對象。

實例
var buf = new Buffer('www.w3cschool.cn');
var json = buf.toJSON(buf);

console.log(json);
複製代碼

執行以上代碼,輸出結果爲:

{ type: 'Buffer',
  data: [ 119, 119, 119, 46, 119, 51, 99, 115, 99, 104, 111, 111, 108, 46, 99, 110 ] }
複製代碼
緩衝區合併
語法

Node 緩衝區合併的語法以下所示:

Buffer.concat(list[, totalLength])
複製代碼
參數

參數描述以下:

  • list - 用於合併的 Buffer 對象數組列表。
  • totalLength - 指定合併後Buffer對象的總長度。
返回值

返回一個多個成員合併的新 Buffer 對象。

實例
var buffer1 = new Buffer('W3Cschool教程 ');
var buffer2 = new Buffer('www.w3cschool.cn');
var buffer3 = Buffer.concat([buffer1,buffer2]);
console.log("buffer3 內容: " + buffer3.toString());
複製代碼

執行以上代碼,輸出結果爲:

buffer3 內容: W3Cschool教程 www.w3cschool.cn
複製代碼
緩衝區比較
語法

Node Buffer 比較的函數語法以下所示, 該方法在 Node.js v0.12.2 版本引入:

buf.compare(otherBuffer);
複製代碼
參數

參數描述以下:

  • otherBuffer - 與 buf 對象比較的另一個 Buffer 對象。
返回值

返回一個數字,表示 buf 在 otherBuffer 以前,以後或相同。

實例
var buffer1 = new Buffer('ABC');
var buffer2 = new Buffer('ABCD');
var result = buffer1.compare(buffer2);

if(result < 0) {
   console.log(buffer1 + " 在 " + buffer2 + "以前");
}else if(result == 0){
   console.log(buffer1 + " 與 " + buffer2 + "相同");
}else {
   console.log(buffer1 + " 在 " + buffer2 + "以後");
}
複製代碼

執行以上代碼,輸出結果爲:

ABC在ABCD以前
複製代碼
拷貝緩衝區

Node 緩衝區拷貝語法以下所示:

buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])
複製代碼
參數

參數描述以下:

  • targetBuffer - 要拷貝的 Buffer 對象。
  • targetStart - 數字, 可選, 默認: 0
  • sourceStart - 數字, 可選, 默認: 0
  • sourceEnd - 數字, 可選, 默認: buffer.length 返回值 沒有返回值。
實例
var buffer1 = new Buffer('ABC');
// 拷貝一個緩衝區
var buffer2 = new Buffer(3);
buffer1.copy(buffer2);
console.log("buffer2 content: " + buffer2.toString());
複製代碼

執行以上代碼,輸出結果爲:

buffer2 content: ABC
複製代碼
緩衝區裁剪

Node 緩衝區裁剪語法以下所示:

buf.slice([start[, end]])
複製代碼
參數

參數描述以下:

  • start - 數字, 可選, 默認: 0
  • end - 數字, 可選, 默認: buffer.length

#####返回值 返回一個新的緩衝區,它和舊緩衝區指向同一塊內存,可是從索引 start 到 end 的位置剪切。

實例
var buffer1 = new Buffer('youj');
// 剪切緩衝區
var buffer2 = buffer1.slice(0,2);
console.log("buffer2 content: " + buffer2.toString());
複製代碼

執行以上代碼,輸出結果爲:

buffer2 content: yo
複製代碼
緩衝區長度
語法

Node 緩衝區長度計算語法以下所示:

buf.length;
複製代碼
返回值

返回 Buffer 對象所佔據的內存長度。

實例
var buffer = new Buffer('www.w3cschool.cn');
// 緩衝區長度
console.log('buffer length:' + buffer.length);
複製代碼

執行以上代碼,輸出結果爲:

buffer length: 16
複製代碼
方法參考手冊
序號 方法 & 描述
1 new Buffer(size) 分配一個新的 size 大小單位爲8位字節的 buffer。 注意, size 必須小於 kMaxLength,不然,將會拋出異常 RangeError。
2 new Buffer(buffer) 拷貝參數 buffer 的數據到 Buffer 實例。
3 new Buffer(str, encoding)分配一個新的 buffer ,其中包含着傳入的 str 字符串。 encoding 編碼方式默認爲 'utf8'。
4 buf.length返回這個 buffer 的 bytes 數。注意這未必是 buffer 裏面內容的大小。length 是 buffer 對象所分配的內存數,它不會隨着這個 buffer 對象內容的改變而改變。
5 buf.write(string, offset[, length])根據參數 offset 偏移量和指定的 encoding 編碼方式,將參數 string 數據寫入buffer。 offset 偏移量默認值是 0, encoding 編碼方式默認是 utf8。 length 長度是將要寫入的字符串的 bytes 大小。 返回 number 類型,表示寫入了多少 8 位字節流。若是 buffer 沒有足夠的空間來放整個 string,它將只會只寫入部分字符串。 length 默認是 buffer.length - offset。 這個方法不會出現寫入部分字符。
6 buf.writeUIntLE(value, offset, byteLength, noAssert)將value 寫入到 buffer 裏, 它由offset 和 byteLength 決定,支持 48 位計算,例如:var b = new Buffer(6); b.writeUIntBE(0x1234567890ab, 0, 6); // <Buffer 12 34 56 78 90 ab> noAssert 值爲 true 時,再也不驗證 value 和 offset 的有效性。 默認是 false。
7 buf.writeUIntBE(value, offset, byteLength, noAssert)將value 寫入到 buffer 裏, 它由offset 和 byteLength 決定,支持 48 位計算。noAssert 值爲 true 時,再也不驗證 value 和 offset 的有效性。 默認是 false。
8 buf.writeIntLE(value, offset, byteLength, noAssert)將value 寫入到 buffer 裏, 它由offset 和 byteLength 決定,支持 48 位計算。noAssert 值爲 true 時,再也不驗證 value 和 offset 的有效性。 默認是 false。
9 buf.writeIntBE(value, offset, byteLength, noAssert)將value 寫入到 buffer 裏, 它由offset 和 byteLength 決定,支持 48 位計算。noAssert 值爲 true 時,再也不驗證 value 和 offset 的有效性。 默認是 false。
10 buf.readUIntLE(offset, byteLength, noAssert)支持讀取 48 位如下的數字。noAssert 值爲 true 時, offset 再也不驗證是否超過 buffer 的長度,默認爲 false。
11 buf.readUIntBE(offset, byteLength, noAssert)支持讀取 48 位如下的數字。noAssert 值爲 true 時, offset 再也不驗證是否超過 buffer 的長度,默認爲 false。
12 buf.readIntLE(offset, byteLength, noAssert)支持讀取 48 位如下的數字。noAssert 值爲 true 時, offset 再也不驗證是否超過 buffer 的長度,默認爲 false。
13 buf.readIntBE(offset, byteLength, noAssert)支持讀取 48 位如下的數字。noAssert 值爲 true 時, offset 再也不驗證是否超過 buffer 的長度,默認爲 false。
14 buf.toString([encoding[, start, end]])根據 encoding 參數(默認是 'utf8')返回一個解碼過的 string 類型。還會根據傳入的參數 start (默認是 0) 和 end (默認是 buffer.length)做爲取值範圍。
15 buf.toJSON()將 Buffer 實例轉換爲 JSON 對象。
16 bufindex獲取或設置指定的字節。返回值表明一個字節,因此返回值的合法範圍是十六進制0x00到0xFF 或者十進制0至 255。
17 buf.equals(otherBuffer)比較兩個緩衝區是否相等,若是是返回 true,不然返回 false。
18 buf.compare(otherBuffer)比較兩個 Buffer 對象,返回一個數字,表示 buf 在 otherBuffer 以前,以後或相同。
19 buf.copy(target[, targetStart[, sourceStart, sourceEnd]])buffer 拷貝,源和目標能夠相同。 targetStart 目標開始偏移和 sourceStart 源開始偏移默認都是 0。 sourceEnd 源結束位置偏移默認是源的長度 buffer.length 。
20 buf.slice([start, end])剪切 Buffer 對象,根據 start(默認是 0 ) 和 end (默認是 buffer.length ) 偏移和裁剪了索引。 負的索引是從 buffer 尾部開始計算的。
21 buf.readUInt8(offset, noAssert)根據指定的偏移量,讀取一個有符號 8 位整數。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 若是這樣 offset 可能會超出buffer 的末尾。默認是 false。
22 buf.readUInt16LE(offset, noAssert)根據指定的偏移量,使用特殊的 endian 字節序格式讀取一個有符號 16 位整數。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 offset 可能會超出 buffer 的末尾。默認是 false。
23 buf.readUInt16BE(offset, noAssert)根據指定的偏移量,使用特殊的 endian 字節序格式讀取一個有符號 16 位整數。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 offset 可能會超出 buffer 的末尾。默認是 false。
24 buf.readUInt32LE(offset, noAssert)根據指定的偏移量,使用指定的 endian 字節序格式讀取一個有符號 32 位整數。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 offset 可能會超出buffer 的末尾。默認是 false。
25 buf.readUInt32BE(offset, noAssert)根據指定的偏移量,使用指定的 endian 字節序格式讀取一個有符號 32 位整數。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 offset 可能會超出buffer 的末尾。默認是 false。
26 buf.readInt8(offset, noAssert)根據指定的偏移量,讀取一個 signed 8 位整數。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 offset 可能會超出 buffer 的末尾。默認是 false。
27 buf.readInt16LE(offset, noAssert)根據指定的偏移量,使用特殊的 endian 格式讀取一個 signed 16 位整數。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 offset 可能會超出 buffer 的末尾。默認是 false。
28 buf.readInt16BE(offset, noAssert)根據指定的偏移量,使用特殊的 endian 格式讀取一個 signed 16 位整數。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 offset 可能會超出 buffer 的末尾。默認是 false。
29 buf.readInt32LE(offset, noAssert)根據指定的偏移量,使用指定的 endian 字節序格式讀取一個 signed 32 位整數。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 offset 可能會超出buffer 的末尾。默認是 false。
30 buf.readInt32BE(offset, noAssert)根據指定的偏移量,使用指定的 endian 字節序格式讀取一個 signed 32 位整數。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 offset 可能會超出buffer 的末尾。默認是 false。
31 buf.readFloatLE(offset, noAssert)根據指定的偏移量,使用指定的 endian 字節序格式讀取一個 32 位浮點數。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 offset 可能會超出buffer的末尾。默認是 false。
32 buf.readFloatBE(offset, noAssert)根據指定的偏移量,使用指定的 endian 字節序格式讀取一個 32 位浮點數。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 offset 可能會超出buffer的末尾。默認是 false。
33 buf.readDoubleLE(offset, noAssert)根據指定的偏移量,使用指定的 endian字節序格式讀取一個 64 位double。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 offset 可能會超出buffer 的末尾。默認是 false。
34 buf.readDoubleBE(offset, noAssert)根據指定的偏移量,使用指定的 endian字節序格式讀取一個 64 位double。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 offset 可能會超出buffer 的末尾。默認是 false。
35 buf.writeUInt8(value, offset, noAssert)根據傳入的 offset 偏移量將 value 寫入 buffer。注意:value 必須是一個合法的有符號 8 位整數。若參數 noAssert 爲 true 將不會驗證 offset 偏移量參數。 這意味着 value 可能過大,或者 offset 可能會超出 buffer 的末尾從而形成 value 被丟棄。 除非你對這個參數很是有把握,不然不要使用。默認是 false。
36 buf.writeUInt16LE(value, offset, noAssert)根據傳入的 offset 偏移量和指定的 endian 格式將 value 寫入 buffer。注意:value 必須是一個合法的有符號 16 位整數。若參數 noAssert 爲 true 將不會驗證 value 和 offset 偏移量參數。 這意味着 value 可能過大,或者 offset 可能會超出buffer的末尾從而形成 value 被丟棄。 除非你對這個參數很是有把握,不然儘可能不要使用。默認是 false。
37 buf.writeUInt16BE(value, offset, noAssert)根據傳入的 offset 偏移量和指定的 endian 格式將 value 寫入 buffer。注意:value 必須是一個合法的有符號 16 位整數。若參數 noAssert 爲 true 將不會驗證 value 和 offset 偏移量參數。 這意味着 value 可能過大,或者 offset 可能會超出buffer的末尾從而形成 value 被丟棄。 除非你對這個參數很是有把握,不然儘可能不要使用。默認是 false。
38 buf.writeUInt32LE(value, offset, noAssert)根據傳入的 offset 偏移量和指定的 endian 格式將 value 寫入buffer。注意:value 必須是一個合法的有符號 32 位整數。若參數 noAssert 爲 true 將不會驗證 value 和 offset 偏移量參數。 這意味着value 可能過大,或者offset可能會超出buffer的末尾從而形成 value 被丟棄。 除非你對這個參數很是有把握,不然儘可能不要使用。默認是 false。
39 buf.writeUInt32BE(value, offset, noAssert)根據傳入的 offset 偏移量和指定的 endian 格式將 value 寫入buffer。注意:value 必須是一個合法的有符號 32 位整數。若參數 noAssert 爲 true 將不會驗證 value 和 offset 偏移量參數。 這意味着value 可能過大,或者offset可能會超出buffer的末尾從而形成 value 被丟棄。 除非你對這個參數很是有把握,不然儘可能不要使用。默認是 false。
40 buf.writeInt8(value, offset, noAssert)

Node.js Stream(流)

Node.js Stream(流)

Stream 是 Node.js 中很是重要的一個模塊,應用普遍。

Stream 是一個抽象接口,Node 中有不少對象實現了這個接口。例如,對http 服務器發起請求的request 對象就是一個 Stream,還有stdout(標準輸出)。

該抽象接口是可讀、可寫或是既可讀又可寫的,經過這些接口,咱們能夠和磁盤文件、套接字、HTTP請求來交互,實現數據從一個地方流動到另外一個地方的功能。

Node.js,Stream 有四種流類型:

  • Readable - 可讀操做。
  • Writable - 可寫操做。
  • Duplex - 可讀可寫操做.
  • Transform - 操做被寫入數據,而後讀出結果。

全部的 Stream 對象都是 EventEmitter 的實例。經常使用的事件有:

  • data - 當有數據可讀時觸發。
  • end - 沒有更多的數據可讀時觸發。
  • error - 在接收和寫入過程當中發生錯誤時觸發。
  • finish - 全部數據已被寫入到底層系統時觸發。
從流中讀取數據

建立 input.txt 文件,內容以下:

W3Cschool教程官網地址:www.w3cschool.cn
複製代碼

建立 main.js 文件, 代碼以下:

var fs = require("fs");
var data = '';

// 建立可讀流
var readerStream = fs.createReadStream('input.txt');

// 設置編碼爲 utf8。
readerStream.setEncoding('UTF8');

// 處理流事件 --> data, end, and error
readerStream.on('data', function(chunk) {
   data += chunk;
});

readerStream.on('end',function(){
   console.log(data);
});

readerStream.on('error', function(err){
   console.log(err.stack);
});

console.log("程序執行完畢");
複製代碼
寫入流

建立 streamdemo2.js 文件, 代碼以下:

var fs = require("fs");
var data = 'W3Cschool教程官網地址:www.w3cschool.cn';

// 建立一個能夠寫入的流,寫入到文件 output.txt 中
var writerSteam = fs.createWriteStream('output.txt');

// 使用 utf8 編碼寫入數據
writerSteam.write(data,'UTF8');

// 標記文件末尾
writerSteam.end();

// 處理流事件 --> data,end,and error
writerSteam.on('finish',function(){
    console.log('寫入完成。');
});

writerSteam.on('error',function(err){
    console.log(err.stack);
});

console.log("程序執行完畢");
複製代碼

以上程序會將 data 變量的數據寫入到 output.txt 文件中。代碼執行結果以下:

$ node main.js
程序執行完畢
寫入完成。
複製代碼

查看 output.txt 文件的內容:

$ cat output.txt
W3Cschool教程官網地址:www.w3cschool.cn
複製代碼
管道流

管道提供了一個輸出流到輸入流的機制。一般咱們用於從一個流中獲取數據並將數據傳遞到另一個流中。

如上面的圖片所示,咱們把文件比做裝水的桶,而水就是文件裏的內容,咱們用一根管子(pipe)鏈接兩個桶使得水從一個桶流入另外一個桶,這樣就慢慢的實現了大文件的複製過程。

如下實例咱們經過讀取一個文件內容並將內容寫入到另一個文件中。

建立 streamdemo3.js 文件, 代碼以下:

var fs = require("fs");

// 建立一個可讀流
var readerStream = fs.createReadStream('input.txt');

// 建立一個可寫流
var writerStream = fs.createWriteStream('output.txt');

// 管道讀寫操做
// 讀取 input.txt 文件內容,並將內容寫入到 output.txt 文件中
readerStream.pipe(writerStream);

console.log("程序執行完畢");
複製代碼

代碼執行結果以下:

$ node main.js
程序執行完畢
複製代碼

查看 output.txt 文件的內容:

$ cat output.txt
W3Cschool教程官網地址:www.w3cschool.cn
管道流操做實例
複製代碼
鏈式流

鏈式是經過鏈接輸出流到另一個流並建立多個對個流操做鏈的機制。鏈式流通常用於管道操做。

接下來咱們就是用管道和鏈式來壓縮和解壓文件。

建立 compress.js 文件, 代碼以下:

var fs = require("fs");
var zlib = require('zlib');

// 壓縮 input.txt 文件爲 input.txt.gz
fs.createReadStream('input.txt')
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream('input.txt.gz'));

console.log("文件壓縮完成。");
複製代碼

代碼執行結果以下:

$ node compress.js
文件壓縮完成。
複製代碼

執行完以上操做後,咱們能夠看到當前目錄下生成了 input.txt 的壓縮文件 input.txt.gz。

接下來,讓咱們來解壓該文件,建立 decompress.js 文件,代碼以下:

var fs = require("fs");
var zlib = require('zlib');

// 解壓 input.txt.gz 文件爲 input.txt
fs.createReadStream('input.txt.gz')
  .pipe(zlib.createGunzip())
  .pipe(fs.createWriteStream('input.txt'));
console.log("文件解壓完成。");
複製代碼

代碼執行結果以下:

$ node decompress.js
文件解壓完成。
複製代碼

Node.js 模塊系統

Node.js 模塊系統

爲了讓Node.js的文件能夠相互調用,Node.js提供了一個簡單的模塊系統。

模塊是Node.js 應用程序的基本組成部分,文件和模塊是一一對應的。換言之,一個 Node.js 文件就是一個模塊,這個文件多是JavaScript 代碼``、JSON 或者編譯過的C/C++ 擴展

建立模塊

在 Node.js 中,建立一個模塊很是簡單,以下咱們建立一個 'CreateModuledemo.js' 文件,代碼以下:

var hello = require('./hello');
hello.world();
複製代碼

以上實例中,代碼 require('./hello') 引入了當前目錄下的hello.js文件(./ 爲當前目錄,node.js默認後綴爲js)。

Node.js 提供了exports 和 require 兩個對象,其中 exports 是模塊公開的接口,require 用於從外部獲取一個模塊的接口,即所獲取模塊的 exports 對象。

接下來咱們就來建立hello.js文件,代碼以下:

exports.world = function() {
  console.log('Hello World');
}
複製代碼

在以上示例中,hello.js 經過 exports 對象把 world 做爲模塊的訪問接口,在 CreateModuledemo.js 中經過 require('./hello') 加載這個模塊,而後就能夠直接訪 問CreateModuledemo.js 中 exports 對象的成員函數了。

有時候咱們只是想把一個對象封裝到模塊中,格式以下:

module.exports = function() {
  // ...
}
複製代碼

例如:

//hello1.js
function Hello() {
 var name;
    this.setName = function(thyName) {
       name = thyName;
  };
   this.sayHello = function() {
     console.log('Hello ' + name);
  };
};
module.exports = Hello;
複製代碼

這樣就能夠直接得到這個對象了:

//main.js
var Hello = require('./hello');
hello = new Hello();
hello.setName('BYVoid');
hello.sayHello();
複製代碼

模塊接口的惟一變化是使用 module.exports = Hello 代替了exports.world = function(){}。 在外部引用該模塊時,其接口對象就是要輸出的 Hello 對象自己,而不是原先的 exports。

服務端的模塊放在哪裏

也許你已經注意到,咱們已經在代碼中使用了模塊了。像這樣:

var http = require("http");

...

http.createServer(...);
複製代碼

Node.js中自帶了一個叫作"http"的模塊,咱們在咱們的代碼中請求它並把返回值賦給一個本地變量。

這把咱們的本地變量變成了一個擁有全部 http 模塊所提供的公共方法的對象。

Node.js 的 require方法中的文件查找策略以下:

因爲Node.js中存在4類模塊(原生模塊和3種文件模塊),儘管require方法極其簡單,可是內部的加載倒是十分複雜的,其加載優先級也各自不一樣。以下圖所示:

從文件模塊緩存中加載

儘管原生模塊與文件模塊的優先級不一樣,可是都不會優先於從文件模塊的緩存中加載已經存在的模塊。

從原生模塊加載

原生模塊的優先級僅次於文件模塊緩存的優先級。require方法在解析文件名以後,優先檢查模塊是否在原生模塊列表中。以http模塊爲例,儘管在目錄下存在一個http/http.js/http.node/http.json文件,require("http")都不會從這些文件中加載,而是從原生模塊中加載。

原生模塊也有一個緩存區,一樣也是優先從緩存區加載。若是緩存區沒有被加載過,則調用原生模塊的加載方式進行加載和執行。

從文件加載

當文件模塊緩存中不存在,並且不是原生模塊的時候,Node.js會解析require方法傳入的參數,並從文件系統中加載實際的文件,加載過程當中的包裝和編譯細節在前一節中已經介紹過,這裏咱們將詳細描述查找文件模塊的過程,其中,也有一些細節值得知曉。

require方法接受如下幾種參數的傳遞:

  • http、fs、path等,原生模塊。
  • ./mod或../mod,相對路徑的文件模塊。
  • /pathtomodule/mod,絕對路徑的文件模塊。
  • mod,非原生模塊的文件模塊。

Node.js 函數

Node.js 函數

在JavaScript中,一個函數能夠做爲另外一個函數接收一個參數。咱們能夠先定義一個函數,而後傳遞,也能夠在傳遞參數的地方直接定義函數。

Node.js中函數的使用與Javascript相似,舉例來講,你能夠這樣作:

function say(word) {
  console.log(word);
}

function execute(someFunction, value) {
  someFunction(value);
}

execute(say, "Hello");
複製代碼

以上代碼中,咱們把 say 函數做爲execute函數的第一個變量進行了傳遞。這裏返回的不是 say 的返回值,而是 say 自己!

這樣一來, say 就變成了execute 中的本地變量 someFunction ,execute能夠經過調用 someFunction() (帶括號的形式)來使用 say 函數。

固然,由於 say 有一個變量, execute 在調用 someFunction 時能夠傳遞這樣一個變量。

匿名函數

咱們能夠把一個函數做爲變量傳遞。可是咱們不必定要繞這個"先定義,再傳遞"的圈子,咱們能夠直接在另外一個函數的括號中定義和傳遞這個函數:

function execute(someFunction, value) {
  someFunction(value);
}

execute(function(word){ console.log(word) }, "Hello");
複製代碼

咱們在 execute 接受第一個參數的地方直接定義了咱們準備傳遞給 execute 的函數。

用這種方式,咱們甚至不用給這個函數起名字,這也是爲何它被叫作匿名函數 。

函數傳遞是如何讓HTTP服務器工做的

帶着這些知識,咱們再來看看咱們簡約而不簡單的HTTP服務器:

var http = require("http");

http.createServer(function(request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.write("Hello World");
  response.end();
}).listen(8888);
複製代碼

如今它看上去應該清晰了不少:咱們向 createServer 函數傳遞了一個匿名函數。

用這樣的代碼也能夠達到一樣的目的:

var http = require("http");

function onRequest(request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.write("Hello World");
  response.end();
}

http.createServer(onRequest).listen(8888);
複製代碼

Node.js 路由

Node.js 路由

咱們要爲路由提供請求的URL和其餘須要的GET及POST參數,隨後路由須要根據這些數據來執行相應的代碼。

所以,咱們須要查看HTTP請求,從中提取出請求的URL以及GET/POST參數。這一功能應當屬於路由仍是服務器(甚至做爲一個模塊自身的功能)確實值得探討,但這裏暫定其爲咱們的HTTP服務器的功能。

咱們須要的全部數據都會包含在request對象中,該對象做爲onRequest()回調函數的第一個參數傳遞。可是爲了解析這些數據,咱們須要額外的Node.JS模塊,它們分別是urlquerystring模塊。

url.parse(string).query
                                           |
           url.parse(string).pathname      |
                       |                   |
                       |                   |
                     ------ -------------------
http://localhost:8888/start?foo=bar&hello=world
                                ---       -----
                                 |          |
                                 |          |
              querystring(string)["foo"]    |
                                            |
                         querystring(string)["hello"]
複製代碼

固然咱們也能夠用querystring模塊來解析POST請求體中的參數,稍後會有演示。

如今咱們來給onRequest()函數加上一些邏輯,用來找出瀏覽器請求的URL路徑:

var url = require("url");

function start() {
  function onRequest(request, response) {
    var pathname = url.parse(request.url).pathname;
    console.log("Request for " + pathname + " received.");
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Hello World");
    response.end();
  }

  http.createServer(onRequest).listen(8888);
  console.log("Server has started.");
}

exports.start = start;
複製代碼

好了,咱們的應用如今能夠經過請求的URL路徑來區別不一樣請求了--這使咱們得以使用路由(還未完成)來將請求以URL路徑爲基準映射處處理程序上。

在咱們所要構建的應用中,這意味着來自/start和/upload的請求可使用不一樣的代碼來處理。稍後咱們將看到這些內容是如何整合到一塊兒的。

如今咱們能夠來編寫路由了,創建一個名爲router.js的文件,添加如下內容:

function route(pathname) {
  console.log("About to route a request for " + pathname);
}

exports.route = route;
複製代碼

如你所見,這段代碼什麼也沒幹,不過對於如今來講這是應該的。在添加更多的邏輯之前,咱們先來看看如何把路由和服務器整合起來。

咱們的服務器應當知道路由的存在並加以有效利用。咱們固然能夠經過硬編碼的方式將這一依賴項綁定到服務器上,可是其它語言的編程經驗告訴咱們這會是一件很是痛苦的事,所以咱們將使用依賴注入的方式較鬆散地添加路由模塊。

首先,咱們來擴展一下服務器的start()函數,以便將路由函數做爲參數傳遞過去:

var http = require("http");
var url = require("url");

function start(route) {
  function onRequest(request, response) {
    var pathname = url.parse(request.url).pathname;
    console.log("Request for " + pathname + " received.");

    route(pathname);

    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Hello World");
    response.end();
  }

  http.createServer(onRequest).listen(8888);
  console.log("Server has started.");
}

exports.start = start;
複製代碼

同時,咱們會相應擴展index.js,使得路由函數能夠被注入到服務器中:

var server = require("./server");
var router = require("./router");

server.start(router.route);
複製代碼

在這裏,咱們傳遞的函數依舊什麼也沒作。

若是如今啓動應用(node index.js,始終記得這個命令行),隨後請求一個URL,你將會看到應用輸出相應的信息,這代表咱們的HTTP服務器已經在使用路由模塊了,並會將請求的路徑傳遞給路由:

bash$ node index.js
Request for /foo received.
About to route a request for /foo
複製代碼

Node.js 全局對象

Node.js 全局對象

本節介紹 Node.js 全局對象,global 全局對象無需引用就能夠直接使用。

JavaScript 中有一個特殊的對象,稱爲全局對象(Global Object),它及其全部屬性均可以在程序的任何地方訪問,即全局變量。

在瀏覽器 JavaScript 中,一般window 是全局對象, 而Node.js 中的全局對象是 global,全部全局變量(除了 global 自己之外)都是 global 對象的屬性。

咱們在Node.js 中可以直接訪問到對象一般都是 global 的屬性,如 consoleprocess 等,下面逐一介紹。

全局對象與全局變量

global 最根本的做用是做爲全局變量的宿主。按照 ECMAScript 的定義,知足如下條件的變量是全局變量:

  • 在最外層定義的變量;
  • 全局對象的屬性;
  • 隱式定義的變量(未定義直接賦值的變量)。

當你定義一個全局變量時,這個變量同時也會成爲全局對象的屬性,反之亦然。須要注 意的是,在Node.js 中你不可能在最外層定義變量,由於全部用戶代碼都是屬於當前模塊的, 而模塊自己不是最外層上下文。

注意: 永遠使用var 定義變量以免引入全局變量,由於全局變量會污染 命名空間,提升代碼的耦合風險。


process

process 是一個全局變量,即 global 對象的屬性。

它用於描述當前Node.js 進程狀態 的對象,提供了一個與操做系統的簡單接口。一般在你寫本地命令行程序的時候,少不了要 和它打交道。下面將會介紹process 對象的一些最經常使用的成員方法。

process.argv是命令行參數數組,第一個元素是 node,第二個元素是腳本文件名, 從第三個元素開始每一個元素是一個運行參數。

console.log(process.argv);
複製代碼

將以上代碼存儲爲argv.js,經過如下命令運行:

$ node argv.js 1991 name=byvoid --v "Carbo Kuo"
[ '/Users/guoguo/.nvm/versions/node/v8.12.0/bin/node',
  '/Users/guoguo/Desktop/node-pro/argv.js',
  '1991',
  'name=byvoid',
  '--v',
  'Guo Guo' ]
複製代碼
  • process.stdout是標準輸出流,一般咱們使用的 console.log() 向標準輸出打印 字符,而 process.stdout.write() 函數提供了更底層的接口。
  • process.stdin是標準輸入流,初始時它是被暫停的,要想從標準輸入讀取數據, 你必須恢復流,並手動編寫流的事件響應函數。
process.stdin.resume();
process.stdin.on('data', function(data) {
process.stdout.write('read from console: ' + data.toString());
});
複製代碼
  • process.nextTick(callback)的功能是爲事件循環設置一項任務,Node.js 會在 下次事件循環調響應時調用 callback。

初學者極可能不理解這個函數的做用,有什麼任務不能在當下執行完,須要交給下次事 件循環響應來作呢?

咱們討論過,Node.js 適合I/O 密集型的應用,而不是計算密集型的應用, 由於一個Node.js 進程只有一個線程,所以在任什麼時候刻都只有一個事件在執行。

若是這個事 件佔用大量的CPU 時間,執行事件循環中的下一個事件就須要等待好久,所以Node.js 的一 個編程原則就是儘可能縮短每一個事件的執行時間。process.nextTick() 提供了一個這樣的 工具,能夠把複雜的工做拆散,變成一個個較小的事件。

function doSomething(args, callback) {
  somethingComplicated(args);
  callback();
}
doSomething(function onEnd() {
  compute();
});
複製代碼

咱們假設compute() 和somethingComplicated() 是兩個較爲耗時的函數,以上 的程序在調用 doSomething() 時會先執行somethingComplicated(),而後當即調用 回調函數,在 onEnd() 中又會執行 compute()。下面用process.nextTick() 改寫上 面的程序:

function doSometing(args,callback){
	somethingComplicated(args);
    process.nextTick(callback);
}
doSometing(function onEnd(){
   compute();
});
複製代碼

改寫後的程序會把上面耗時的操做拆分爲兩個事件,減小每一個事件的執行時間,提升事件響應速度。

注意: 不要使用setTimeout(fn,0)代替process.nextTick(callback), 前者比後者效率要低得多。

咱們探討了process對象經常使用的幾個成員,除此以外process還展現了process.platform、 process.pid、process.execPath、process.memoryUsage() 等方法,以及POSIX 進程信號響應機制。有興趣的讀者能夠訪問 nodejs.org/api/process… 瞭解詳細 內容。

######console

console 用於提供控制檯標準輸出,它是由Internet Explorer 的JScript 引擎提供的調試 工具,後來逐漸成爲瀏覽器的事實標準。

Node.js 沿用了這個標準,提供與習慣行爲一致的 console 對象,用於向標準輸出流(stdout)或標準錯誤流(stderr)輸出字符。 console.log():向標準輸出流打印字符並以換行符結束。

console.log 接受若干 個參數,若是隻有一個參數,則輸出這個參數的字符串形式。若是有多個參數,則 以相似於C 語言 printf() 命令的格式輸出。

第一個參數是一個字符串,若是沒有 參數,只打印一個換行。

console.log('Hello world');
console.log('byvoid%diovyb');
console.log('byvoid%diovyb', 1991);
複製代碼

運行結果爲:

Hello world
byvoid%diovyb
byvoid1991iovyb
複製代碼
  • console.error():與console.log() 用法相同,只是向標準錯誤流輸出。
  • console.trace():向標準錯誤流輸出當前的調用棧。
console.trace();
複製代碼

運行結果爲:

Trace:
at Object.<anonymous> (/home/byvoid/consoletrace.js:1:71)
at Module._compile (module.js:441:26)
at Object..js (module.js:459:10)
at Module.load (module.js:348:31)
at Function._load (module.js:308:12)
at Array.0 (module.js:479:10)
at EventEmitter._tickCallback (node.js:192:40)
複製代碼

Node.js 經常使用工具util

Node.js 經常使用工具util

util做爲Node.js的一個核心模塊,可以提供經常使用函數的集合,彌補核心JavaScript的功能過於精簡的不足。

util.inherits

util.inherits(constructor, superConstructor)是一個實現對象間原型繼承的函數。

與常見的基於類的不一樣,JavaScript的面向對象特性是基於原型的。JavaScript沒有提供對象繼承的語言級別特性,而是經過原型複製來實現的。

在這裏咱們只介紹util.inherits的用法,示例以下:

var util = require('util');
function Base() {
    this.name = 'base';
    this.base = 1991;
    this.sayHello = function() {
        console.log('Hello ' + this.name);
    };
}

Base.prototype.showName = function() {
    console.log(this.name);
};

function Sub() {
    this.name = 'sub';
}
util.inherits(Sub,Base);
var objBase = new Base();
objBase.showName();
objBase.sayHello();
console.log(objBase);
var objSub = new Sub();
objSub.showName();
// objSub.sayHello();
console.log(objSub);
複製代碼

咱們定義了一個基礎對象Base和一個繼承自Base的Sub,Base有三個在構造函數內定義的屬性和一個原型中定義的函數,經過util.inherits實現繼承。運行結果以下:

base
Hello base
Base { name: 'base', base: 1991, sayHello: [Function] }
sub
Sub { name: 'sub' }
複製代碼

注意: Sub僅僅繼承了Base在原型中定義的函數,而構造函數內部創造的base屬性和sayHello函數都沒有被Sub繼承。

同時,在原型中定義的屬性不會被console.log做爲對象的屬性輸出。若是咱們去掉objSub.sayHello(); 這行的註釋,將會看到:

base
Hello base
Base { name: 'base', base: 1991, sayHello: [Function] }
sub
/Users/guoguo/Desktop/node-pro/utilInheritsDemo.js:24
objSub.sayHello();
       ^

TypeError: objSub.sayHello is not a function
    at Object.<anonymous> (/Users/guoguo/Desktop/node-pro/utilInheritsDemo.js:24:8)
    at Module._compile (module.js:653:30)
    at Object.Module._extensions..js (module.js:664:10)
    at Module.load (module.js:566:32)
    at tryModuleLoad (module.js:506:12)
    at Function.Module._load (module.js:498:3)
    at Function.Module.runMain (module.js:694:10)
    at startup (bootstrap_node.js:204:16)
    at bootstrap_node.js:625:3
複製代碼
util.inspect

util.inspect(object,showHidden,depth,colors)方法能夠將任意對象轉換爲字符串,一般用於調試和錯誤輸出。它至少接受一個object參數,即要轉換的對象。

showHidden是一個可選參數,若是值爲true,將會輸出更多隱藏信息。

depth表示最大遞歸的層數,若是對象很複雜,你能夠指定層數以控制輸出信息的多少。若是不指定depth,則默認遞歸2層,指定爲null時表示將不限遞歸層數完整遍歷對象。 若是color值爲true,則輸出格式將會以ANSI顏色編碼,一般用於在終端顯示更漂亮的效果。

特別要指出的是,util.inspect並不會簡單地直接把對象轉換爲字符串,即便該對象定義了toString方法也不會調用。

var util = require('util');

 function Person(){
     this.name = 'guoguo';
     this.toString = function() {
         return this.name;
     };
 }

 var obj = new Person();
 console.log(util.inspect(obj));
 console.log(util.inspect(obj,true));
複製代碼

運行結果是:

Person { name: 'guoguo', toString: [Function] }
Person {
  name: 'guoguo',
  toString:
   { [Function]
     [length]: 0,
     [name]: '',
     [arguments]: null,
     [caller]: null,
     [prototype]: { [constructor]: [Circular] } } }
複製代碼
util.isArray(object)

若是給定的參數 "object" 是一個數組返回true,不然返回false。

var util = require('util');

util.isArray([])	// true
util.isArray(new Array) 	// true
util.isArray({})	// false
複製代碼
util.isRegExp(object)

若是給定的參數"object"是一個正則表達式返回true,不然返回false。

var util = require('util');

util.isRegExp(/some regexp/)	// true
util.isRegExp(new RegExp('another regexp'))	// true
util.isRegExp({})	// false
複製代碼
util.isDate(object)

若是給定的參數 "object" 是一個日期返回true,不然返回false。

var util = require('util');

util.isDate(new Date())
  // true
util.isDate(Date())
  // false (without 'new' returns a String)
util.isDate({})
  // false
複製代碼
util.isError(object)

若是給定的參數 "object" 是一個錯誤對象返回true,不然返回false。

var util = require('util');

util.isError(new Error())
  // true
util.isError(new TypeError())
  // true
util.isError({ name: 'Error', message: 'an error occurred' })
  // false
複製代碼

更多詳情能夠訪問 nodejs.org/api/util.ht… 瞭解詳細內容。

Node.js 文件系統

Node.js 文件系統

Node.js文件系統被封裝在fs模塊中,它提供了文件的讀取、寫入、改名、刪除、遍歷目錄、連接等POSIX文件系統操做。

與其餘模塊不一樣的是,fs模塊中全部的操做都提供了異步的和同步的兩個版本,例如讀取文件內容的函數有異步的fs.readFile()和同步的fs.readFileSync()。咱們以幾個函數爲表明,介紹fs經常使用的功能,並列出fs全部函數的定義和功能。

fs.readFile

Node.js讀取文件函數語法以下:

fs.readFile(filename,[encoding],[callback(err,data)])
複製代碼
  • filename(必選),表示要讀取的文件名。
  • encoding(可選),表示文件的字符編碼。
  • callback 是回調函數,用於接收文件的內容。

若是不指定encoding,則callback就是第二個參數。回調函數提供兩個參數err和data,err表示有沒有錯誤發生,data是文件內容。若是指定了encoding,data是一個解析後的字符串,不然data將會是以Buffer形式表示的二進制數據。

例如如下程序,咱們從content.txt中讀取數據,但不指定編碼:

var fs = require('fs');
fs.readFile('content.txt', function(err, data) {
    if(err) {
        console.error(err);
  } else{
      console.log(data);
   }
});
複製代碼

假設content.txt中的內容是UTF-8編碼的Text文本文件示例,運行結果以下:

<Buffer 54 65 78 74 20 e6 96 87 e6 9c ac e6 96 87 e4 bb b6 e7 a4 ba e4 be 8b>
複製代碼

這個程序以二進制的模式讀取了文件的內容,data的值是Buffer對象。若是咱們給fs.readFile的encoding指定編碼:

var fs = require('fs');
    fs.readFile('content.txt', 'utf-8', function(err, data) {
    if (err) {
       console.error(err);
  } else {
     console.log(data);
   }
});
複製代碼

那麼運行結果則是:

Text 文本文件示例
複製代碼

當讀取文件出現錯誤時,err將會是Error對象。若是content.txt不存在,運行前面的代碼則會出現如下結果:

{ [Error: ENOENT, no such file or directory 'content.txt'] errno: 34, code: 'ENOENT',
path: 'content.txt' }
複製代碼
fs.readFileSync

fs.readFileSync(filename, encoding)是fs.readFile同步的版本。它接受和 fs.readFile 相同的參數,而讀取到的文件內容會以函數返回值的形式返回。若是有錯誤發生,fs將會拋出異常,你須要使用try和catch捕捉並處理異常。

注意: 與同步I/O函數不一樣,Node.js中異步函數大多沒有返回值。

fs.open

fs.open(path, flags, mode, callback(err, fd))是POSIX open函數的封裝,相似於C語言標準庫中的fopen函數。它接受兩個必選參數,path爲文件的路徑, flags 能夠是如下值:

  • r :以讀取模式打開文件。
  • r+ :以讀寫模式打開文件。
  • w :以寫入模式打開文件,若是文件不存在則建立。
  • w+ :以讀寫模式打開文件,若是文件不存在則建立。
  • a :以追加模式打開文件,若是文件不存在則建立。
  • a+ :以讀取追加模式打開文件,若是文件不存在則建立
fs.read

fs.read語法格式以下:

fs.read(fd, buffer, offset, length, position, [callback(err, bytesRead, buffer)])
複製代碼

參數說明:

  • fd: 讀取數據並寫入buffer指向的緩衝區對象。
  • offset: 是buffer的寫入偏移量。
  • length: 是要從文件中讀取的字節數。
  • position: 是文件讀取的起始位置,若是position的值爲null,則會從當前文件指針的位置讀取。
  • callback:回調函數傳遞bytesRead和buffer,分別表示讀取的字節數和緩衝區對象。

如下是一個使用fs.open和fs.read的示例。

var fs = require('fs');
fs.open('content.txt', 'r', function(err, fd) {
    if(err) {
        console.error(err);
      return;
  }
    var buf = new Buffer(8);
  fs.read(fd, buf, 0, 8, null, function(err, bytesRead, buffer) {
      if(err) {
            console.error(err);
          return;
      }
        console.log('bytesRead: ' + bytesRead);
        console.log(buffer);
 })
});
複製代碼

運行結果是:

bytesRead: 8
<Buffer 54 65 78 74 20 e6 96 87>
複製代碼

通常來講,除非必要,不然不要使用這種方式讀取文件,由於它要求你手動管理緩衝區和文件指針,尤爲是在你不知道文件大小的時候,這將會是一件很麻煩的事情。

fs 模塊函數表

更多詳情可點擊查看:nodejs.org/api/fs.html


2019-8-14

相關文章
相關標籤/搜索