1. 開始使用nodejs
1.1. Hello Word
好了阁吝,讓我們開始實現(xiàn)第一個 Node.js 程序吧。打開你常用的文本編輯器博烂,在其中輸入:
console.log('Hello World');
將文件保存為 helloworld.js,打開命令行工具,進入 helloworld.js所在的目錄篙贸,執(zhí)行以下命令:
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 程序的基本方法就是執(zhí)行 node script.js雁芙,
其中 script.js是腳本的文件名。
除了直接運行腳本文件外钞螟, node --help 顯示的使用方法中說明了另一種輸出 Hello
World 的方式:
$ node -e "console.log('Hello World');"
Hello World
我們可以把要執(zhí)行的語句作為 node -e 的參數(shù)直接執(zhí)行兔甘。
1.3 建立 HTTP 服務器
讓我們創(chuàng)建一個 HTTP 服務器吧。建立一個名為 app.js 的文件鳞滨,內(nèi)容
為:
//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會監(jiān)視你對代碼的改動洞焙,并自動重啟 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. 回調函數(shù)
讓我們看看在 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.
同步式讀取文件的方式比較容易理解甸鸟,將文件名作為參數(shù)傳入 fs.readFileSync 函數(shù),阻塞等待讀取完成后兵迅,將文件的內(nèi)容作為函數(shù)的返回值賦給 data變量抢韭,接下來控制臺輸出 data 的值,最后輸出 end.恍箭。
異步式讀取文件就稍微有些違反直覺了刻恭, end.先被輸出。要想理解結果扯夭,我們必須先知道在 Node.js 中吠各,異步式 I/O是通過回調函數(shù)來實現(xiàn)的臀突。 fs.readFile 接收了三個參數(shù),第一個是文件名贾漏,第二個是編碼方式候学,第三個是一個函數(shù),我們稱這個函數(shù)為回調函數(shù)纵散。JavaScript 支 持 匿 名 的 函 數(shù) 定 義 方 式 梳码, 譬 如 我 們 例 子 中 回 調 函 數(shù) 的 定 義 就 是 嵌 套 在fs.readFile 的參數(shù)表中的。這種定義方式在 JavaScript中極為普遍伍掀,與下面這種定義
方式實現(xiàn)的功能是一致的:
//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 操作在完成時都會發(fā)送一個事件到事件隊列掰茶。在開發(fā)者看來,事件由 EventEmitter 對象提供蜜笤。前面提到的 fs.readFile 和 http.createServer 的回調函數(shù)都是通過 EventEmitter 來實現(xiàn)的濒蒋。下面我們用一個簡單的例子說明 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 的一個監(jiān)聽器沪伙,然后我們通過 setTimeout在1000毫秒以后向
event 對象發(fā)送事件 some_event,此時會調用 some_event 的監(jiān)聽器县好。
3. 模塊和包
3.1. 什么是模塊
模塊是 Node.js 應用程序的基本組成部分围橡,文件和模塊是一一對應的。換言之缕贡,一個Node.js 文件就是一個模塊翁授,這個文件可能是 JavaScript 代碼、 JSON 或者編譯過的 C/C++擴展晾咪。
在前面章節(jié)的例子中,我們曾經(jīng)用到了 var http = require('http')谍倦, 其中 http是 Node.js 的一個核心模塊炬守,其內(nèi)部是用 C++ 實現(xiàn)的曹洽,外部用 JavaScript 封裝。我們通過require 函數(shù)獲取了這個模塊隅俘,然后才能使用其中的對象。
3.2. 創(chuàng)建及加載模塊
- 創(chuàng)建模塊
在 Node.js 中笤喳,創(chuàng)建一個模塊非常簡單为居,因為一個文件就是一個模塊,我們要關注的問題僅僅在于如何在其他文件中獲取這個模塊杀狡。 Node.js 提供了 exports 和 require 兩個對象蒙畴,其中 exports 是模塊公開的接口, require用于從外部獲取一個模塊的接口呜象,即所獲取模塊的 exports 對象膳凝。
創(chuàng)建一個 module.js 的文件,內(nèi)容是:
//module.js
var name;
exports.setName = function(thyName) {
name = thyName;
};
exports.sayHello = function() {
console.log('Hello ' + name);
};
在同一目錄下創(chuàng)建 getmodule.js董朝,內(nèi)容是:
//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 對象的成員函數(shù)了。
- 單次加載
上面這個例子有點類似于創(chuàng)建一個對象哥捕,但實際上和對象又有本質的區(qū)別牧抽,因為require 不會重復加載模塊,也就是說無論調用多少次 require遥赚, 獲得的模塊都是同一個扬舒。我們在 getmodule.js 的基礎上稍作修改:
//loadmodule.js
var hello1 = require('./module');
hello1.setName('BYVoid');
var hello2 = require('./module');
hello2.setName('BYVoid 2');
hello1.sayHello();
運行后發(fā)現(xiàn)輸出結果是 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 指向同一個對象的變量瓮栗,它本身會在模塊執(zhí)行結束后釋放,但 module 會,因此只能通過指定module.exports 來改變訪問接口费奸。
3.3 創(chuàng)建包
包是在模塊基礎上更深一步的抽象弥激, Node.js 的包類似于 C/C++ 的函數(shù)庫或者 Java/.Net的類庫。它將某個獨立的功能封裝起來愿阐,用于發(fā)布微服、更新、依賴管理和版本控制换况。 Node.js 根據(jù) CommonJS 規(guī)范實現(xiàn)了包機制职辨,開發(fā)了 npm來解決包的發(fā)布和獲取需求。
Node.js 的包是一個目錄戈二,其中包含一個 JSON 格式的包說明文件 package.json舒裤。嚴格符合 CommonJS 規(guī)范的包應該具備以下特征:
- package.json 必須在包的頂層目錄下;
- 二進制文件應該在 bin 目錄下觉吭;
- JavaScript 代碼應該在 lib 目錄下腾供;
- 文檔應該在 doc 目錄下;
- 單元測試應該在 test 目錄下鲜滩。
- 作為文件夾的模塊
模塊與文件是一一對應的伴鳖。文件不僅可以是 JavaScript 代碼或二進制代碼,還可以是一個文件夾徙硅。最簡單的包榜聂,就是一個作為文件夾的模塊。下面我們來看一個例子嗓蘑,建立一個叫做 somepackage 的文件夾须肆,在其中創(chuàng)建 index.js,內(nèi)容如下:
//somepackage/index.js
exports.hello = function() {
console.log('Hello.');
};
然后在 somepackage 之外建立 getpackage.js桩皿,內(nèi)容如下:
//getpackage.js
var somePackage = require('./somepackage');
somePackage.hello();
運行 node getpackage.js豌汇,控制臺將輸出結果 Hello.。
我們使用這種方法可以把文件夾封裝為一個模塊泄隔,即所謂的包拒贱。包通常是一些模塊的集合,在模塊的基礎上提供了更高層的抽象佛嬉,相當于提供了一些固定接口的函數(shù)庫逻澳。通過定制package.json,我們可以創(chuàng)建更復雜暖呕、更完善斜做、更符合規(guī)范的包用于發(fā)布。
- package.json
在前面例子中的 somepackage 文件夾下缰揪,我們創(chuàng)建一個叫做 package.json的文件,內(nèi)容如下所示:.
{
"main" : "./lib/interface.js"
}
然后將 index.js 重命名為 interface.js 并放入 lib子文件夾下。以同樣的方式再次調用這個包钝腺,依然可以正常使用抛姑。
Node.js 在調用某個包時,會首先檢查包中 package.json 文件的 main 字段艳狐,將其作為包的接口模塊定硝,如果 package.json 或 main 字段不存在,會嘗試尋找index.js 或 index.node 作為包的接口毫目。
package.json 是 CommonJS 規(guī)定的用來描述包的文件蔬啡,完全符合規(guī)范的 package.json 文件應該含有以下字段。
- name:包的名稱镀虐,必須是唯一的箱蟆,由小寫英文字母、數(shù)字和下劃線組成刮便,不能包含空格空猜。
- description:包的簡要說明。
- version:符合語義化版本識別規(guī)范的版本字符串恨旱。
- keywords:關鍵字數(shù)組辈毯,通常用于搜索。
- maintainers:維護者數(shù)組搜贤,每個元素要包含 name谆沃、 email (可選)、 web (可選)字段仪芒。
- contributors:貢獻者數(shù)組唁影,格式與maintainers相同。包的作者應該是貢獻者數(shù)組的第一個元素
- bugs:提交bug的地址桌硫,可以是網(wǎng)址或者電子郵件地址夭咬。
- licenses:許可證數(shù)組,每個元素要包含 type (許可證的名稱)和 url (鏈接到許可證文本的地址)字段铆隘。
- repositories:倉庫托管地址數(shù)組卓舵,每個元素要包含 type(倉庫的類型,如 git )膀钠、url (倉庫的地址)和 path (相對于倉庫的路徑掏湾,可選)字段。
- dependencies:包的依賴肿嘲,一個關聯(lián)數(shù)組融击,由包名稱和版本號組成。下面是一個完全符合 CommonJS 規(guī)范的 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 官方提供的包管理工具①尊浪,它已經(jīng)成了 Node.js 包的標準發(fā)布平臺匣屡,用于 Node.js 包的發(fā)布、傳播拇涤、依賴控制捣作。 npm 提供了命令行工具,使你可以方便地下載鹅士、安裝券躁、升級、刪除包掉盅,也可以讓你作為開發(fā)者發(fā)布并維護包也拜。
4 調試
4.1. 命令行調試
Node.js 支持命令行下的單步調試。下面是一個簡單的程序:
var a = 1;
var b = 'world';
var c = function(x) {
console.log('hello ' + x + a);
};
c(b);
在命令行下執(zhí)行 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 | 執(zhí)行腳本慢哈,在第一行暫停 |
restart | 重新執(zhí)行腳本 |
cont, c | 繼續(xù)執(zhí)行,直到遇到下一個斷點 |
next, n | 單步執(zhí)行 |
step, s | 單步執(zhí)行并進入函數(shù) |
out, o | 從函數(shù)中步出 |
setBreakpoint(), sb() | 在當前行設置斷點 |
setBreakpoint(‘f()’), sb(...) | 在函數(shù)f的第一行設置斷點 |
setBreakpoint(‘script.js’, 20), sb(...) | 在 script.js 的第20行設置斷點 |
clearBreakpoint, cb(...) | 清除所有斷點 |
backtrace, bt | 顯示當前的調用棧 |
list(5) | 顯示當前執(zhí)行到的前后5行代碼 |
watch(expr) | 把表達式 expr 加入監(jiān)視列表 |
unwatch(expr) | 把表達式 expr 從監(jiān)視列表移除 |
watchers | 顯示監(jiān)視列表中所有的表達式和值 |
repl | 在當前上下文打開即時求值環(huán)境 |
kill | 終止當前執(zhí)行的腳本 |
scripts | 顯示當前已加載的所有腳本 |
version | 顯示 V8 的版本 |