Node.js 概述

Node.js 概述

來自《JavaScript 標準參考教程(alpha)》,by 阮一峯javascript

目錄

簡介

Node是JavaScript語言的服務器運行環境。html

所謂「運行環境」有兩層意思:首先,JavaScript語言經過Node在服務器運行,在這個意義上,Node有點像JavaScript虛擬機;其次,Node提供大量工具庫,使得JavaScript語言與操做系統互動(好比讀寫文件、新建子進程),在這個意義上,Node又是JavaScript的工具庫。java

Node內部採用Google公司的V8引擎,做爲JavaScript語言解釋器;經過自行開發的libuv庫,調用操做系統資源。node

安裝與更新

訪問官方網站nodejs.org或者github.com/nodesource/distributions,查看Node的最新版本和安裝方法。git

官方網站提供編譯好的二進制包,能夠把它們解壓到/usr/local目錄下面。github

$ tar -xf node-someversion.tgz 

而後,創建符號連接,把它們加到$PATH變量裏面的路徑。shell

$ ln -s /usr/local/node/bin/node /usr/local/bin/node $ ln -s /usr/local/node/bin/npm /usr/local/bin/npm 

下面是Ubuntu和Debian下面安裝Deb軟件包的安裝方法。npm

$ curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash - $ sudo apt-get install -y nodejs $ apt-get install nodejs 

安裝完成之後,運行下面的命令,查看是否能正常運行。json

$ node --version # 或者 $ node -v 

更新node.js版本,能夠經過node.js的n模塊完成。api

$ sudo npm install n -g $ sudo n stable 

上面代碼經過n模塊,將node.js更新爲最新發布的穩定版。

n模塊也能夠指定安裝特定版本的node。

$ sudo n 0.10.21 

版本管理工具nvm

若是想在同一臺機器,同時安裝多個版本的node.js,就須要用到版本管理工具nvm。

$ git clone https://github.com/creationix/nvm.git ~/.nvm $ source ~/.nvm/nvm.sh 

安裝之後,nvm的執行腳本,每次使用前都要激活,建議將其加入~/.bashrc文件(假定使用Bash)。激活後,就能夠安裝指定版本的Node。

# 安裝最新版本 $ nvm install node # 安裝指定版本 $ nvm install 0.12.1 # 使用已安裝的最新版本 $ nvm use node # 使用指定版本的node $ nvm use 0.12 

nvm也容許進入指定版本的REPL環境。

$ nvm run 0.12 

若是在項目根目錄下新建一個.nvmrc文件,將版本號寫入其中,就只輸入nvm use命令便可,再也不須要附加版本號。

下面是其餘常常用到的命令。

# 查看本地安裝的全部版本 $ nvm ls # 查看服務器上全部可供安裝的版本。 $ nvm ls-remote # 退出已經激活的nvm,使用deactivate命令。 $ nvm deactivate 

基本用法

安裝完成後,運行node.js程序,就是使用node命令讀取JavaScript腳本。

當前目錄的demo.js腳本文件,能夠這樣執行。

$ node demo # 或者 $ node demo.js 

使用-e參數,能夠執行代碼字符串。

$ node -e 'console.log("Hello World")' Hello World 

REPL環境

在命令行鍵入node命令,後面沒有文件名,就進入一個Node.js的REPL環境(Read–eval–print loop,」讀取-求值-輸出」循環),能夠直接運行各類JavaScript命令。

$ node > 1+1 2 > 

若是使用參數 –use_strict,則REPL將在嚴格模式下運行。

$ node --use_strict 

REPL是Node.js與用戶互動的shell,各類基本的shell功能均可以在裏面使用,好比使用上下方向鍵遍歷曾經使用過的命令。

特殊變量下劃線(_)表示上一個命令的返回結果。

> 1 + 1 2 > _ + 1 3 

在REPL中,若是運行一個表達式,會直接在命令行返回結果。若是運行一條語句,就不會有任何輸出,由於語句沒有返回值。

> x = 1 1 > var x = 1

上面代碼的第二條命令,沒有顯示任何結果。由於這是一條語句,不是表達式,因此沒有返回值。

異步操做

Node採用V8引擎處理JavaScript腳本,最大特色就是單線程運行,一次只能運行一個任務。這致使Node大量採用異步操做(asynchronous opertion),即任務不是立刻執行,而是插在任務隊列的尾部,等到前面的任務運行完後再執行。

因爲這種特性,某一個任務的後續操做,每每採用回調函數(callback)的形式進行定義。

var isTrue = function(value, callback) { if (value === true) { callback(null, "Value was true."); } else { callback(new Error("Value is not true!")); } }

上面代碼就把進一步的處理,交給回調函數callback。

Node約定,若是某個函數須要回調函數做爲參數,則回調函數是最後一個參數。另外,回調函數自己的第一個參數,約定爲上一步傳入的錯誤對象。

var callback = function (error, value) { if (error) { return console.log(error); } console.log(value); }

上面代碼中,callback的第一個參數是Error對象,第二個參數纔是真正的數據參數。這是由於回調函數主要用於異步操做,當回調函數運行時,前期的操做早結束了,錯誤的執行棧早就不存在了,傳統的錯誤捕捉機制try…catch對於異步操做行不通,因此只能把錯誤交給回調函數處理。

try { db.User.get(userId, function(err, user) { if(err) { throw err } // ... }) } catch(e) { console.log(Oh no!); } 

上面代碼中,db.User.get方法是一個異步操做,等到拋出錯誤時,可能它所在的try…catch代碼塊早就運行結束了,這會致使錯誤沒法被捕捉。因此,Node統一規定,一旦異步操做發生錯誤,就把錯誤對象傳遞到回調函數。

若是沒有發生錯誤,回調函數的第一個參數就傳入null。這種寫法有一個很大的好處,就是說只要判斷回調函數的第一個參數,就知道有沒有出錯,若是不是null,就確定出錯了。另外,這樣還能夠層層傳遞錯誤。

if(err) { // 除了放過No Permission錯誤意外,其餘錯誤傳給下一個回調函數 if(!err.noPermission) { return next(err); } } 

全局對象和全局變量

Node提供如下幾個全局對象,它們是全部模塊均可以調用的。

  • global:表示Node所在的全局環境,相似於瀏覽器的window對象。須要注意的是,若是在瀏覽器中聲明一個全局變量,其實是聲明瞭一個全局對象的屬性,好比var x = 1等同於設置window.x = 1,可是Node不是這樣,至少在模塊中不是這樣(REPL環境的行爲與瀏覽器一致)。在模塊文件中,聲明var x = 1,該變量不是global對象的屬性,global.x等於undefined。這是由於模塊的全局變量都是該模塊私有的,其餘模塊沒法取到。

  • process:該對象表示Node所處的當前進程,容許開發者與該進程互動。

  • console:指向Node內置的console模塊,提供命令行環境中的標準輸入、標準輸出功能。

Node還提供一些全局函數。

  • setTimeout():用於在指定毫秒以後,運行回調函數。實際的調用間隔,還取決於系統因素。間隔的毫秒數在1毫秒到2,147,483,647毫秒(約24.8天)之間。若是超過這個範圍,會被自動改成1毫秒。該方法返回一個整數,表明這個新建定時器的編號。
  • clearTimeout():用於終止一個setTimeout方法新建的定時器。
  • setInterval():用於每隔必定毫秒調用回調函數。因爲系統因素,可能沒法保證每次調用之間正好間隔指定的毫秒數,但只會多於這個間隔,而不會少於它。指定的毫秒數必須是1到2,147,483,647(大約24.8天)之間的整數,若是超過這個範圍,會被自動改成1毫秒。該方法返回一個整數,表明這個新建定時器的編號。
  • clearInterval():終止一個用setInterval方法新建的定時器。
  • require():用於加載模塊。
  • Buffer():用於操做二進制數據。

Node提供兩個全局變量,都以兩個下劃線開頭。

  • __filename:指向當前運行的腳本文件名。
  • __dirname:指向當前運行的腳本所在的目錄。

除此以外,還有一些對象其實是模塊內部的局部變量,指向的對象根據模塊不一樣而不一樣,可是全部模塊都適用,能夠看做是僞全局變量,主要爲module, module.exports, exports等。

模塊化結構

概述

Node.js採用模塊化結構,按照CommonJS規範定義和使用模塊。模塊與文件是一一對應關係,即加載一個模塊,實際上就是加載對應的一個模塊文件。

require命令用於指定加載模塊,加載時能夠省略腳本文件的後綴名。

var circle = require('./circle.js'); // 或者 var circle = require('./circle');

require方法的參數是模塊文件的名字。它分紅兩種狀況,第一種狀況是參數中含有文件路徑(好比上例),這時路徑是相對於當前腳本所在的目錄,第二種狀況是參數中不含有文件路徑,這時Node到模塊的安裝目錄,去尋找已安裝的模塊(好比下例)。

var bar = require('bar');

有時候,一個模塊自己就是一個目錄,目錄中包含多個文件。這時候,Node在package.json文件中,尋找main屬性所指明的模塊入口文件。

{ "name" : "bar", "main" : "./lib/bar.js" }

上面代碼中,模塊的啓動文件爲lib子目錄下的bar.js。當使用require('bar')命令加載該模塊時,實際上加載的是./node_modules/bar/lib/bar.js文件。下面寫法會起到一樣效果。

var bar = require('bar/lib/bar.js') 

若是模塊目錄中沒有package.json文件,node.js會嘗試在模塊目錄中尋找index.js或index.node文件進行加載。

模塊一旦被加載之後,就會被系統緩存。若是第二次還加載該模塊,則會返回緩存中的版本,這意味着模塊實際上只會執行一次。若是但願模塊執行屢次,則可讓模塊返回一個函數,而後屢次調用該函數。

核心模塊

若是隻是在服務器運行JavaScript代碼,用處並不大,由於服務器腳本語言已經有不少種了。Node.js的用處在於,它自己還提供了一系列功能模塊,與操做系統互動。這些核心的功能模塊,不用安裝就可使用,下面是它們的清單。

  • http:提供HTTP服務器功能。
  • url:解析URL。
  • fs:與文件系統交互。
  • querystring:解析URL的查詢字符串。
  • child_process:新建子進程。
  • util:提供一系列實用小工具。
  • path:處理文件路徑。
  • crypto:提供加密和解密功能,基本上是對OpenSSL的包裝。

上面這些核心模塊,源碼都在Node的lib子目錄中。爲了提升運行速度,它們安裝時都會被編譯成二進制文件。

核心模塊老是最優先加載的。若是你本身寫了一個HTTP模塊,require('http')加載的仍是核心模塊。

自定義模塊

Node模塊採用CommonJS規範。只要符合這個規範,就能夠自定義模塊。

下面是一個最簡單的模塊,假定新建一個foo.js文件,寫入如下內容。

// foo.js module.exports = function(x) { console.log(x); };

上面代碼就是一個模塊,它經過module.exports變量,對外輸出一個方法。

這個模塊的使用方法以下。

// index.js var m = require('./foo'); m("這是自定義模塊");

上面代碼經過require命令加載模塊文件foo.js(後綴名省略),將模塊的對外接口輸出到變量m,而後調用m。這時,在命令行下運行index.js,屏幕上就會輸出「這是自定義模塊」。

$ node index 這是自定義模塊

module變量是整個模塊文件的頂層變量,它的exports屬性就是模塊向外輸出的接口。若是直接輸出一個函數(就像上面的foo.js),那麼調用模塊就是調用一個函數。可是,模塊也能夠輸出一個對象。下面對foo.js進行改寫。

// foo.js var out = new Object(); function p(string) { console.log(string); } out.print = p; module.exports = out;

上面的代碼表示模塊輸出out對象,該對象有一個print屬性,指向一個函數。下面是這個模塊的使用方法。

// index.js var m = require('./foo'); m.print("這是自定義模塊");

上面代碼表示,因爲具體的方法定義在模塊的print屬性上,因此必須顯式調用print屬性。

異常處理

Node是單線程運行環境,一旦拋出的異常沒有被捕獲,就會引發整個進程的崩潰。因此,Node的異常處理對於保證系統的穩定運行很是重要。

通常來講,Node有三種方法,傳播一個錯誤。

  • 使用throw語句拋出一個錯誤對象,即拋出異常。
  • 將錯誤對象傳遞給回調函數,由回調函數負責發出錯誤。
  • 經過EventEmitter接口,發出一個error事件。

try…catch結構

最經常使用的捕獲異常的方式,就是使用try…catch結構。可是,這個結構沒法捕獲異步運行的代碼拋出的異常。

try { process.nextTick(function () { throw new Error("error"); }); } catch (err) { //can not catch it console.log(err); } try { setTimeout(function(){ throw new Error("error"); },1) } catch (err) { //can not catch it console.log(err); } 

上面代碼分別用process.nextTick和setTimeout方法,在下一輪事件循環拋出兩個異常,表明異步操做拋出的錯誤。它們都沒法被catch代碼塊捕獲,由於catch代碼塊所在的那部分已經運行結束了。

一種解決方法是將錯誤捕獲代碼,也放到異步執行。

function async(cb, err) { setTimeout(function() { try { if (true) throw new Error("woops!"); else cb("done"); } catch(e) { err(e); } }, 2000) } async(function(res) { console.log("received:", res); }, function(err) { console.log("Error: async threw an exception:", err); }); // Error: async threw an exception: Error: woops! 

上面代碼中,async函數異步拋出的錯誤,能夠一樣部署在異步的catch代碼塊捕獲。

這兩種處理方法都不太理想。通常來講,Node只在不多場合才用try/catch語句,好比使用JSON.parse解析JSON文本。

回調函數

Node採用的方法,是將錯誤對象做爲第一個參數,傳入回調函數。這樣就避免了捕獲代碼與發生錯誤的代碼不在同一個時間段的問題。

fs.readFile('/foo.txt', function(err, data) { if (err !== null) throw err; console.log(data); }); 

上面代碼表示,讀取文件foo.txt是一個異步操做,它的回調函數有兩個參數,第一個是錯誤對象,第二個是讀取到的文件數據。若是第一個參數不是null,就意味着發生錯誤,後面代碼也就再也不執行了。

下面是一個完整的例子。

function async2(continuation) { setTimeout(function() { try { var res = 42; if (true) throw new Error("woops!"); else continuation(null, res); // pass 'null' for error } catch(e) { continuation(e, null); } }, 2000); } async2(function(err, res) { if (err) console.log("Error: (cps) failed:", err); else console.log("(cps) received:", res); }); // Error: (cps) failed: woops! 

上面代碼中,async2函數的回調函數的第一個參數就是一個錯誤對象,這是爲了處理異步操做拋出的錯誤。

EventEmitter接口的error事件

發生錯誤的時候,也能夠用EventEmitter接口拋出error事件。

var EventEmitter = require('events').EventEmitter; var emitter = new EventEmitter(); emitter.emit('error', new Error('something bad happened')); 

使用上面的代碼必須當心,由於若是沒有對error事件部署監聽函數,會致使整個應用程序崩潰。因此,通常老是必須同時部署下面的代碼。

emitter.on('error', function(err) { console.error('出錯:' + err.message); }); 

uncaughtException事件

當一個異常未被捕獲,就會觸發uncaughtException事件,能夠對這個事件註冊回調函數,從而捕獲異常。

var logger = require('tracer').console(); process.on('uncaughtException', function(err) { console.error('Error caught in uncaughtException event:', err); }); try { setTimeout(function(){ throw new Error("error"); },1); } catch (err) { //can not catch it console.log(err); } 

只要給uncaughtException配置了回調,Node進程不會異常退出,但異常發生的上下文已經丟失,沒法給出異常發生的詳細信息。並且,異常可能致使Node不能正常進行內存回收,出現內存泄露。因此,當uncaughtException觸發後,最好記錄錯誤日誌,而後結束Node進程。

process.on('uncaughtException', function(err) { logger.log(err); process.exit(1); }); 

unhandledRejection事件

iojs有一個unhandledRejection事件,用來監聽沒有捕獲的Promise對象的rejected狀態。

var promise = new Promise(function(resolve, reject) { reject(new Error("Broken.")); }); promise.then(function(result) { console.log(result); }) 

上面代碼中,promise的狀態變爲rejected,而且拋出一個錯誤。可是,不會有任何反應,由於沒有設置任何處理函數。

只要監聽unhandledRejection事件,就能解決這個問題。

process.on('unhandledRejection', function (err, p) { console.error(err.stack); }) 

須要注意的是,unhandledRejection事件的監聽函數有兩個參數,第一個是錯誤對象,第二個是產生錯誤的promise對象。這能夠提供不少有用的信息。

var http = require('http'); http.createServer(function (req, res) { var promise = new Promise(function(resolve, reject) { reject(new Error("Broken.")) }) promise.info = {url: req.url} }).listen(8080) process.on('unhandledRejection', function (err, p) { if (p.info && p.info.url) { console.log('Error in URL', p.info.url) } console.error(err.stack) }) 

上面代碼會在出錯時,輸出用戶請求的網址。

Error in URL /testurl Error: Broken. at /Users/mikeal/tmp/test.js:9:14 at Server.<anonymous> (/Users/mikeal/tmp/test.js:4:17) at emitTwo (events.js:87:13) at Server.emit (events.js:169:7) at HTTPParser.parserOnIncoming [as onIncoming] (_http_server.js:471:12) at HTTPParser.parserOnHeadersComplete (_http_common.js:88:23) at Socket.socketOnData (_http_server.js:322:22) at emitOne (events.js:77:13) at Socket.emit (events.js:166:7) at readableAddChunk (_stream_readable.js:145:16) 

命令行腳本

node腳本能夠做爲命令行腳本使用。

$ node foo.js 

上面代碼執行了foo.js腳本文件。

foo.js文件的第一行,若是加入瞭解釋器的位置,就能夠將其做爲命令行工具直接調用。

#!/usr/bin/env node 

調用前,需更改文件的執行權限。

$ chmod u+x foo.js $ ./foo.js arg1 arg2 ... 

做爲命令行腳本時,console.log用於輸出內容到標準輸出,process.stdin用於讀取標準輸入,child_process.exec()用於執行一個shell命令。

參考連接

 | last modified on 2013-12-04

相關文章
相關標籤/搜索