node.js快速入門

1. 開始使用nodejs


1.1. Hello Word

好了,讓我們開始實(shí)現(xiàn)第一個(gè) Node.js 程序吧。打開你常用的文本編輯器,在其中輸入:

console.log('Hello World');

將文件保存為 helloworld.js,打開命令行工具,進(jìn)入 helloworld.js所在的目錄,執(zhí)行以下命令:

node helloworld.js

如果一切正常,你將會在命令行工具中看到輸出 Hello World。

1.2. Node.js命令行工具

在前面的 Hello World 示例中,我們用到了命令行中的 node 命令,輸入 node --help
可以看到詳細(xì)的幫助信息:

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 的用法,運(yùn)行 Node.js 程序的基本方法就是執(zhí)行 node script.js,
其中 script.js是腳本的文件名。

除了直接運(yùn)行腳本文件外, node --help 顯示的使用方法中說明了另一種輸出 Hello
World 的方式:

$ node -e "console.log('Hello World');"

Hello World

我們可以把要執(zhí)行的語句作為 node -e 的參數(shù)直接執(zhí)行。

1.3 建立 HTTP 服務(wù)器

讓我們創(chuàng)建一個(gè) HTTP 服務(wù)器吧。建立一個(gè)名為 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.

當(dāng)代碼被改動時(shí),運(yùn)行的腳本會被終止,然后重新啟動。在終端中顯示的結(jié)果如下:

DEBUG: crashing child
DEBUG: Starting child process with 'node app.js'
HTTP server is listening at port 3000.

2. 異步式編程


2.1. 回調(diào)函數(shù)

讓我們看看在 Node.js 中如何用異步的方式讀取一個(gè)文件,下面是一個(gè)例子:

//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.');

運(yùn)行結(jié)果:

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.');

運(yùn)行的結(jié)果與前面不同,如下所示:

$ node readfilesync.js
Contents of the file.
end.

同步式讀取文件的方式比較容易理解,將文件名作為參數(shù)傳入 fs.readFileSync 函數(shù),阻塞等待讀取完成后,將文件的內(nèi)容作為函數(shù)的返回值賦給 data變量,接下來控制臺輸出 data 的值,最后輸出 end.。

異步式讀取文件就稍微有些違反直覺了, end.先被輸出。要想理解結(jié)果,我們必須先知道在 Node.js 中,異步式 I/O是通過回調(diào)函數(shù)來實(shí)現(xiàn)的。 fs.readFile 接收了三個(gè)參數(shù),第一個(gè)是文件名,第二個(gè)是編碼方式,第三個(gè)是一個(gè)函數(shù),我們稱這個(gè)函數(shù)為回調(diào)函數(shù)。JavaScript 支 持 匿 名 的 函 數(shù) 定 義 方 式 , 譬 如 我 們 例 子 中 回 調(diào) 函 數(shù) 的 定 義 就 是 嵌 套 在fs.readFile 的參數(shù)表中的。這種定義方式在 JavaScript中極為普遍,與下面這種定義
方式實(shí)現(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 操作在完成時(shí)都會發(fā)送一個(gè)事件到事件隊(duì)列。在開發(fā)者看來,事件由 EventEmitter 對象提供。前面提到的 fs.readFile 和 http.createServer 的回調(diào)函數(shù)都是通過 EventEmitter 來實(shí)現(xiàn)的。下面我們用一個(gè)簡單的例子說明 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);

運(yùn)行這段代碼, 1秒后控制臺輸出了 some_event occured.。其原理是 event 對象注冊了事件 some_event 的一個(gè)監(jiān)聽器,然后我們通過 setTimeout在1000毫秒以后向
event 對象發(fā)送事件 some_event,此時(shí)會調(diào)用 some_event 的監(jiān)聽器。

3. 模塊和包


3.1. 什么是模塊

模塊是 Node.js 應(yīng)用程序的基本組成部分,文件和模塊是一一對應(yīng)的。換言之,一個(gè)Node.js 文件就是一個(gè)模塊,這個(gè)文件可能是 JavaScript 代碼、 JSON 或者編譯過的 C/C++擴(kuò)展。
在前面章節(jié)的例子中,我們曾經(jīng)用到了 var http = require('http'), 其中 http是 Node.js 的一個(gè)核心模塊,其內(nèi)部是用 C++ 實(shí)現(xiàn)的,外部用 JavaScript 封裝。我們通過require 函數(shù)獲取了這個(gè)模塊,然后才能使用其中的對象。

3.2. 創(chuàng)建及加載模塊

  1. 創(chuàng)建模塊

在 Node.js 中,創(chuàng)建一個(gè)模塊非常簡單,因?yàn)橐粋€(gè)文件就是一個(gè)模塊,我們要關(guān)注的問題僅僅在于如何在其他文件中獲取這個(gè)模塊。 Node.js 提供了 exports 和 require 兩個(gè)對象,其中 exports 是模塊公開的接口, require用于從外部獲取一個(gè)模塊的接口,即所獲取模塊的 exports 對象。

創(chuàng)建一個(gè) 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();

運(yùn)行node getmodule.js,結(jié)果是:

Hello BYVoid

在以上示例中, module.js 通過 exports 對象把 setName 和 sayHello 作為模塊的訪問接口,在 getmodule.js 中通過 require('./module') 加載這個(gè)模塊,然后就可以直接訪問 module.js 中 exports 對象的成員函數(shù)了。

  1. 單次加載

上面這個(gè)例子有點(diǎn)類似于創(chuàng)建一個(gè)對象,但實(shí)際上和對象又有本質(zhì)的區(qū)別,因?yàn)閞equire 不會重復(fù)加載模塊,也就是說無論調(diào)用多少次 require, 獲得的模塊都是同一個(gè)。我們在 getmodule.js 的基礎(chǔ)上稍作修改:

//loadmodule.js

    var hello1 = require('./module');
    hello1.setName('BYVoid');

    var hello2 = require('./module');
    hello2.setName('BYVoid 2');

    hello1.sayHello();

運(yùn)行后發(fā)現(xiàn)輸出結(jié)果是 Hello BYVoid 2,這是因?yàn)樽兞?hello1 和 hello2 指向的是同一個(gè)實(shí)例,因此 hello1.setName 的結(jié)果被 hello2.setName 覆蓋,最終輸出結(jié)果是由后者決定的。

  1. 覆蓋 exports

有時(shí)候我們只是想把一個(gè)對象封裝到模塊中,例如:

//singleobject.js

    function Hello() {
        var name;
        this.setName = function (thyName) {
        name = thyName;
    };
    this.sayHello = function () {
        console.log('Hello ' + name);
    };
};

exports.Hello = Hello;

此時(shí)我們在其他文件中需要通過 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;

這樣就可以直接獲得這個(gè)對象了:

//gethello.js

    var Hello = require('./hello');
    hello = new Hello();
    hello.setName('BYVoid');
    hello.sayHello();

不可以通過對 exports 直接賦值代替對 module.exports 賦值。exports 實(shí)際上只是一個(gè)和 module.exports 指向同一個(gè)對象的變量,它本身會在模塊執(zhí)行結(jié)束后釋放,但 module 會,因此只能通過指定module.exports 來改變訪問接口。

3.3 創(chuàng)建包

包是在模塊基礎(chǔ)上更深一步的抽象, Node.js 的包類似于 C/C++ 的函數(shù)庫或者 Java/.Net的類庫。它將某個(gè)獨(dú)立的功能封裝起來,用于發(fā)布、更新、依賴管理和版本控制。 Node.js 根據(jù) CommonJS 規(guī)范實(shí)現(xiàn)了包機(jī)制,開發(fā)了 npm來解決包的發(fā)布和獲取需求。

Node.js 的包是一個(gè)目錄,其中包含一個(gè) JSON 格式的包說明文件 package.json。嚴(yán)格符合 CommonJS 規(guī)范的包應(yīng)該具備以下特征:

  • package.json 必須在包的頂層目錄下;
  • 二進(jìn)制文件應(yīng)該在 bin 目錄下;
  • JavaScript 代碼應(yīng)該在 lib 目錄下;
  • 文檔應(yīng)該在 doc 目錄下;
  • 單元測試應(yīng)該在 test 目錄下。
  1. 作為文件夾的模塊

模塊與文件是一一對應(yīng)的。文件不僅可以是 JavaScript 代碼或二進(jìn)制代碼,還可以是一個(gè)文件夾。最簡單的包,就是一個(gè)作為文件夾的模塊。下面我們來看一個(gè)例子,建立一個(gè)叫做 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();

運(yùn)行 node getpackage.js,控制臺將輸出結(jié)果 Hello.。

我們使用這種方法可以把文件夾封裝為一個(gè)模塊,即所謂的包。包通常是一些模塊的集合,在模塊的基礎(chǔ)上提供了更高層的抽象,相當(dāng)于提供了一些固定接口的函數(shù)庫。通過定制package.json,我們可以創(chuàng)建更復(fù)雜、更完善、更符合規(guī)范的包用于發(fā)布。

  1. package.json

在前面例子中的 somepackage 文件夾下,我們創(chuàng)建一個(gè)叫做 package.json的文件,內(nèi)容如下所示:.

{
"main" : "./lib/interface.js"
}

然后將 index.js 重命名為 interface.js 并放入 lib子文件夾下。以同樣的方式再次調(diào)用這個(gè)包,依然可以正常使用。

Node.js 在調(diào)用某個(gè)包時(shí),會首先檢查包中 package.json 文件的 main 字段,將其作為包的接口模塊,如果 package.json 或 main 字段不存在,會嘗試尋找index.js 或 index.node 作為包的接口。

package.json 是 CommonJS 規(guī)定的用來描述包的文件,完全符合規(guī)范的 package.json 文件應(yīng)該含有以下字段。

  • name:包的名稱,必須是唯一的,由小寫英文字母、數(shù)字和下劃線組成,不能包含空格。
  • description:包的簡要說明。
  • version:符合語義化版本識別規(guī)范的版本字符串。
  • keywords:關(guān)鍵字?jǐn)?shù)組,通常用于搜索。
  • maintainers:維護(hù)者數(shù)組,每個(gè)元素要包含 name、 email (可選)、 web (可選)字段。
  • contributors:貢獻(xiàn)者數(shù)組,格式與maintainers相同。包的作者應(yīng)該是貢獻(xiàn)者數(shù)組的第一個(gè)元素
  • bugs:提交bug的地址,可以是網(wǎng)址或者電子郵件地址。
  • licenses:許可證數(shù)組,每個(gè)元素要包含 type (許可證的名稱)和 url (鏈接到許可證文本的地址)字段。
  • repositories:倉庫托管地址數(shù)組,每個(gè)元素要包含 type(倉庫的類型,如 git )、url (倉庫的地址)和 path (相對于倉庫的路徑,可選)字段。
  • dependencies:包的依賴,一個(gè)關(guān)聯(lián)數(shù)組,由包名稱和版本號組成。下面是一個(gè)完全符合 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 包的標(biāo)準(zhǔn)發(fā)布平臺,用于 Node.js 包的發(fā)布、傳播、依賴控制。 npm 提供了命令行工具,使你可以方便地下載、安裝、升級、刪除包,也可以讓你作為開發(fā)者發(fā)布并維護(hù)包。

4 調(diào)試

4.1. 命令行調(diào)試

Node.js 支持命令行下的單步調(diào)試。下面是一個(gè)簡單的程序:

var a = 1;
var b = 'world';

var c = function(x) {
    console.log('hello ' + x + a);
};

c(b);

在命令行下執(zhí)行 node debug debug.js,將會啟動調(diào)試工具:

< 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í)行,直到遇到下一個(gè)斷點(diǎn)
next, n 單步執(zhí)行
step, s 單步執(zhí)行并進(jìn)入函數(shù)
out, o 從函數(shù)中步出
setBreakpoint(), sb() 在當(dāng)前行設(shè)置斷點(diǎn)
setBreakpoint(‘f()’), sb(...) 在函數(shù)f的第一行設(shè)置斷點(diǎn)
setBreakpoint(‘script.js’, 20), sb(...) 在 script.js 的第20行設(shè)置斷點(diǎn)
clearBreakpoint, cb(...) 清除所有斷點(diǎn)
backtrace, bt 顯示當(dāng)前的調(diào)用棧
list(5) 顯示當(dāng)前執(zhí)行到的前后5行代碼
watch(expr) 把表達(dá)式 expr 加入監(jiān)視列表
unwatch(expr) 把表達(dá)式 expr 從監(jiān)視列表移除
watchers 顯示監(jiān)視列表中所有的表達(dá)式和值
repl 在當(dāng)前上下文打開即時(shí)求值環(huán)境
kill 終止當(dāng)前執(zhí)行的腳本
scripts 顯示當(dāng)前已加載的所有腳本
version 顯示 V8 的版本
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,995評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評論 2 374

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