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

如果一切正常,你將會(huì)在命令行工具中看到輸出 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會(huì)監(jiān)視你對代碼的改動(dòng),并自動(dòng)重啟 Node.js。
使用方法很簡單,首先使用 npm 安裝 supervisor:

$ npm install -g supervisor

接下來,使用 supervisor 命令啟動(dòng) 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)代碼被改動(dòng)時(shí),運(yùn)行的腳本會(huì)被終止,然后重新啟動(dòng)。在終端中顯示的結(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變量,接下來控制臺(tái)輸出 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í)都會(huì)發(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秒后控制臺(tái)輸出了 some_event occured.。其原理是 event 對象注冊了事件 some_event 的一個(gè)監(jiān)聽器,然后我們通過 setTimeout在1000毫秒以后向
event 對象發(fā)送事件 some_event,此時(shí)會(huì)調(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 不會(huì)重復(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è)對象的變量,它本身會(huì)在模塊執(zhí)行結(jié)束后釋放,但 module 會(huì),因此只能通過指定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,控制臺(tái)將輸出結(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í),會(huì)首先檢查包中 package.json 文件的 main 字段,將其作為包的接口模塊,如果 package.json 或 main 字段不存在,會(huì)嘗試尋找index.js 或 index.node 作為包的接口。

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

  • name:包的名稱,必須是唯一的,由小寫英文字母、數(shù)字和下劃線組成,不能包含空格。
  • description:包的簡要說明。
  • version:符合語義化版本識(shí)別規(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ù)組,由包名稱和版本號(hào)組成。下面是一個(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ā)布平臺(tái),用于 Node.js 包的發(fā)布、傳播、依賴控制。 npm 提供了命令行工具,使你可以方便地下載、安裝、升級(jí)、刪除包,也可以讓你作為開發(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,將會(huì)啟動(dòng)調(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)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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