node.js快速入門

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

  1. 創建模塊

在 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 對象的成員函數了。

  1. 單次加載

上面這個例子有點類似于創建一個對象,但實際上和對象又有本質的區別,因為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 覆蓋,最終輸出結果是由后者決定的。

  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 指向同一個對象的變量,它本身會在模塊執行結束后釋放,但 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 目錄下。
  1. 作為文件夾的模塊

模塊與文件是一一對應的。文件不僅可以是 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,我們可以創建更復雜、更完善、更符合規范的包用于發布。

  1. 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 的版本
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容