1. 開始使用nodejs
1.1. Hello Word
好了,讓我們開始實現第一個 Node.js 程序吧。打開你常用的文本編輯器,在其中輸入:
console.log('Hello World');
將文件保存為 helloworld.js,打開命令行工具,進入 helloworld.js所在的目錄,執行以下命令:
node helloworld.js
如果一切正常,你將會在命令行工具中看到輸出 Hello World。
1.2. Node.js命令行工具
在前面的 Hello World 示例中,我們用到了命令行中的 node 命令,輸入 node --help
可以看到詳細的幫助信息:
Usage: node [options] [ -e script | script.js ] [arguments]
node debug script.js [arguments]
Options:
-v, --version print node's version
-e, --eval script evaluate script
-p, --print print result of --eval
--v8-options print v8 command line options
--vars print various compiled-in variables
--max-stack-size=val set max v8 stack size (bytes)
Environment variables:
NODE_PATH ';'-separated list of directories
prefixed to the module search path.
NODE_MODULE_CONTEXTS Set to 1 to load modules in their own
global contexts.
NODE_DISABLE_COLORS Set to 1 to disable colors in the REPL
Documentation can be found at http://nodejs.org/
其中顯示了 node 的用法,運行 Node.js 程序的基本方法就是執行 node script.js,
其中 script.js是腳本的文件名。
除了直接運行腳本文件外, node --help 顯示的使用方法中說明了另一種輸出 Hello
World 的方式:
$ node -e "console.log('Hello World');"
Hello World
我們可以把要執行的語句作為 node -e 的參數直接執行。
1.3 建立 HTTP 服務器
讓我們創建一個 HTTP 服務器吧。建立一個名為 app.js 的文件,內容
為:
//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
supervisor會監視你對代碼的改動,并自動重啟 Node.js。
使用方法很簡單,首先使用 npm 安裝 supervisor:
$ npm install -g supervisor
接下來,使用 supervisor 命令啟動 app.js:
$ supervisor app.js
DEBUG: Running node-supervisor with
DEBUG: program 'app.js'
DEBUG: --watch '.'
DEBUG: --extensions 'node|js'
DEBUG: --exec 'node'
DEBUG: Starting child process with 'node app.js'
DEBUG: Watching directory '/home/byvoid/.' for changes.
HTTP server is listening at port 3000.
當代碼被改動時,運行的腳本會被終止,然后重新啟動。在終端中顯示的結果如下:
DEBUG: crashing child
DEBUG: Starting child process with 'node app.js'
HTTP server is listening at port 3000.
2. 異步式編程
2.1. 回調函數
讓我們看看在 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.
Node.js 也提供了同步讀取文件的 API:
//readfilesync.js
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.
同步式讀取文件的方式比較容易理解,將文件名作為參數傳入 fs.readFileSync 函數,阻塞等待讀取完成后,將文件的內容作為函數的返回值賦給 data變量,接下來控制臺輸出 data 的值,最后輸出 end.。
異步式讀取文件就稍微有些違反直覺了, end.先被輸出。要想理解結果,我們必須先知道在 Node.js 中,異步式 I/O是通過回調函數來實現的。 fs.readFile 接收了三個參數,第一個是文件名,第二個是編碼方式,第三個是一個函數,我們稱這個函數為回調函數。JavaScript 支 持 匿 名 的 函 數 定 義 方 式 , 譬 如 我 們 例 子 中 回 調 函 數 的 定 義 就 是 嵌 套 在fs.readFile 的參數表中的。這種定義方式在 JavaScript中極為普遍,與下面這種定義
方式實現的功能是一致的:
//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.');
2.2. 事件
Node.js 所有的異步 I/O 操作在完成時都會發送一個事件到事件隊列。在開發者看來,事件由 EventEmitter 對象提供。前面提到的 fs.readFile 和 http.createServer 的回調函數都是通過 EventEmitter 來實現的。下面我們用一個簡單的例子說明 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 的監聽器。
3. 模塊和包
3.1. 什么是模塊
模塊是 Node.js 應用程序的基本組成部分,文件和模塊是一一對應的。換言之,一個Node.js 文件就是一個模塊,這個文件可能是 JavaScript 代碼、 JSON 或者編譯過的 C/C++擴展。
在前面章節的例子中,我們曾經用到了 var http = require('http'), 其中 http是 Node.js 的一個核心模塊,其內部是用 C++ 實現的,外部用 JavaScript 封裝。我們通過require 函數獲取了這個模塊,然后才能使用其中的對象。
3.2. 創建及加載模塊
- 創建模塊
在 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();
運行node getmodule.js,結果是:
Hello BYVoid
在以上示例中, module.js 通過 exports 對象把 setName 和 sayHello 作為模塊的訪問接口,在 getmodule.js 中通過 require('./module') 加載這個模塊,然后就可以直接訪問 module.js 中 exports 對象的成員函數了。
- 單次加載
上面這個例子有點類似于創建一個對象,但實際上和對象又有本質的區別,因為require 不會重復加載模塊,也就是說無論調用多少次 require, 獲得的模塊都是同一個。我們在 getmodule.js 的基礎上稍作修改:
//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 覆蓋,最終輸出結果是由后者決定的。
- 覆蓋 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();
不可以通過對 exports 直接賦值代替對 module.exports 賦值。exports 實際上只是一個和 module.exports 指向同一個對象的變量,它本身會在模塊執行結束后釋放,但 module 會,因此只能通過指定module.exports 來改變訪問接口。
3.3 創建包
包是在模塊基礎上更深一步的抽象, Node.js 的包類似于 C/C++ 的函數庫或者 Java/.Net的類庫。它將某個獨立的功能封裝起來,用于發布、更新、依賴管理和版本控制。 Node.js 根據 CommonJS 規范實現了包機制,開發了 npm來解決包的發布和獲取需求。
Node.js 的包是一個目錄,其中包含一個 JSON 格式的包說明文件 package.json。嚴格符合 CommonJS 規范的包應該具備以下特征:
- 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 作為包的接口。
package.json 是 CommonJS 規定的用來描述包的文件,完全符合規范的 package.json 文件應該含有以下字段。
- name:包的名稱,必須是唯一的,由小寫英文字母、數字和下劃線組成,不能包含空格。
- description:包的簡要說明。
- version:符合語義化版本識別規范的版本字符串。
- keywords:關鍵字數組,通常用于搜索。
- maintainers:維護者數組,每個元素要包含 name、 email (可選)、 web (可選)字段。
- contributors:貢獻者數組,格式與maintainers相同。包的作者應該是貢獻者數組的第一個元素
- bugs:提交bug的地址,可以是網址或者電子郵件地址。
- licenses:許可證數組,每個元素要包含 type (許可證的名稱)和 url (鏈接到許可證文本的地址)字段。
- repositories:倉庫托管地址數組,每個元素要包含 type(倉庫的類型,如 git )、url (倉庫的地址)和 path (相對于倉庫的路徑,可選)字段。
- 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": "bills@example.com",
}
],
"contributors": [
{
"name": "BYVoid",
"web": "http://www.byvoid.com/"
}
],
"bugs": {
"mail": "dev@example.com",
"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.4 Node.js 包管理器
Node.js包管理器,即npm是 Node.js 官方提供的包管理工具①,它已經成了 Node.js 包的標準發布平臺,用于 Node.js 包的發布、傳播、依賴控制。 npm 提供了命令行工具,使你可以方便地下載、安裝、升級、刪除包,也可以讓你作為開發者發布并維護包。
4 調試
4.1. 命令行調試
Node.js 支持命令行下的單步調試。下面是一個簡單的程序:
var a = 1;
var b = 'world';
var c = function(x) {
console.log('hello ' + x + a);
};
c(b);
在命令行下執行 node debug debug.js,將會啟動調試工具:
< debugger listening on port 5858
connecting... ok
break in /home/byvoid/debug.js:1
1 var a = 1;
2 var b = 'world';
3 var c = function(x) {
debug>
命令 | 功能 |
---|---|
run | 執行腳本,在第一行暫停 |
restart | 重新執行腳本 |
cont, c | 繼續執行,直到遇到下一個斷點 |
next, n | 單步執行 |
step, s | 單步執行并進入函數 |
out, o | 從函數中步出 |
setBreakpoint(), sb() | 在當前行設置斷點 |
setBreakpoint(‘f()’), sb(...) | 在函數f的第一行設置斷點 |
setBreakpoint(‘script.js’, 20), sb(...) | 在 script.js 的第20行設置斷點 |
clearBreakpoint, cb(...) | 清除所有斷點 |
backtrace, bt | 顯示當前的調用棧 |
list(5) | 顯示當前執行到的前后5行代碼 |
watch(expr) | 把表達式 expr 加入監視列表 |
unwatch(expr) | 把表達式 expr 從監視列表移除 |
watchers | 顯示監視列表中所有的表達式和值 |
repl | 在當前上下文打開即時求值環境 |
kill | 終止當前執行的腳本 |
scripts | 顯示當前已加載的所有腳本 |
version | 顯示 V8 的版本 |