console.log('%s: %d', 'Hello', 25); // 可以像C语言格式一样输出
//app.jsvar 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.");小技巧——使用 supervisorNode.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.jsvar 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.jsContents of the file.end.可以看到, 异步的方式与同步的方式执行结果不同, 同步的方式好理解, 就是传统的程序运行的方式.异步式读取文件就稍微有些违反直觉了,end.先被输出. 我们必须先知道在 Node.js 中,异步式 I/O 是通过回调函数来实现的. fs.readFile 接收了三个参数, 第一个是文件名,第二个是编码方式,第三个是一个函数,我们称这个函数为回调函数。//readfilecallback.jsfunction 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.jsvar name;exports.setName = function(thyName) { name = thyName;};exports.sayHello = function() { console.log('Hello ' + name);};在同一目录下创建 getmodule.js,内容是://getmodule.jsvar myModule = require('./module');myModule.setName('BYVoid');myModule.sayHello();从以上例子中, 我们可以看到, "模块即文件", 为了方便引用.,npm 提供的上万个模块都是通过这种简单的方式搭建起来的.单次加载
上面这个例子有点类似于创建一个对象,但实际上和对象又有本质的区别,因为require 不会重复加载模块,
也就是说无论调用多少次 require,获得的模块都是同一个。//loadmodule.jsvar 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.jsfunction Hello() { var name;this.setName = function (thyName) { name = thyName;};this.sayHello = function () { console.log('Hello ' + name);};};exports.Hello = Hello;此时我们在其他文件中需要通过 require('./singleobject').Hello 来获取Hello 对象,这略显冗余,可以用下面方法稍微简化://hello.jsfunction Hello() { var name;this.setName = function(thyName) { name = thyName;};this.sayHello = function() { console.log('Hello ' + name);};};module.exports = Hello;这样就可以直接获得这个对象了://gethello.jsvar 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.jsexports.hello = function() { console.log('Hello.');}; 然后在 somepackage 之外建立 getpackage.js,内容如下://getpackage.jsvar 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) byvoidmoduleDescription: A module for learning perpose.Package version: (0.0.0) 0.0.1Project homepage: (none) http://www.byvoid.com/Project git repository: (none)Author name: BYVoidAuthor email: (none) byvoid.kcp@gmail.comAuthor 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 调试