node.js快速入門

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)建及加載模塊

  1. 創(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ù)了。

  1. 單次加載

上面這個例子有點類似于創(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 覆蓋讲坎,最終輸出結果是由后者決定的。

  1. 覆蓋 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 目錄下鲜滩。
  1. 作為文件夾的模塊

模塊與文件是一一對應的伴鳖。文件不僅可以是 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ā)布。

  1. 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 的版本
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扼脐,一起剝皮案震驚了整個濱河市岸军,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓦侮,老刑警劉巖艰赞,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異肚吏,居然都是意外死亡方妖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門罚攀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來党觅,“玉大人,你說我怎么就攤上這事斋泄”埃” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵炫掐,是天一觀的道長魁莉。 經(jīng)常有香客問我,道長募胃,這世上最難降的妖魔是什么旗唁? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮痹束,結果婚禮上检疫,老公的妹妹穿的比我還像新娘。我一直安慰自己祷嘶,他們只是感情好屎媳,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布夺溢。 她就那樣靜靜地躺著,像睡著了一般烛谊。 火紅的嫁衣襯著肌膚如雪企垦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天晒来,我揣著相機與錄音,去河邊找鬼郑现。 笑死湃崩,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的接箫。 我是一名探鬼主播攒读,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辛友!你這毒婦竟也來了薄扁?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤废累,失蹤者是張志新(化名)和其女友劉穎邓梅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邑滨,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡日缨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了掖看。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匣距。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哎壳,靈堂內(nèi)的尸體忽然破棺而出毅待,到底是詐尸還是另有隱情,我是刑警寧澤归榕,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布尸红,位于F島的核電站,受9級特大地震影響蹲坷,放射性物質發(fā)生泄漏驶乾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一循签、第九天 我趴在偏房一處隱蔽的房頂上張望级乐。 院中可真熱鬧,春花似錦县匠、人聲如沸风科。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贼穆。三九已至题山,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間故痊,已是汗流浹背顶瞳。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留愕秫,地道東北人慨菱。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像戴甩,于是被迫代替她去往敵國和親符喝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容