console.log('%s: %d', 'Hello', 25); // 可以像C语言格式一样输出
//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
Node.js 只有在第一次引用到某部份时才会去解析脚本文件,以后都会直接访问内存,避免重复载入
Node.js的这种设计虽然有利于提高性能,却不利于开发调试,因为我们在开发过程中总是希望修改后立即看到效果,
而不是每次都要终止进程并重启。
supervisor 可以帮助你实现这个功能,它会监视你对代码的改动,并自动重启 Node.js/
使用方法很简单,首先使用 npm 安装 supervisor:
npm install -g supervisor
接下来,使用 supervisor 命令启动 app.js:
supervisor app.js
异步式 I/O 与事件式编程
这种模式与传统的同步式 I/O 线性的编程思路有很大的不同,因为控制流很大程度上要靠事件
和回调函数来组织,一个逻辑要拆分为若干个单元。
线程必须有事件循环,不断地检查有没有未处理的事件,依次予以处理.
Node.js 使用了单线程、非阻塞的事件编程模式。
异步式编程的缺点在于不符合人们一般的程序设计思维,容易让控制流变得晦涩难懂,给编码和调试都带来不小的困难.
回调函数
让我们看看在 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.
同步的方式:
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.
可以看到, 异步的方式与同步的方式执行结果不同, 同步的方式好理解, 就是传统的程序运行的方式.
异步式读取文件就稍微有些违反直觉了,end.先被输出. 我们必须先知道在 Node.js 中,
异步式 I/O 是通过回调函数来实现的. fs.readFile 接收了三个参数, 第一个是文件名,第二个是编码方式,
第三个是一个函数,我们称这个函数为回调函数。
//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.');
fs.readFile 调用时所做的工作只是将异步式 I/O 请求发送给了操作系统,然后立即返回并执行后面的语句,
执行完以后进入事件循环监听事件。当 fs 接收到 I/O 请求完成的事件时,事件循环会主动调用回调函数以完成后续工作。
因此我们会先看到 end.,再看到file.txt 文件的内容。
Question : 什么时候将控制权返回给事件循环 ?
事件
Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。,事件由 EventEmitter 对象提供。
前面提到的 fs.readFile 和 http.createServer 的回调函数都是通过 EventEmitter 来实现的。
Node.js 的事件循环机制
Node.js 在什么时候会进入事件循环呢?
答案是 Node.js 程序由事件循环开始,到事件循环结束,所有的逻辑都是事件的回调函数,所以 Node.js 始终在事件循环中,
程序入口就是事件循环第一个事件的回调函数。事件的回调函数在执行的过程中,可能会发出 I/O 请求或直接发射(emit)事件,
执行完毕后再返回事件循环, 事件循环会检查事件队列中有没有未处理的事件,直到程序结束。
与其他语言不同的是,Node.js 没有显式的事件循环, Node.js 的事件循环对开发者不可见,由 libev 库实现.
模块和包
模块(Module)和包(Package)是 Node.js 最重要的支柱。开发一个具有一定规模的程序不可能只用一个文件,
通常需要把各个功能拆分、封装,然后组合起来,模块正是为了实现这种方式而诞生的, 而且模块都是基于文件的,机制十分简单。
我们经常把 Node.js 的模块和包相提并论,因为模块和包是没有本质区别的.
模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的. 我们曾经用到了 var http = require('http'),其中 http
是 Node.js 的一个核心模块,其内部是用 C++ 实现的,外部用 JavaScript 封装.(也是一个文件)
我们通过require 函数获取了这个模块,然后才能使用其中的对象。
创建模块
在 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();
从以上例子中, 我们可以看到, "模块即文件", 为了方便引用.
,npm 提供的上万个模块都是通过这种简单的方式搭建起来的.
单次加载
上面这个例子有点类似于创建一个对象,但实际上和对象又有本质的区别,因为require 不会重复加载模块,
也就是说无论调用多少次 require,获得的模块都是同一个。
//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 覆盖,最终输出结果是由后者决定的.
覆盖 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();
注意,模块接口的唯一变化是使用 module.exports = Hello 代替了 exports.Hello=Hello, 另外, 就是文件名变了.
创建包
包是在模块基础上更深一步的抽象,Node.js 的包类似于 C/C++ 的函数库或者 Java/.Net的类库.
它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制.
Node.js 的包是一个目录, 其中包含一个 JSON 格式的包说明文件 package.json。严格按照以下规则:
package.json 必须在包的顶层目录下;
二进制文件应该在 bin 目录下;
JavaScript 代码应该在 lib 目录下;
文档应该在 doc 目录下;
单元测试应该在 test 目录下。
作为文件夹的模块
模块与文件是一一对应的, 文件不仅可以是 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,我们可以创建更复杂、更完善、更符合规范的包用于发布.
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 作为包的接口。
下面是一个完全符合 CommonJS 规范的 package.json 示例:
Node.js 包管理器
Node.js包管理器,即npm是 Node.js 官方提供的包管理工具,它已经成了 Node.js 包的标准发布平台,
用于 Node.js 包的发布、传播、依赖控制。npm 提供了命令行工具,使你可以方便地下载、安装、升级、删除包,也可以让你作为开发者发布并维护包。
获取一个包
npm [install/i] [package_name], 例如 : npm install express
并且放置在当前目录的 node_modules 子目录下.
本地模式和全局模式
npm在默认情况下会从http://npmjs.org搜索或下载包,将包安装到当前目录的node_modules子目录下。
在使用 npm 安装包的时候,有两种模式:本地模式和全局模式。默认情况下我们使用 npminstall命令就是采用本地模式,
即把包安装到当前目录的 node_modules 子目录下. Node.js的 require 在加载模块时会尝试搜寻 node_modules 子目录,
因此使用 npm 本地模式安装的包可以直接被引用。
npm 还有另一种不同的安装模式被成为全局模式,使用方法为:npm [install/i] -g [package_name]
我们在 介绍 supervisor那个小节中使用了 npm install -g supervisor 命令,就是以全局模式安装 supervisor。
为什么要使用全局模式呢?
因为本地模式不会注册 PATH 环境变量.举例说明,我们安装supervisor 是为了在命令行中运行它,譬如直接运行 supervisor script.js,
这时就需要在 PATH环境变量中注册 supervisor。
使用全局模式安装的包并不能直接在 JavaScript 文件中用 require 获得,因为 require 不会搜索PATH中对应的某个目录.
总而言之,当我们要把某个包作为工程运行时的一部分时,通过本地模式获取,如果要在命令行下使用,则使用全局模式安装。
创建全局链接
npm 提供了一个有趣的命令 npm link,它的功能是在本地包和全局包之间创建符号链接。
我们说过使用全局模式安装的包不能直接通过 require 使用,但通过 npm link命令可以打破这一限制.
npm link 命令不支持Windows
包的发布
npm 可以非常方便地发布一个包, 通过使用 npm init 可以根据交互式问答产生一个符合标准的 package.json,
例如创建一个名为 byvoidmodule 的目录,然后在这个目录中运行npm init:
Package name: (byvoidmodule) byvoidmodule
Description: A module for learning perpose.
Package version: (0.0.0) 0.0.1
Project homepage: (none) http://www.byvoid.com/
Project git repository: (none)
Author name: BYVoid
Author email: (none) byvoid.kcp@gmail.com
Author url: (none) http://www.byvoid.com/
Main module/entry point: (none)
Test command: (none)
What versions of node does it run on? (~0.6.10)
About to write to /home/byvoid/byvoidmodule/package.json
{
"author": "BYVoid <byvoid.kcp@gmail.com> (http://www.byvoid.com/)",
"name": "byvoidmodule",
"description": "A module for learning perpose.",
"version": "0.0.1",
"homepage": "http://www.byvoid.com/",
"repository": {
"url": ""
},
"engines": {
"node": "~0.6.12"
},
"dependencies": {},
"devDependencies": {}
}
Is this ok? (yes) yes
接下来,在 package.json 所在目录下运行 npm publish,稍等片刻就可以完成发布了。
打开浏览器,访问 http://search.npmjs.org/ 就可以找到自己刚刚发布的包了。
现在我们可以在世界的任意一台计算机上使用 npm install byvoidmodule 命令来安装它
如果你的包将来有更新,只需要在 package.json 文件中修改 version 字段, 然后重新使用 npm publish 命令就行了.
如果你对已发布的包不满意, 可以使用 npm unpublish 命令来取消发布。
调试
命令行调试
在命令行下执行 node debug debug.js,将会启动调试工具
使用 eclipse 调试