NodeJS 初體驗

console.log('%s: %d', 'Hello', 25);  // 能夠像C語言格式同樣輸出
//app.js
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.");
小技巧——使用 supervisor
Node.js 只有在第一次引用到某部份時纔會去解析腳本文件,之後都會直接訪問內存,避免重複載入
Node.js的這種設計雖然有利於提升性能,卻不利於開發調試,由於咱們在開發過程當中老是但願修改後當即看到效果,
而不是每次都要終止進程並重啓。
supervisor 能夠幫助你實現這個功能,它會監視你對代碼的改動,並自動重啓 Node.js/
使用方法很簡單,首先使用 npm 安裝 supervisor:
npm install -g supervisor
接下來,使用 supervisor 命令啓動 app.js:
supervisor app.jshtml

異步式 I/O 與事件式編程

這種模式與傳統的同步式 I/O 線性的編程思路有很大的不一樣,由於控制流很大程度上要靠事件
和回調函數來組織,一個邏輯要拆分爲若干個單元。
線程必須有事件循環,不斷地檢查有沒有未處理的事件,依次予以處理.
Node.js 使用了單線程、非阻塞的事件編程模式。
異步式編程的缺點在於不符合人們通常的程序設計思惟,容易讓控制流變得晦澀難懂,給編碼和調試都帶來不小的困難.node

回調函數

讓咱們看看在 Node.js 中如何用異步的方式讀取一個文件,下面是一個例子:
//readfile.js
var fs = require('fs');
fs.readFile('file.txt', 'utf-8', function(err, data) {
if (err) {
console.error(err);
} else {
console.log(data);
}
});
console.log('end.');
運行的結果以下:
end.
Contents of the file.
同步的方式:
var fs = require('fs');
var data = fs.readFileSync('file.txt', 'utf-8');
console.log(data);
console.log('end.');
運行的結果與前面不一樣,以下所示:
$ node readfilesync.js
Contents of the file.
end.
能夠看到, 異步的方式與同步的方式執行結果不一樣, 同步的方式好理解, 就是傳統的程序運行的方式.
異步式讀取文件就稍微有些違反直覺了,end.先被輸出. 咱們必須先知道在 Node.js 中,
異步式 I/O 是經過回調函數來實現的. fs.readFile 接收了三個參數, 第一個是文件名,第二個是編碼方式,
第三個是一個函數,咱們稱這個函數爲回調函數。
//readfilecallback.js
function 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.');
fs.readFile 調用時所作的工做只是將異步式 I/O 請求發送給了操做系統,而後當即返回並執行後面的語句,
執行完之後進入事件循環監聽事件。當 fs 接收到 I/O 請求完成的事件時,事件循環會主動調用回調函數以完成後續工做。
所以咱們會先看到 end.,再看到file.txt 文件的內容。
Question : 何時將控制權返回給事件循環 ?git

事件

Node.js 全部的異步 I/O 操做在完成時都會發送一個事件到事件隊列。,事件由 EventEmitter 對象提供。
前面提到的 fs.readFile 和 http.createServer 的回調函數都是經過 EventEmitter 來實現的。
Node.js 的事件循環機制
Node.js 在何時會進入事件循環呢?
答案是 Node.js 程序由事件循環開始,到事件循環結束,全部的邏輯都是事件的回調函數,因此 Node.js 始終在事件循環中,
程序入口就是事件循環第一個事件的回調函數。事件的回調函數在執行的過程當中,可能會發出 I/O 請求或直接發射(emit)事件,
執行完畢後再返回事件循環, 事件循環會檢查事件隊列中有沒有未處理的事件,直到程序結束。
與其餘語言不一樣的是,Node.js 沒有顯式的事件循環, Node.js 的事件循環對開發者不可見,由 libev 庫實現.
express

模塊和包

模塊(Module)和包(Package)是 Node.js 最重要的支柱。開發一個具備必定規模的程序不可能只用一個文件,
一般須要把各個功能拆分、封裝,而後組合起來,模塊正是爲了實現這種方式而誕生的, 並且模塊都是基於文件的,機制十分簡單。
咱們常常把 Node.js 的模塊和包相提並論,由於模塊和包是沒有本質區別的.
模塊是 Node.js 應用程序的基本組成部分,文件和模塊是一一對應的. 咱們曾經用到了 var http = require('http'),其中 http
是 Node.js 的一個核心模塊,其內部是用 C++ 實現的,外部用 JavaScript 封裝.(也是一個文件)
咱們經過require 函數獲取了這個模塊,而後才能使用其中的對象。npm


建立模塊

在 Node.js 中,建立一個模塊很是簡單,由於一個文件就是一個模塊,咱們要關注的問題僅僅在於如何在其餘文件中獲取這個模塊.
。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);
};
在同一目錄下建立 getmodule.js,內容是:
//getmodule.js
var myModule = require('./module');
myModule.setName('BYVoid');
myModule.sayHello();
從以上例子中, 咱們能夠看到, "模塊即文件", 爲了方便引用.
,npm 提供的上萬個模塊都是經過這種簡單的方式搭建起來的.編程


單次加載

上面這個例子有點相似於建立一個對象,但實際上和對象又有本質的區別,由於require 不會重複加載模塊,
也就是說不管調用多少次 require,得到的模塊都是同一個。
//loadmodule.js
var hello1 = require('./module');
hello1.setName('BYVoid');
var hello2 = require('./module');
hello2.setName('BYVoid 2');
hello1.sayHello();
運行後發現輸出結果是 Hello BYVoid 2,這是由於變量 hello1 和 hello2 指向的是同一個實例,
所以 hello1.setName 的結果被 hello2.setName 覆蓋,最終輸出結果是由後者決定的.json


覆蓋 exports

有時候咱們只是想把一個對象封裝到模塊中,例如:
//singleobject.js
function Hello() {
var name;
this.setName = function (thyName) {
name = thyName;
};
this.sayHello = function () {
console.log('Hello ' + name);
};
};
exports.Hello = Hello;
此時咱們在其餘文件中須要經過 require('./singleobject').Hello 來獲取Hello 對象,這略顯冗餘,能夠用下面方法稍微簡化:
//hello.js
function Hello() {
var name;
this.setName = function(thyName) {
name = thyName;
};
this.sayHello = function() {
console.log('Hello ' + name);
};
};
module.exports = Hello;
這樣就能夠直接得到這個對象了:
//gethello.js
var Hello = require('./hello');
hello = new Hello();
hello.setName('BYVoid');
hello.sayHello();
注意,模塊接口的惟一變化是使用 module.exports = Hello 代替了 exports.Hello=Hello, 另外, 就是文件名變了.瀏覽器


建立包

包是在模塊基礎上更深一步的抽象,Node.js 的包相似於 C/C++ 的函數庫或者 Java/.Net的類庫.
它將某個獨立的功能封裝起來,用於發佈、更新、依賴管理和版本控制.
Node.js 的包是一個目錄, 其中包含一個 JSON 格式的包說明文件 package.json。嚴格按照如下規則:
package.json 必須在包的頂層目錄下;
二進制文件應該在 bin 目錄下;
JavaScript 代碼應該在 lib 目錄下;
文檔應該在 doc 目錄下;
單元測試應該在 test 目錄下。
做爲文件夾的模塊
模塊與文件是一一對應的, 文件不只能夠是 JavaScript 代碼或二進制代碼,還能夠是一個文件夾, 最簡單的包,
就是一個做爲文件夾的模塊, 下面咱們來看一個例子,創建一個叫作 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 做爲包的接口。
下面是一個徹底符合 CommonJS 規範的 package.json 示例:app


Node.js 包管理器

Node.js包管理器,即npm是 Node.js 官方提供的包管理工具,它已經成了 Node.js 包的標準發佈平臺,
用於 Node.js 包的發佈、傳播、依賴控制。npm 提供了命令行工具,使你能夠方便地下載、安裝、升級、刪除包,也可讓你做爲開發者發佈並維護包。
獲取一個包
npm [install/i] [package_name], 例如 : npm install express
而且放置在當前目錄的 node_modules 子目錄下.eclipse


本地模式和全局模式

npm在默認狀況下會從http://npmjs.org搜索或下載包,將包安裝到當前目錄的node_modules子目錄下。
在使用 npm 安裝包的時候,有兩種模式:本地模式和全局模式。默認狀況下咱們使用 npminstall命令就是採用本地模式,
即把包安裝到當前目錄的 node_modules 子目錄下. Node.js的 require 在加載模塊時會嘗試搜尋 node_modules 子目錄,
所以使用 npm 本地模式安裝的包能夠直接被引用。
npm 還有另外一種不一樣的安裝模式被成爲全局模式,使用方法爲:npm [install/i] -g [package_name]
咱們在 介紹 supervisor那個小節中使用了 npm install -g supervisor 命令,就是以全局模式安裝 supervisor。
爲何要使用全局模式呢?
由於本地模式不會註冊 PATH 環境變量.舉例說明,咱們安裝supervisor 是爲了在命令行中運行它,譬如直接運行 supervisor script.js,
這時就須要在 PATH環境變量中註冊 supervisor。
使用全局模式安裝的包並不能直接在 JavaScript 文件中用 require 得到,由於 require 不會搜索PATH中對應的某個目錄.

總而言之,當咱們要把某個包做爲工程運行時的一部分時,經過本地模式獲取,若是要在命令行下使用,則使用全局模式安裝。
建立全局連接
npm 提供了一個有趣的命令 npm link,它的功能是在本地包和全局包之間建立符號連接。
咱們說過使用全局模式安裝的包不能直接經過 require 使用,但經過 npm link命令能夠打破這一限制.
npm link 命令不支持Windows


包的發佈

npm 能夠很是方便地發佈一個包, 經過使用 npm init 能夠根據交互式問答產生一個符合標準的 package.json,
例如建立一個名爲 byvoidmodule 的目錄,而後在這個目錄中運行npm init:
Package name: (byvoidmodule) byvoidmodule
Description: A module for learning perpose.
Package version: (0.0.0) 0.0.1
Project homepage: (none) http://www.byvoid.com/
Project git repository: (none)
Author name: BYVoid
Author email: (none) byvoid.kcp@gmail.com
Author url: (none) http://www.byvoid.com/
Main module/entry point: (none)
Test command: (none)
What versions of node does it run on? (~0.6.10)
About to write to /home/byvoid/byvoidmodule/package.json
{
"author": "BYVoid <byvoid.kcp@gmail.com> (http://www.byvoid.com/)",
"name": "byvoidmodule",
"description": "A module for learning perpose.",
"version": "0.0.1",
"homepage": "http://www.byvoid.com/",
"repository": {
"url": ""
},
"engines": {
"node": "~0.6.12"
},
"dependencies": {},
"devDependencies": {}
}
Is this ok? (yes) yes
接下來,在 package.json 所在目錄下運行 npm publish,稍等片刻就能夠完成發佈了。
打開瀏覽器,訪問 http://search.npmjs.org/ 就能夠找到本身剛剛發佈的包了。
如今咱們能夠在世界的任意一臺計算機上使用 npm install byvoidmodule 命令來安裝它
若是你的包未來有更新,只須要在 package.json 文件中修改 version 字段, 而後從新使用 npm publish 命令就好了.
若是你對已發佈的包不滿意, 可使用 npm unpublish 命令來取消發佈。

調試

命令行調試在命令行下執行 node debug debug.js,將會啓動調試工具使用 eclipse 調試

相關文章
相關標籤/搜索