node.js概述

關於node.js

Node.js 是服務器端的 JavaScript 運行環境,它具有無阻塞(non-blocking)和事件驅動(event-driven)等的特色,Node.js 採用V8引擎,同樣,Node.js實現了類似 Apache 和 nginx 的web服務,讓你可以通過它來搭建基於 JavaScript的Web App。
Node.js採用C++語言編寫而成,是一個跨平臺的Javascript的運行環境(解釋器),這意味着你可以編寫系統級或者服務器端的Javascript代碼,交給Node.js來解釋執行。node.js是對Google V8引擎的封裝和優化。V8引擎執行Javascript的速度非常快,性能非常好。同時還提供了很多系統級的API,如文件操作、網絡編程等。瀏覽器端的Javascript代碼在運行時會受到各種安全性的限制,對客戶系統的操作有限。相比之下,Node.js則是一個全面的後臺運行時,爲Javascript提供了其他語言能夠實現的許多功能。
node.js使得javascript擴展到服務器端,統一了WEB開發前後端的語言。
Node.js採用事件驅動、異步編程,爲網絡服務而設計。重要的優勢在於,充分利用了系統資源,執行代碼無須阻塞等待某種操作完成,有限的資源可以用於其他的任務。此類設計非常適合於後端的網絡服務編程。
node.js有大量的插件,社區蓬勃,可以實現從web服務器到文檔服務器到命令行工具等五花八門的功能,實爲全能平臺。
缺陷:
node.js是爲服務器端響應大量用戶的併發請求設計的,對於單用戶/多任務型應用不太理想。比如在提供圖形界面的同時進行某些計算。node.js適用於I/O密集型而不是計算你密集型應用。受制於它的單線程特點,對於計算密集型的應用,例如某個事件的回調函數要進行復雜的計算,那麼事件循環中的所有請求都必須等待。另外,由於node.js的控制流不是線性的,它被一個個事件拆散,所以,在處理複雜的邏輯時會變得過於複雜。
綜上所述,node.js適用於邏輯簡單但訪問頻繁的任務:
(1)提供web server;
(2)訪問數據庫;
(3)訪問文件系統(讀寫磁盤);
(4)與操作系統交互;
(5)運行C/C++擴展。
總之,就是後端I/O的部分。不是I/O的部分,包括業務邏輯的執行,應該交給前端MVC框架來完成。

學習node.js主要就是學習它的API。


node.js下的編程規範

1.兩格縮進:以應付過多的嵌套。
2.使用尾括號,如:
function func(boolVar) {
if (boolVar) {
console.log('True');
} else {
console.log('False');
}
};
3.行寬:爲了保證在任何設備上都可以方便地閱讀,建議把行寬限制爲80個字符。
4.永遠使用var 定義變量。確保每個語句定義一個變量,不要使用逗號同時定義多個變量。
5.使用小駝峯法命名所有變量、函數和屬性。下橫線命名所有事件。但類的名字使用大駝峯法。
6.引號:建議需要使用引號的地方一律使用雙引號。因爲JSON指定使用雙引號。
7.相等:在JS中使用===代表等號,在CS中使用==。
8.回調函數:回調函數的名字在CS中一律用cb代表。node的API和其他第三方模塊大多約定回調函數的第一個參數是err,因爲錯誤肯定會被輸出,所以不用顯式地寫if(err)...語句了。除非你要顯示專門的出錯信息。

9.所有代碼都預先編譯爲javascript再放到node.js下運行。

node.js的安裝

首先要選擇node.js的版本。node.gyp(編譯C/C++擴展的工具)對版本有一定要求(v0.8.9),但考慮到它只是在編譯擴展時採用,而且最好安裝在目標平臺上(Windows/Linux/MacOS)用。所以,平時就放心大膽地用node的最新版本好了(基本是在Ubuntu下開發)。
自動安裝:現代的ubuntu系統如12.0都可以通過軟件倉庫來自動安裝node.js.或用命令apt-get install nodejs
手動安裝:登陸官網http://nodejs.org, 下載最新安裝包。解壓到/software文件夾裏,執行標準編譯安裝命令:
$ ./configure
$ make
$ sudo make install
5分鐘左右就編譯安裝完了,已自帶npm。(也可以通過make uninstall 卸載)
前提是系統中有編譯器(如G++)、python(2.6+)等編譯環境。參見《nodeJS+開發指南》。
由於可編譯擴展的老版本,又需要功能最多的新版本,所以要在一個系統上保持多個node.js版。這時可使用多版本包管理器--n。官網https://github.com/visionmedia/n
如果你已經安裝好了 Node.js 和 npm 環境,就可以直接使用 npm install -g n 命令來安裝 n。
在終端中運行 n --help 即可看到它的使用說明。運行sudo n 顯示所有已安裝的node.js版本。在終端中運行 n 0.10.5 即可切換到node v0.10.5。
進入命令行模式: 直接輸入node回車即可。
運行文件:輸入node 文件名即可。

在node.js中運行和組織coffeescript
利用coffeescript的監控工具實時把CS代碼轉化爲JS代碼,在node.js中只用JS代碼(因爲要與其他第三方庫合作以及模塊之間調用的問題)


node.js的編程要領

異步式IO和事件驅動。
傳統服務器端的問題:阻塞(blocking)
比如,在整個數據查詢的過程中,當前程序進程往往只是在等待結果的返回,這就造成了進程的阻塞。對於高並 發,I/O密集行的網絡應用中,一方面進程很長時間處於等待狀態,另一方面爲了應付新的請求不斷的增加新的進程。這樣的浪費會導致系統支持的用戶數遠遠小於後端數據服務能夠支撐的,成爲了系統的瓶頸。而且這樣的系統也特別容易被慢鏈接攻擊(客戶端故意不接收或減緩接收數據,加長進程等待時間)。
Node.js 最大的特點就是異步式 I/O(或者非阻塞 I/O)與事件緊密結合的編程模式。爲了解決阻塞問題,Node.js引入事件處理機制解決這個問題。在查詢請求發起之前註冊數據加載事件的響應函數,請求發出之後立即將進程交出,而當數據返回後再觸發這個事件並在預定好的事件響應函數中繼續處理數據。 我們看到若按照這個思路解決阻塞問題,首先我們要提供一套高效的異步事件調度機制。而主要用於處理瀏覽器端的各種交互事件的JavaScript,相對於其他語言,至少有兩個關鍵點特別適合完成這個任務:一是它是函數式語言,這個特性使得爲事件指定回調函數變得很容易。特別是JavaScript還支持匿名函數。二是它支持閉包,可在函數運行時保持上下文狀態。
實際上node.js的各種API都會發出各種事件,使我們指定的回調函數在接收到事件時會被自動調用。而閉包可以讓回調函數在運行時能夠訪問到主函數定義時的所處作用域內的所有變量。
我們看到node.js通過JavaScript函數式語言特性、匿名函數支持和閉包很漂亮的解決了同步編程到異步編程轉化過程中遇到的一系列最重要的問題。但node.js是否就是最好的?非也,node.js的主要問題是:(1)閉包保持的變量耗費內存;(2)異步編程在邏輯上不太好理解(這個問題後面還要說)。
異步編程模式與傳統的同步式 I/O 線性的編程思路有很大的不同,因爲控制流很大程度上要靠事件和回調函數來組織,一個邏輯要拆分爲若干個單元。Node.js 的異步編程接口習慣是以函數的最後一個參數爲回調函數,通常一個函數只有一個回調函數。回調函數是實際參數中第一個是 err,其餘的參數是其他返回的內容。如果沒有發生錯誤,err 的值會是 null 或undefined。如果有錯誤發生,err 通常是 Error 對象的實例。


事件驅動 events

events 是 Node.js 最重要的模塊,沒有“之一”,原因是 Node.js 本身架構就是事件式的,而它提供了唯一的接口,所以堪稱 Node.js 事件編程的基石。events 模塊不僅用於用戶代碼與 Node.js 下層事件循環的交互,還幾乎被所有的模塊依賴。
事件發射器
events 模塊只提供了一個對象: events.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) 爲指定事件註冊一個監聽器,接受一個字
符串 event 和一個回調函數 listener。
 EventEmitter.emit(event, [arg1], [arg2], [...]) 發射 event 事件,傳
遞若干可選參數到事件監聽器的參數表。
 EventEmitter.once(event, listener) 爲指定事件註冊一個單次監聽器,即
監聽器最多隻會觸發一次,觸發後立刻解除該監聽器。
 EventEmitter.removeListener(event, listener) 移除指定事件的某個監聽
器,listener 必須是該事件已經註冊過的監聽器。
4.4 文件系統 fs
65
 EventEmitter.removeAllListeners([event]) 移除所有事件的所有監聽器,
如果指定 event,則移除指定事件的所有監聽器。
大多數時候我們不會直接使用 EventEmitter,而是在對象中繼承它。包括 fs、net、http 在內的,只要是支持事件響應的核心模塊都是 EventEmitter 的子類。
爲什麼要這樣做呢?原因有兩點。首先,具有某個實體功能的對象實現事件符合語義,事件的監聽和發射應該是一個對象的方法。其次 JavaScript 的對象機制是基於原型的,支持部分多重繼承,繼承 EventEmitter 不會打亂對象原有的繼承關係。
EventEmitter 定義了一個特殊的事件 error,它包含了“錯誤”的語義,我們在遇到異常的時候通常會發射 error 事件。當 error 被髮射時,EventEmitter 規定如果沒有響應的監聽器,Node.js 會把它當作異常,退出程序並打印調用棧。
我們一般要爲會發射 error事件的對象設置監聽器,避免遇到錯誤後整個程序崩潰。

異步編程的問題

node.js的大量I/O操作是異步的,異步會帶來一個大問題:第一個操作還沒有執行完就先執行了第二個,如果第二個操作依賴於第一個操作的結果,或者在執行順序上有嚴格的要求,那就會產生錯誤。比如還沒有讀取完文件內容就關閉了文件。
解決方案是要麼採用同步版本(sync),要麼採用函數嵌套的形式。
下面採用嵌套的方式打開一個文件,獲取其文件描述符,讀出其內容,再關閉之。
var fs = require('fs');


fs.open('file.txt', flages = 'r', function(err,fd) {
    if (err) throw error;
    else {
console.log("opened.");
var id = fd;
fs.readFile('file.txt', 'utf-8', function(err, data) {
   if (err) throw error;
   else {
console.log(data);
fs.close(id, function(err) {
           if (err) throw error;
           else {
       console.log("file closeed.");
                    }
                });
            }
});
    }
});
可見,fs.open中嵌套着fs.readFile,裏面又嵌套着fs.close。這只是個三層的嵌套。
當操作數據庫時,經常要執行更多的順序操作:打開數據庫-打開集合-查找記錄-查找另一條記錄-將兩條記錄的值進行某種運算-寫入數據庫-關閉數據庫。這如果用嵌套的寫法,會形成非常深的嵌套。如:
var p_client = new Db('integration_tests_20', new Server("127.0.0.1", 27017, {}), {'pk':CustomPKFactory});
p_client.open(function(err, p_client) {
  p_client.dropDatabase(function(err, done) {
    p_client.createCollection('test_custom_key', function(err, collection) {
      collection.insert({'a':1}, function(err, docs) {
        collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, function(err, cursor) {
          cursor.toArray(function(err, items) {
            test.assertEquals(1, items.length);
            p_client.close();
          });
        });
      });
    });
  });
});
除了深層嵌套不直觀外,這也不利於單元測試。還有循環陷阱的問題,即循環還沒有執行完,就跳到後面的代碼去執行了。這個問題可以用加計數器來解決,如:
var fs = require('fs');


fs.readdir(".", function (err, files) {
  var count = files.length,
      results = {};
  files.forEach(function (filename) {
    fs.readFile(filename, function (data) {
      results[filename] = data;
      count--;
      if (count <= 0) {
        // Do something once we know all the files are read.
      }
    });
  });
});
利用計數器count確保所有的文件都打開了才做something else。
但更嚴重的問題是在異步編程下,傳統的順序執行邏輯被打散,程序的邏輯關係不太容易被看清。是否考慮使用Jscex這類類庫?參見http://www.infoq.com/cn/articles/jscex-javascript-asynchronous-programming
總結的技巧包括:
在異步編程中,需要把依賴於異步函數(需要其執行結果或者達到某種狀態)的代碼放在對應的回調函數中。
異步函數後面的代碼會立即執行,所以在編程時需要通盤考慮,以免出現意外之外的運行結果。
併發運行的相同異步函數如果協作完成任務,需要添加代碼判斷執行狀態,並且需要把所有異步函數完成後執行的代碼放在判斷條件的語句塊裏。
對於異步函數的順序循環處理(目的是代碼複用)可以通過定時器機制或者事件回調函數等方法來實現,但不能採用傳統的循環語句模式。
“函數套函數”(通常是異步函數)的方式需要開發人員對代碼結構有清晰的理解,以免造成代碼編寫錯誤,如在內部異步函數中試圖影響外部函數的執行等問題。


node.js的API

最重要的就是學習API。在node.js官網上列舉了如下API:
Assertion Testing   用於單元測試
Buffer              用於操作二進制流數據的緩存
C/C++ Addons        C/C++擴展
Child Processes     創建子進程
Cluster             創建node進程簇(實現類似多進程的功能)
Crypto              用於創建證書(用於安全連接/加解密等)
Debugger            node的debug工具
DNS                 DNS解析工具
Domain              將不同的IO操作打包成一組
Events              事件工具
File System         對文件系統的操作
Globals             全局對象
HTTP                高效的web服務器和客戶端,提供HTTP服務
HTTPS               安全HTTPS 
Modules             模塊解決方案
Net                 異步的操作網絡端口的方案
OS                  提供操作系統信息
Path                操作和轉換文件路徑
Process             進程接口,可控制進程和了解進程的各種狀況
Punycode            用於處理punycode編碼。
Query Strings       用於操作字符串查詢
Readline            用於逐行讀取流中的文本
REPL                可交互式運行javascript的工具(常用於調試)
STDIO               標準輸出入(如打印到控制檯的console.log語句等)
Stream              用於操作流
String Decoder      將buffer中的內容解碼爲string
Timers              定時器功能
TLS/SSL             使用openSSL提供傳輸層的安全
TTY                 控制檯操作
UDP/Datagram        用於操作UDP傳輸(使用datagram socket)
URL                 解析URL
Utilities           各種工具集
VM                  用於編譯和運行javascript
ZLIB                用於壓縮和解壓縮文件(Gzip/Gunzip格式)


Global 全局對象

JavaScript 中有一個特殊的對象,稱爲全局對象(Global Object)
,它及其所有屬性都可以在程序的任何地方訪問,即全局變量。在瀏覽器 JavaScript 中,通常 window 是全局對象,
而 Node.js 中的全局對象是 global,所有全局變量(除了 global 本身以外)都是 global對象的屬性。
我們在 Node.js 中能夠直接訪問到對象通常都是 global 的屬性, 如console、process、require等。


console 打印接口

最常用的輸出信息的方法。
console.log 同 console.info   向標準輸出(stdout)輸出信息
可以使用console.log('count: %d', count);這樣的形式輸出變量。

console.error 同 console.warn 向標準錯誤(stderr)輸出信息

console.time(label)  和 console.timeEnd(label)
對一個事件啓動計時器,如下面的例子:
console.time('100-elements');
for (var i = 0; i < 100; i++) {
  console.log('count:%d', i);
}
console.timeEnd('100-elements');
對一個打印事件啓動計時器,最後統計出打印100次的事件是5ms。


OS 操作系統接口

提供了訪問操作系統的基本功能函數。
os.platform()  返回操作系統平臺的名字(如Linux, Windows)
os.type() 返回操作系統的名字
os.hostname() 返回主機名
os.arch() 返回體系架構(如ia32)
os.release() 返回操作系統內核版本號(如2.6.32)
os.cpus() 返回每核CPU的參數(型號/主頻/用於基礎進程的毫秒數等)
os.totalmem() 返回總內存數
os.freemem() 返回自由內存數
os.networkInterfaces() 返回網絡接口(網卡的名稱/地址/IP等)
os.EOL 返回系統換行符(如 '\n')
os.tmpdir()返回系統臨時文件的文件夾
os.endianness() 返回系統大小尾特徵(如'LE')


Porcess 進程接口

process.pid  返回進程的ID

process.version 返回當前node的版本

process.versions 返回當前node及其各個依賴項的版本
{ http_parser: '1.0',
  node: '0.10.4',
  v8: '3.14.5.8',
  ares: '1.9.0-DEV',
  uv: '0.10.3',
  zlib: '1.2.3',
  modules: '11',
  openssl: '1.0.1e' }


process.execPath 返回node.js的執行文件的路徑
如:/usr/local/bin/node


process.cwd() 返回當前目錄
如:/home/swedepan/workspace/nodetest


process.config 返回當前編譯環境(即config.gypi設定的環境)


process.env 返回當前系統環境設置(很複雜)


process.memoryUsuage() 返回node對於內存的使用
如: rss: 4935680,
  heapTotal: 1826816,
  heapUsed: 650472 }
heapTotal 和 heapUsed 代表 V8 的內存使用。


process.setuid() 和process.getuid() 設置和返回進程的用戶ID(只對POSIX系統有效)
如:
if (process.getuid && process.setuid) {
  console.log('Current uid: ' + process.getuid());
  try {
    process.setuid(501);
    console.log('New uid: ' + process.getuid());
  }
  catch (err) {
    console.log('Failed to set uid: ' + err);
  }
}


process.exit(code) 退出進程,返回code。
如:process.exit(1); 將退出進程後返回1.


process.kill(pid,[signal]) 殺死某進程,返回一個信號
如:
process.on('SIGHUP', function() {
  console.log('Got SIGHUP signal.');
});


setTimeout(function() {
  console.log('Exiting.');
  process.exit(0);
}, 100);


process.kill(process.pid, 'SIGHUP');


process.chdir(directory)切換當前目錄到指定目錄


process.stdin() 讀取標準輸入
process.stdout() 寫入標準輸出
如:
process.stdin.resume();  //必須現有這句開啓stdin
process.stdin.setEncoding('utf8');  //適合輸入中文
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 doFirst(args, callback) {
somethingComplicated(args);
callback();
}
doSecond(function onEnd() {
compute();
});

我們假設 compute() 和 somethingComplicated() 是兩個較爲耗時的函數,以上
的程序在調用 doFirst() 時會先執行 somethingComplicated(),然後立即調用
回調函數,在 onEnd() 中又會執行 compute()。下面用 process.nextTick() 改寫上
面的程序:
function doFirst(args, callback) {
somethingComplicated(args);
process.nextTick(callback);
}
doSecond(function onEnd() {
compute();
});
改寫後的程序會把上面耗時的操作拆分爲兩個事件,減少每個事件的執行時間,提高事件響應速度。不要使用 setTimeout(fn,0)代替 process.nextTick(callback),
前者比後者效率要低得多。


File System 文件系統

fs 模塊是文件操作的封裝,它提供了文件的讀取、寫入、更名、刪除、遍歷目錄、鏈接等 POSIX 文件系統操作。與其他模塊不同的是,fs 模塊中所有的操作都提供了異步的和同 步 的 兩 個 版 本 , 例 如 讀 取 文 件 內 容 的 函 數 有 異 步 的 fs.readFile() 和 同 步 的fs.readFileSync()。我們以幾個函數爲代表,介紹 fs 常用的功能,並列出 fs 所有函數的定義和功能。

讀取文件 fs.read(fd,buffer,offset,length,position,[callback(err, bytesRead, buffer)]) 
        fs.readSync(fd, buffer, offset,length, position)

寫入文件 fs.write(fd,buffer,offset,length,position, [callback(err, bytesWritten, buffer)])
        fs.writeSync(fd, buffer, offset, length, position)
                 
讀取文件內容 fs.readFile(filename,[encoding],[callback(err, data)])
           fs.readFileSync(filename,[encoding])

寫入文件內容 fs.writeFile(filename, [callback(err)]) 
           fs.writeFileSync(filename, data, [encoding])

刪除文件 fs.unlink(path, [callback(err)]) 

創建目錄 fs.mkdir(path, [mode], [callback(err)]) 
       fs.mkdirSync(path, [mode])

刪除目錄 fs.rmdir(path, [callback(err)]) 
        fs.rmdirSync(path)

讀取目錄 fs.readdir(path, [callback(err, files)]) 
        fs.readdirSync(path)

獲取真實路徑 fs.realpath(path, [callback(err,resolvedPath)]) 
           fs.realpathSync(path)
                   
更名 fs.rename(path1, path2, [callback(err)]) 
     fs.renameSync(path1, path2)

其他fs命令見node API


下面詳解主要命令:
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' }
強大的readFile功能可以打開文本文件、JSON文件、圖片,但打開doc、pdf文件會亂碼。
fs.readFileSync是打開文件的同步版本,其用法是fs.readFileSync(filename, [coding]。如果編碼格式不指定,則以二進制模式打開。
注意,這就不用回調函數了。其值以函數返回值的形式返回。
result = fs.readFileSync("file.txt", "utf-8");
console.log(result);


寫文件 fs.writeFile(filename, data, [options], callback)
其中,data是要寫的數據(字符串),options是參數,包括encoding(默認是utf-8), mode(默認是0666),flag(默認是w)。例子:
var fs = require('fs');


fs.writeFile('file.txt', 'Hello Node', function (err) {
  if (err) throw err;
  console.log('It\'s saved!');
});


這樣會把整個文件重寫(全覆蓋)。如果要追加寫入,應用appendFile命令(\n表示換行):
fs.appendFile('file.txt', '\n data to append',  function (err) {
  if (err) throw err;
  console.log('It\'s saved!');
});


新建文件夾fs.mkdir(path, [mode], callback)
下面的例子在當前文件夾裏新建了一個文件夾
var fs = require('fs');


fs.mkdir('./new', function(err) {
    if (err) console.error(err);
    console.log('It\'s made!');
});
如果把裏面的fs.mkdir改成fs.rmdir就變成刪除文件夾了。注意:如果文件夾裏面有文件則無法刪除。


讀取文件夾 fs.readdir(path, callback)
這回調函數中有兩個參數err和fils,files是一個數組,包含了文件夾下所有的文件名(除了.和..文件)。
var fs = require('fs');
fs.readdir('./', function(err, files) {
    if (err) {
    console.error(err);
    }
    else
    console.log(files);
});
這個例子返回了當前文件夾下的所有文件,連同子文件夾。


fs.open 打開文件
fs.open(path, flags, [mode], [callback(err, fd)])是 POSIX open 函數的
封裝,與 C 語言標準庫中的 fopen 函數類似。它接受兩個必選參數,path 爲文件的路徑,
flags 可以是以下值。
r :以讀取模式打開文件。
r+ :以讀寫模式打開文件。
w :以寫入模式打開文件,如果文件不存在則創建。
w+ :以讀寫模式打開文件,如果文件不存在則創建。
a :以追加模式打開文件,如果文件不存在則創建。
a+ :以讀取追加模式打開文件,如果文件不存在則創建。
mode 參數用於創建文件時給文件指定權限,默認是 0666(所有者:讀寫,同組人:讀寫,其他用戶:讀寫)。
回調函數返回的是fd--文件描述符,內核(kernel)利用文件描述符(file descriptor)來訪問文件。文件描述符是非負整數。打開現存文件或新建文件時,內核會返回一個文件描述符。讀寫文件也需要使用文件描述符來指定待讀寫的文件。


文件關閉 fs.close(fd,callback) 注意:使用文件描述符來定位文件。


刪除文件 fs.unlink(path, callback)
下例刪除了hello.cc文件
var fs = require('fs');


fs.unlink('hello.cc', function(err) {
    if (err) console.error(err);
    console.log('It\'s removed!');
});


文件重命名 fs.rename(path1, path2, [callback])
var fs=require('fs');
fs.rename('D:/test.js','D:/test1.js',function(err){
    if(err){
       console.log(err);  
    }else{
       console.log('renamed complete');
     }
})


Path 路徑模塊



該模塊包括了一些處理文件路徑的功能,可以通過require('path')方法來使用它。該模塊提供瞭如下的方法:
(返回當前路徑要用process模塊中的process.cwd())


path.normalize(p)
該方法用於標準化一個字符型的路徑,請注意'..' 與 '.' 的使用。
當發現有多個斜槓(/)時,系統會將他們替換爲一個斜槓;如果路徑末尾中包含有一個斜槓,那麼系統會保留這個斜槓。在Windows中,上述路徑中的斜槓(/)要換成反斜槓(\)。


示例:


path.normalize('/foo/bar//baz/asdf/quux/..')
// returns
'/foo/bar/baz/asdf'


path.join([path1], [path2], [...])
該方法用於合併方法中的各參數並得到一個標準化合併的路徑字符串。


示例:


node> require('path').join(
...   '/foo', 'bar', 'baz/asdf', 'quux', '..')
'/foo/bar/baz/asdf'


path.resolve([from ...], to)
將to參數解析爲絕對路徑。
如果參數 to當前不是絕對的,系統會將from 參數按從右到左的順序依次前綴到to上,直到在from中找到一個絕對路徑時停止。如果遍歷所有from中的路徑後,系統依然沒有找到一個絕對路徑,那麼當前工作目錄也會作爲參數使用。最終得到的路徑是標準化的字符串,並且標準化時系統會自動刪除路徑末尾的斜槓,但是如果獲取的路徑是解析到根目錄的,那麼系統將保留路徑末尾的斜槓。
你也可以將這個方法理解爲Shell中的一組cd命令。


path.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile')
就類似於:


cd foo/bar
cd /tmp/file/
cd ..
cd a/../subfile
pwd


該方法與cd命令的區別在於該方法中不同的路徑不一定存在,而且這些路徑也可能是文件。
示例:
path.resolve('/foo/bar', './baz')
// returns
'/foo/bar/baz'
 
path.resolve('/foo/bar', '/tmp/file/')
// returns
'/tmp/file'
 
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif')
// if currently in /home/myself/node, it returns
'/home/myself/node/wwwroot/static_files/gif/image.gif'
path.dirname(p)
該方法返回一個路徑的目錄名,類似於Unix中的dirname命令。


示例:
path.dirname('/foo/bar/baz/asdf/quux')
// returns
'/foo/bar/baz/asdf'
path.basename(p, [ext])
該方法返回一個路徑中最低一級目錄名,類似於Unix中的 basename命令。


示例:
path.basename('/foo/bar/baz/asdf/quux.html')
// returns
'quux.html'
 
path.basename('/foo/bar/baz/asdf/quux.html', '.html')
// returns
'quux'
path.extname(p)
該方法返回路徑中的文件擴展名,即路徑最低一級的目錄中'.'字符後的任何字符串。如果路徑最低一級的目錄中'沒有'.' 或者只有'.',那麼該方法返回一個空字符串。

示例:
path.extname('index.html')
// returns
'.html'
 
path.extname('index')
// returns
''
path.exists(p, [callback])
該方法用於測試參數p中的路徑是否存在。然後以true 或者 false的方式調用callback參數。示例:


path.exists('/etc/passwd', function (exists) {
  util.debug(exists ? "it's there" : "no passwd!");
});


readline 提供處理用戶行級輸入的交互式接口

需要先require('readline');
如:
var readline = require('readline');


var rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});


rl.question('What is your favorite food?', function(answer) {
  console.log('Oh, so your favorite food is ' + answer);


  rl.close();
});
將提示用戶輸入他們喜愛的食物,然後將其答案打印出來。
用readline可以實現按命令菜單控制程序部分執行的調試系統。如:
var readline = require('readline'),
    rl = readline.createInterface(process.stdin, process.stdout);


rl.setPrompt('test> ');
rl.prompt();


rl.on('line', function(line) {
  switch(line.trim()) {
    case 'help':
      console.log('open db : 1');
      console.log('save data : 2');
      console.log('close db : 3');
      break;
    case '1':
      console.log('Now you are openning a database.');
      break;
    case '2':
      console.log('Save some data...');
      break;
    case '3':
      console.log('Droping a database.');
      break;
    default:
      console.log('Say what? This command is not exist `' + line.trim() + '`');
      console.log('open db : 1');
      console.log('save data : 2');
      console.log('close db : 3');
      break;
  }
  rl.prompt();
})
rl.on('SIGINT', function() {
  rl.question('Are you sure you want to exit?', function(answer) {
    if (answer.match(/^y(es)?$/i)) rl.close();
    else rl.prompt();
  });
});


這個小程序可以打印出命令菜單,然後根據用戶的選項執行某些命令。最後按^C退出。其中:
rl.setPrompt('test> ') 是設光標控制符顯示的樣式
rl.prompt();  進入讀入等待狀態,顯示光標
rl.on('line', function(line) 基於用戶的回車事件,執行line函數
rl.on('SIGINT', function() 基於用戶的^C事件,執行回調函數
上面這個例子的CS版本
readline = require("readline")
rl = readline.createInterface(process.stdin, process.stdout)


rl.setPrompt("test> ")
rl.prompt()


rl.on('line', cb = (line) ->
  switch(line.trim()) 
    when "help" 
      console.log """
                  open db   : 1
                  save data : 2
                  close db  : 3
                  """
    when "1" 
      console.log("Now you are openning a database.")
      
    when "2" 
      console.log("Save some data...")
      
    when "3" 
      console.log("Droping a database.")
      
    else
      console.log("Say what? This command is not exist `" + line.trim() + "`")
      console.log """
                  open db   : 1
                  save data : 2
                  close db  : 3
                  """
  rl.prompt()
)


rl.on('SIGINT', cb = () ->
  rl.question("Are you sure you want to exit?", cb = (answer) ->
    if (answer.match(/^y(es)?$/i)) 
      rl.close()
    else 
      rl.prompt()
  )
)

REPL Node自帶的控制檯工具

可生成交互式運行-解釋JS的窗口。還可集成在其他程序中使其帶有控制檯功能。
首先要require("repl")
下面的小程序實現了JS交互窗口功能
var repl = require("repl");
    
repl.start({
  prompt: "node via stdin> ",
  input: process.stdin,
  output: process.stdout
});
運行這個程序,在閃爍的node via stdin>後面輸入任何JS命令,回車就可以看結果了。
更酷的是,還可以和net模塊結合,實現通過TCP等端口通訊的遠程交互系統。
官網的例子:
var net = require("net"),
    repl = require("repl");


connections = 0;


repl.start({
  prompt: "node via stdin> ",
  input: process.stdin,
  output: process.stdout
});


net.createServer(function (socket) {
  connections += 1;
  repl.start({
    prompt: "node via Unix socket> ",
    input: socket,
    output: socket
  }).on('exit', function() {
    socket.end();
  })
}).listen("/tmp/node-repl-sock");


net.createServer(function (socket) {


  connections += 1;
  repl.start({
    prompt: "node via TCP socket> ",
    input: socket,
    output: socket
  }).on('exit', function() {
    socket.end();
  });
}).listen(5001);
實現了一個通過socket端口和TCP端口(5001)遠程通訊的例子。TELNET命令可用於遠程連接。


timers  定時器

    Timers
        setTimeout(callback, delay, [arg], [...])
        clearTimeout(timeoutId)
        setInterval(callback, delay, [arg], [...])
        clearInterval(intervalId)


Timers
setTimeout(callback, delay, [arg], [...]) 
在delay毫秒後執行一次回調函數(callback)。返回一個定時器id(timeoutId),此定時器id可用於clearTimeout()。可以選擇傳遞到回調函數。
重要提示:回調函數在delay毫秒內很可能不能調用。Node.js不保證回調函數激活的執行時間,此時也不能安排其他事情。回調函數將在靠近設置的延時時間調用。
例子:
function a () {                                          //這是一個打印函數
console.log("this is function a in executing");
};


console.log("start now!");
setTimeout(a, 1000) ;        //停了1000毫秒後,執行a函數。
有很多人習慣把要執行的函數寫在裏面:
setTimeout(function() {
  console.log('Foo');
}, 1000);
但我覺得這樣不如分開寫清晰。


clearTimeout(timeoutId) 
阻止停止目標timeout的執行。


setInterval(callback, delay, [arg], [...]) 
計劃在重複執行回調函數,時間間隔爲delay毫秒。返回一個clearInterval()可用的intervalId。可以選擇設置回調函數的參數。
例如:
function a () {
console.log("this is function a in executing");
};


console.log("start now!");


setInterval(a, 1000);
這會每隔1秒執行一次a函數。


clearInterval(intervalId) 
停止setInterval()
可以把計數器和停止條件寫在要重複執行的函數中間,來達到“執行了XX次後停止”的效果。如:


// Setting and clearing an interval after 5 times execution.


var counter = 0;


function a () {
console.log('counter=%d', counter);
counter++;
if (counter >= 5) {
    clearInterval(interval);
    };
};


var interval = setInterval(a, 1000);


Utility 工具集

需要先導入  var util = require('util');


格式化工具
util.format(format, [...])     前面是格式字符串,後面是要格式化的東西
格式字符串由如下佔位符組成:
%s - 字符串.
%d - 數字 (整數或浮點數均可).
%j - JSON.
% - 百分號 ('%').
作用是使多個變量按格式字符串的樣式排列起來。如下例:
var util = require('util');
var a = "temperature";
var b = 36
var result = util.format('%s:%d', a, b);
console.log(result)
結果輸出temperature:36


類型判斷工具
共有四個:
util.isArray(object)  判斷是否數組
例子:
util.isArray([])
  // true
util.isArray(new Array)
  // true
util.isArray({})
  // false
 
util.isRegExp(object) 判斷是否正則表達式
例子:
util.isRegExp(/some regexp/)
  // true
util.isRegExp(new RegExp('another regexp'))
  // true
util.isRegExp({})
  // false


util.isDate(object)  判斷是否日期
util.isDate(new Date())
  // true
util.isDate(Date())
  // false (without 'new' returns a String)
util.isDate({})
  // false


util.isError(object) 判斷是否是錯誤對象
util.isError(new Error())
  // true
util.isError(new TypeError())
  // true
util.isError({ name: 'Error', message: 'an error occurred' })
  // false


調試類工具
util.debug(string) 將字符串輸出到標準錯誤
util.error([...])  將所有參數輸出到標準錯誤
util.puts([...])   把所有參數分行打印到標準輸出 
util.print([...])  把所有參數不分行打印到標準輸出 
util.log(string)   打印字符串,前面加上時間戳
util.inspect(object,[showHidden],[depth],[colors]) 將對象轉換爲字符串返回.這個很有用!!!
屬性:showHidden 是否顯示隱藏內容(默認否),depth 遞歸深度(默認是2級,null就是無限級),colors 是否顯示顏色(默認是否),customInspect是否支持自定義顯示(這個要配套util.inspect.styles 和 util.inspect.colors 對象使用).

http

Node.js 標準庫提供了 http協議操作模塊,其中封裝了一個高效的 HTTP 服務器和一個簡易的HTTP 客戶端。使用時需要先require('http')
http.Server 是一個基於事件的 HTTP 服務器(Web Server),它的核心由 Node.js 下層 C++部分實現,而接口由 JavaScript 封裝,兼顧了高性能與簡易性。http.request 則是一個HTTP 客戶端工具,用於向 HTTP 服務器發起請求。
要想深入瞭解http模塊的使用,需要了解HTTP協議。請參見相關文章。
重要的一點:http模塊既可以搭建WEB遠程服務器,也可以充當瀏覽器-本地資源交互的機制,實現webapp。


1.http.Server
http.Server 是 http 模塊中的 HTTP 服務器對象。它提供了一套封裝級別很低的 API,僅僅是流控制和簡單的消息解析,所有的高層功能都要通過它的
接口來實現。
http.Server 是一個基於事件的 HTTP 服務器,所有的請求都被封裝爲獨立的事件,開發者只需要對它的事件編寫響應函數即可實現 HTTP 服務器的所有功能。它繼承自
EventEmitter,提供了以下幾個事件:
-request:當客戶端請求到來時,該事件被觸發,提供兩個參數 req 和res,分別是http.ServerRequest 和 http.ServerResponse 的實例,表示請求和響應信息。
-connection :當 TCP 連接建立時,該事件被觸發,提供一個參數 socket ,爲net.Socket 的實例。 connection 事件的粒度要大於 request ,因爲客戶端在
Keep-Alive 模式下可能會在同一個連接內發送多次請求。
-close :當服務器關閉時,該事件被觸發。注意不是在用戶連接斷開時。
除此之外還有 checkContinue、upgrade、clientError 事件,通常我們不需要關心,只有在實現複雜的 HTTP 服務器的時候纔會用到。
一個標準例子:
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.");


但是由於這種作業太常見了,node專門給了一個快捷方式:
var http = require('http');
http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Node.js</h1>');
res.end('<p>Hello World</p>');
}).listen(3000);
console.log("HTTP server is listening at port 3000.");


看,這麼簡單就實現了一個WEB SERVER!


在上述代碼中,回調函數 function(req, res)由request事件觸發,有兩個參數,其一是req(request),代表請求的內容。這個可以用瀏覽器在URL地址欄的?後面寫上,配合url.parse來解析。一般都是標準請求,不用管。其二是res(response),代表返回的信息,決定了瀏覽器能看到什麼。它有三個重要的成員函數,用於返回響應頭、響應內容以及結束
請求。
(1)response.writeHead(statusCode, [headers]):向請求的客戶端發送響應頭。statusCode 是 HTTP 狀態碼,如 200 (請求成功)、404 (未找到)等。headers是一個類似關聯數組的對象,表示響應頭的每個屬性。該函數在一個請求內最多隻
能調用一次,如果不調用,則會自動生成一個響應頭。
(2)response.write(data, [encoding]):向請求的客戶端發送響應內容。data 是一個 Buffer 或字符串,表示要發送的內容。如果 data 是字符串,那麼需要指定encoding 來說明它的編碼方式,默認是 utf-8。在 response.end 調用之前,response.write 可以被多次調用。
(3)response.end([data], [encoding]):結束響應,告知客戶端所有發送已經完成。當所有要返回的內容發送完畢的時候,該函數 必須 被調用一次。它接受兩個可
選參數,意義和 response.write 相同。如果不調用該函數,客戶端將永遠處於等待狀態。


2. HTTP 客戶端
http 模塊提供了兩個函數 http.request 和 http.get,功能是作爲客戶端向 HTTP服務器發起請求。可以作爲文本型的瀏覽器。
其中http.request是標準寫法:
var http = require('http');


var options = {                //請求的參數
host: 'localhost',
port: 3000,
path: '/',
method: 'GET'
};


var req = http.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (data) { //接到數據後顯示出來
console.log(data);
});
});
req.end();
上述例子請求localhost:3000/ 下的全部內容,並在接到後在客戶端顯示出來。
var http = require('http');


http.get({host: 'localhost'}, function(res) {
res.setEncoding('utf8');
res.on('data', function (data) {
console.log(data);
});
});


 http 模塊還提供了一個更加簡便的方法http.get(options, callback),它是 http.request 的簡化版,
唯一的區別在於http.get默認處理GET請求:自動將請求方法設爲了 GET 請求,同時不需要手動調用 req.end()。
在實踐中常用的是http.Server。客戶端一般由瀏覽器來充當了。


真正的WEB SERVER需要建立路由,返回不同的頁面文件,解析各種後綴的文件,定義各種錯誤信息和參數,是比較複雜的。所以要依賴框架。
下面的例子是一個簡單的WEB SERVER,它在本地8888端口定義了一個http服務器,瀏覽器一旦訪問,就返回/WebRoot/jstest.html文件。
//------------------------------------------------ 


//server.js 


// 一個演示Web服務器 
//------------------------------------------------ 

//請求模塊 


var libHttp = require('http'); //HTTP協議模塊 
var libUrl=require('url'); //URL解析模塊 
var libFs = require("fs"); //文件系統模塊 
var libPath = require("path"); //路徑解析模塊 


//依據路徑獲取返回內容類型字符串,用於http返回頭 

var funGetContentType=function(filePath){ 
var contentType=""; 


//使用路徑解析模塊獲取文件擴展名 

var ext=libPath.extname(filePath); 
switch(ext){ 
    case ".html": 
        contentType= "text/html"; 
        break; 
    case ".js": 
contentType="text/javascript"; 
break; 
case ".css": 
contentType="text/css"; 
break; 
    case ".gif": 
        contentType="image/gif"; 
        break; 
    case ".jpg": 
        contentType ="image/jpeg"; 
        break; 
    case ".png":
        contentType="image/png"; 
        break; 
    case ".ico": 
        contentType="image/icon"; 
        break; 
    default: 
        contentType="application/octet-stream"; 
    } 


    return contentType; //返回內容類型字符串 



//Web服務器主函數,解析請求,返回Web內容 

var funWebSvr = function (req, res){ 
var reqUrl=req.url; //獲取請求的url 


//向控制檯輸出請求的路徑 


console.log(reqUrl); 


//使用url解析模塊獲取url中的路徑名 


var pathName = libUrl.parse(reqUrl).pathname; 
if (libPath.extname(pathName)=="") { 
//如果路徑沒有擴展名 
    pathName+="/"; //指定訪問目錄 



if (pathName.charAt(pathName.length-1)=="/"){ 
//如果訪問目錄
    pathName+="index.html"; //指定爲默認網頁 



//使用路徑解析模塊,組裝實際文件路徑 (假定WebRoot是文件根目錄)

var filePath = libPath.join("./WebRoot",pathName); 
//判斷文件是否存在 
libPath.exists(filePath,function(exists){ 
    if(exists){//文件存在 
//在返回頭中寫入內容類型 
        res.writeHead(200, {"Content-Type": funGetContentType(filePath) }); 


//創建只讀流用於返回,使用二進制流格式以適應圖片

        var stream = libFs.createReadStream(filePath, "binary", {flags : "r", encoding : 'utf8'}); 

//指定如果流讀取錯誤,返回404錯誤 
        stream.on("error", function() { 
            res.writeHead(404); 
            res.end("<h1>404 Read Error</h1>"); 
        }); 


//連接文件流和http返回流的管道,用於返回實際Web內容 
        stream.pipe(res); 
    } 


    else {                    //文件不存在,返回404錯誤 
            res.writeHead(404, {"Content-Type": "text/html"}); 
            res.end("<h1>404 Not Found</h1>"); 
        } 
    }); 



//創建一個http服務器 

var webSvr=libHttp.createServer(funWebSvr); 


//指定服務器錯誤事件響應 
webSvr.on("error", function(error) { 
    console.log(error); //在控制檯中輸出錯誤信息 
}); 

//開始偵聽8888端口 
webSvr.listen(8888,function(){
//向控制檯輸出服務啓動的信息 
    console.log('[WebSvr][Start] running at http://127.0.0.1:8888/'); 
});




模塊化開發

開發一個具有一定規模的程序不可能只用一個文件,通常需要把各個功能拆分、封裝,然後組合起來,模塊正是爲了實現這種方式而誕生的。Node.js 提供了模塊(Module)和包(Package)機制。模塊就是一個文件,可以使用require 函數來調用其他模塊。包是實現了某個功能模塊的集合,用於發佈和維護(可以作爲一個文件夾)。
Node.js 的模塊和包機制的實現參照了 CommonJS 的標準,但並未完全遵循。

1.模塊

模塊是 Node.js 應用程序的基本組成部分,文件和模塊是一一對應的。換言之,一個Node.js 文件就是一個模塊,這個文件可能是 JavaScript 代碼、JSON 或者編譯過的 C/C++ 擴展。Node.js的API都是核心模塊,如我們曾經用到了 var http = require('http'),其中 http是 Node.js 的一個核心模塊,其內部是用 C++ 實現的,外部用 JavaScript 封裝。我們通過require 函數獲取了這個模塊,然後才能使用其中的對象。
除了核心模塊之外,我們可以自己編寫外部模塊。
1.1創建模塊
在 Node.js 中,創建一個模塊非常簡單,因爲一個文件就是一個模塊,我們要關注的問題僅僅在於如何在其他文件中獲取這個模塊。Node.js 提供了 exports 和 require 兩個對象,其中 exports 用於公開模塊的接口,require 用於從外部獲取一個模塊的接口,即所獲取模塊的 exports 對象。


讓我們以一個例子來了解。
//module.js
//使用export暴露需要供其他模塊調用的方法


var name


exports.setName = function (theName){
    name = theName;
};


exports.sayHello = function() {
console.log ("hello" + name);
};


//getmodule.js


var myModule = require ('./module')  //與模塊放在同一文件夾下。如果在一個單獨的文件夾下則應寫爲require ('./module/module')


myModule.setName("Pan");
myModule.sayHello();


模塊的導入只支持js文件,所以CS寫的源文件要編譯爲JS文件後再測試。但問題是當使用require語句時,只能require JS文件(因爲node.js 10.6起就取消了requre.extention功能),所以,CS只適合寫單個文件/測試單個文件,當連接各個模塊時,就要先編譯爲JS文件,再測試了。
require不會重複加載模塊,無論調用require多少次,獲得的都是同一模塊。


對象的輸出
如果不是想僅僅輸出某個方法(函數),而是要輸出整個對象(函數),可以用module.export = 對象名 的方法。如:
//module.js


function Hello() {
  var name


  this.setName = function (theName){    //要使用this
    name = theName;
  };


  this.sayHello = function() {
console.log ("hello" + name);
  };
};


module.exports = Hello;


//getmodule.js


var Hello = require ('./module')


var hello = new Hello();    //必須將對象實例化


hello.setName("Pan");
hello.sayHello();


由於require只能引進整個對象,然後引用它的方法,所以要引進多個對象,就不能用module.exports = Hello這樣的寫法輸出整個模塊,而要用exports.Hello = Hello;這樣的方法輸出一個對象。
下面的例子輸出了兩個對象,並在另外一個文件中引用。
//module.js
function Hello() {
  var name


  this.setName = function (theName){    //要使用this
    name = theName;
  };


  this.sayHello = function() {
console.log ("hello " + name);
  };
};


exports.Hello = Hello;


function Hello2() {
  var name


  this.setName = function (theName){    //要使用this
    name = theName;
  };


  this.sayHello = function() {
console.log ("hello world " + name);
  };
};


//getmodule.js
var Hello = require ('./module').Hello
var Hello2 = require ('./module').Hello2


var hello = new Hello();    //必須將對象實例化


hello.setName("Pan");
hello.sayHello();


var hello2 = new Hello2();    //必須將對象實例化


hello2.setName("Wang");
hello2.sayHello();
exports.Hello2 = Hello2;


2.包

包是在模塊基礎上更深一步的抽象,Node.js 的包類似於 C/C++ 的函數庫或者 Java/.Net的類庫。它將某個獨立的功能封裝起來,用於發佈、更新、依賴管理和版本控制。Node.js 根據 CommonJS 規範實現了包機制,開發了 npm來解決包的發佈和獲取需求。
Node.js 的包是一個目錄,其中包含一個 JSON 格式的包說明文件 package.json。嚴格符
合 CommonJS 規範的包應該具備以下特徵:
 package.json 必須在包的頂層目錄下;
 二進制文件應該在 bin 目錄下;
 JavaScript 代碼應該在 lib 目錄下;
 文檔應該在 doc 目錄下;
 單元測試應該在 test 目錄下。
Node.js 對包的要求並沒有這麼嚴格,只要頂層目錄下有 package.json,並符合一些規範即可。當然爲了提高兼容性,我們還是建議你在製作包的時候,嚴格遵守 CommonJS 規範。
Node.js 的包是一個目錄,其中包含一個 JSON 格式的包說明文件 package.json。嚴格符合 CommonJS 規範的包應該具備以下特徵:
 package.json 必須在包的頂層目錄下;
 二進制文件應該在 bin 目錄下;
 JavaScript 代碼應該在 lib 目錄下;
 文檔應該在 doc 目錄下;
 單元測試應該在 test 目錄下。
Node.js 對包的要求並沒有這麼嚴格,只要頂層目錄下有 package.json,並符合一些規範
即可。當然爲了提高兼容性,我們還是建議你在製作包的時候,嚴格遵守 CommonJS 規範。
最簡單的包,就是一個作爲文件夾的模塊。下面我們來看一個例子,建立一個叫
做 somepackage 的文件夾,在其中創建 index.js,內容如下:
//somepackage/index.js
exports.hello = function() {
console.log('Hello.');
};
然後在 somepackage 之外建立 getpackage.js,內容如下:
//getpackage.js
var somePackage = require('./somepackage');
somePackage.hello();


運行 node getpackage.js,控制檯將輸出結果 Hello.。


我們使用這種方法可以把文件夾封裝爲一個模塊,即所謂的包。包通常是一些模塊的集合,在模塊的基礎上提供了更高層的抽象,相當於提供了一些固定接口的函數庫。通過定製
package.json,我們可以創建更復雜、更完善、更符合規範的包用於發佈。
package.json
在前面例子中的 somepackage 文件夾下,我們創建一個叫做 package.json 的文件,內容如下所示:
{
"main" : "./lib/interface.js"
}
然後將 index.js 重命名爲 interface.js 並放入 lib 子文件夾下。以同樣的方式再次調用這個包,依然可以正常使用。
Node.js 在調用某個包時,會首先檢查包中 package.json 文件的 main 字段,將其作爲包的接口模塊,如果 package.json 或 main 字段不存在,會嘗試尋找 index.js 或 index.node 作爲包的接口。
package.json 是 CommonJS 規定的用來描述包的文件,完全符合規範的 package.json 文件應該含有以下字段。
 name:包的名稱,必須是唯一的,由小寫英文字母、數字和下劃線組成,不能包含空格。
 description:包的簡要說明。
 version:符合語義化版本識別 規範的版本字符串。
 keywords:關鍵字數組,通常用於搜索。
 maintainers:維護者數組,每個元素要包含 name、email (可選) web (可選)字段。
 contributors:貢獻者數組,格式與maintainers相同。包的作者應該是貢獻者
數組的第一個元素。
 bugs:提交bug的地址,可以是網址或者電子郵件地址。
 licenses:許可證數組,每個元素要包含 type (許可證的名稱)和 url (鏈接到許可證文本的地址)字段。
 repositories:倉庫託管地址數組,每個元素要包含 type(倉庫的類型, git )
 dependencies:包的依賴,一個關聯數組,由包名稱和版本號組成。
下面是一個完全符合 CommonJS 規範的 package.json 示例:


{
"name": "mypackage",
"description": "Sample package for CommonJS. This package demonstrates the required
elements of a CommonJS package.",
"version": "0.7.0",
"keywords": [
"package",
"example"
],
"maintainers": [
{
"name": "Bill Smith",
"email": "[email protected]",
}
],
"contributors": [
{
"name": "BYVoid",
"web": "http://www.byvoid.com/"
}
],
"bugs": {
"mail": "[email protected]",
"web": "http://www.example.com/bugs"
},
"licenses": [
{
"type": "GPLv2",
"url": "http://www.example.org/licenses/gpl.html"
}
],
"repositories": [
{
"type": "git",
"url": "http://github.com/BYVoid/mypackage.git"
}
],
"dependencies": {
"webkit": "1.2",
"ssl": {
"gnutls": ["1.0", "2.0"],
"openssl": "0.9.8"
}
}
}



3.npm 包管理器

Node.js包管理器,即npm是 Node.js 官方提供的包管理工具,它已經成了 Node.js 包的
標準發佈平臺,用於 Node.js 包的發佈、傳播、依賴控制。npm 提供了命令行工具,使你可
以方便地下載、安裝、升級、刪除包,也可以讓你作爲開發者發佈並維護包。
3.1. 獲取一個包
使用 npm 安裝包的命令格式爲:
npm [install/i] [package_name]
例如你要安裝 express,可以在命令行運行:
$ npm install express
或者:
$ npm i express
隨後你會看到以下安裝信息:
npm http GET https://registry.npmjs.org/express
......
此時 express 就安裝成功了,並且放置在當前目錄的 node_modules 子目錄下。npm 在獲取 express 的時候還將自動解析其依賴,並獲取 express 依賴的 mime、mkdirp、qs 和 connect。
3.2. 本地模式和全局模式
npm在默認情況下會從http://npmjs.org搜索或下載包,將包安裝到當前目錄的node_modules。在使用 npm 安裝包的時候,
有兩種模式:
本地模式和全局模式。
默認情況下我們使用 npm即把包安裝到當前目錄的 node_modules 子目錄下。
Node.jsinstall命令就是採用本地模式,的 require 在加載模塊時會嘗試搜尋 node_modules 子目錄,因此使用 npm 本地模式安裝的包可以直接被引用。
npm 還有另一種不同的安裝模式被成爲全局模式,使用方法爲:
npm [install/i] -g [package_name]
與本地模式的不同之處就在於多了一個參數 -g。
我們在安裝coffeescript中使用了 npm install -g coffee-script 命令,就是以全局模式安裝coffee-script。
爲什麼要使用全局模式呢?是因爲本地模式不會註冊 PATH 環境變量。舉例說明,我們安裝
coffeescript是爲了在命令行中運行它,這時就需要在 PATH環境變量中註冊 coffeescript。npm 本地模式僅僅是把包安裝到 node_modules 子目錄下,其中
的 bin 目錄沒有包含在 PATH 環境變量中,不能直接在命令行中調用。而當我們使用全局模式安裝時,npm 會將包安裝到系統目錄,
譬如 /usr/local/lib/node_modules/,同時 package.json 文件中 bin 字段包含的文件會被鏈接到 /usr/local/bin/。/usr/local/bin/ 是在PATH 環境變量中默認定義的,因此就可以直接在命令行中運行coffescript test.cs命令了。
創建全局鏈接
npm 提供了一個有趣的命令 npm link,它的功能是在本地包和全局包之間創建符號鏈接。我們說過使用全局模式安裝的包不能直接通過 require 使用,但通過 npm link命令可以打破這一限制。舉個例子,我們已經通過 npm install -g express 安裝了 express, 這時在工程的目錄下運行命令:
$ npm link express
./node_modules/express -> /usr/local/lib/node_modules/express我們可以在 node_modules 子目錄中發現一個指向安裝到全局的包的符號鏈接。通過這
種方法,我們就可以把全局包當本地包來使用了。npm link 命令不支持 Windows。
除了將全局的包鏈接到本地以外,使用 npm link命令還可以將本地的包鏈接到全局。使用方法是在包目錄( package.json 所在目錄)中運行 npm link 命令。如果我們要開發一個包,利用這種方法可以非常方便地在不同的工程間進行測試。
3.3包的發佈
npm 可以非常方便地發佈一個包,比 pip、gem、pear 要簡單得多。在發佈之前,首先需要讓我們的包符合 npm 的規範,npm 有一套以 CommonJS 爲基礎包規範,
通過使用 npm init 可以根據交互式問答產生一個符合標準的 package.json,例如創建一個名爲 project 的目錄,然後在這個
目錄中運行npm init:
$ npm init
Package name: mymodule
Description: A module for xxx.
Package version: (0.0.0) 0.0.1
Project homepage: (none) http://www.myspace.com/
......
這樣就在 project 目錄中生成一個符合 npm 規範的 package.json 文件。創建一個index.js 作爲包的接口,一個簡單的包就製作完成了。我們可以把它發佈node.js的全球包管理中心npm上(https://npmjs.org/)。發佈很簡單,在 package.json 所在目錄下運行 npm publish,稍等片刻就可以完成發佈了。
打開瀏覽器,訪問 http://search.npmjs.org/ 就可以找到自己剛剛發佈的包了。現在我們可以在世界的任意一臺計算機上使用 npm install mymodule 命令來安裝它。
如果你的包將來有更新,只需要在 package.json 文件中修改 version 字段,然後重新使用 npm publish 命令就行了。如果你對已發佈的包不滿意,可以使用 npm unpublish 命令來取消發佈。
發佈了14 篇原創文章 · 獲贊 4 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章