Node.js快速入门

jopen 8年前

1 异步式I/O  与事件式编程

Node.js 最大的特点就是异步式I/O (或者非阻塞 I/O )与事件紧密结合的编程模式。这种模式与传统的同步式 I/O  线性的编程思路有很大的不同,因为控制流很大程度上要靠事件和回调函数来组织,一个逻辑要拆分为若干个单元。

阻塞模式下,一个线程只能处理一项任务,要想提高吞吐量必须通过多线程。而非阻塞

模式下,一个线程永远在执行计算操作,这个线程所使用的 CPU  核心利用率永远是 100%,

I/O  以事件的方式通知。在阻塞模式下,多线程往往能提高系统吞吐量,因为一个线程阻塞时还有其他线程在工作,多线程可以让 CPU  资源不被阻塞中的线程浪费。而在非阻塞模式下,线程不会被 I/O  阻塞,永远在利用 CPU。多线程带来的好处仅仅是在多核 CPU  的情况下利用更多的核,而Node.js的单线程也能带来同样的好处。这就是为什么 Node.js  使用了单线程、非阻塞的事件编程模式。

单线程事件驱动的异步式 I/O  比传统的多线程阻塞式 I/O  究竟好在哪里呢?简而言之,异步式I/O  就是少了多线程的开销。对操作系统来说,创建一个线程的代价是十分昂贵的,需要给它分配内存、列入调度,同时在线程切换的时候还要执行内存换页,CPU 的缓存被清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性。

让我们看看在 Node.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("Hello World \n");  }).listen(8080, "127.0.0.1");    console.log("Server running at http://127.0.0.1:8080/");

运行的结果如下:

end.

Contents of thefile.


Node.js 也提供了同步读取文件的API :

//readfilesync.js      var  fs = require('fs');   var  data = fs.readFileSync('file.txt', 'utf-8');   console.log(data);   console.log('end.');

运行的结果与前面不同,如下所示:

$ nodereadfilesync.js

Contents of thefile.

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

fs.readFile 调用时所做的工作只是将异步式I/O  请求发送给了操作系统,然后立即返回并执行后面的语句,执行完以后进入事件循环监听事件。当 fs 接收到 I/O  请求完成的事件时,事件循环会主动调用回调函数以完成后续工作。因此我们会先看到 end. ,再看到file.txt  文件的内容。

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  的监听器。

Node.js 的事件循环机制

Node.js 在什么时候会进入事件循环呢?答案是Node.js  程序由事件循环开始,到事件循

环结束,所有的逻辑都是事件的回调函数,所以 Node.js  始终在事件循环中,程序入口就是事件循环第一个事件的回调函数。事件的回调函数在执行的过程中,可能会发出 I/O  请求或直接发射(emit )事件,执行完毕后再返回事件循环,事件循环会检查事件队列中有没有未处理的事件,直到程序结束,下图说明了事件循环的原理。


3、模块和包

模块(Module)和包(Package)是  Node.js 最重要的支柱。开发一个具有一定规模的程序不可能只用一个文件,通常需要把各个功能拆分、封装,然后组合起来,模块正是为了实现这种方式而诞生的。在浏览器 JavaScript  中,脚本模块的拆分和组合通常使用 HTML  的 script 标签来实现。Node.js提供了 require 函数来调用其他模块,而且模块都是基于

文件的,机制十分简单。

Node.js  的模块和包机制的实现参照了CommonJS  的标准,但并未完全遵循。不过

两者的区别并不大,一般来说你大可不必担心,只有当你试图制作一个除了支持 Node.js

之外还要支持其他平台的模块或包的时候才需要仔细研究。通常,两者没有直接冲突的

地方。

我们经常把 Node.js  的模块和包相提并论,因为模块和包是没有本质区别的,两个概念也时常混用。如果要辨析,那么可以把包理解成是实现了某个功能模块的集合,用于发布

和维护。对使用者来说,模块和包的区别是透明的,因此经常不作区分。

3.1、什么是模块

         模块是Node.js  应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个

Node.js 文件就是一个模块,这个文件可能是JavaScript  代码、JSON  或者编译过的 C/C++ 扩展。

3.2  创建及加载模块

介绍了什么是模块之后,下面我们来看看如何创建并加载它们。在 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 对象的成员函数了。

3.4  创建包

包是在模块基础上更深一步的抽象,Node.js的包类似于 C/C++  的函数库或者Java/.Net

的类库。它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制。Node.js 根

据 CommonJS 规范实现了包机制,开发了 npm来解决包的发布和获取需求。

Node.js 的包是一个目录,其中包含一个 JSON  格式的包说明文件package.json 。严格符

合 CommonJS 规范的包应该具备以下特征:

  package.json 必须在包的顶层目录下; 

  二进制文件应该在 bin  目录下; 

  JavaScript 代码应该在 lib  目录下; 

  文档应该在 doc  目录下; 

  单元测试应该在 test  目录下。

Node.js 对包的要求并没有这么严格,只要顶层目录下有 package.json ,并符合一些规范即可。当然为了提高兼容性,我们还是建议你在制作包的时候,严格遵守 CommonJS 规范。

3.4.1. 作为文件夹的模块

模块与文件是一一对应的。文件不仅可以是JavaScript  代码或二进制代码,还可以是一个文件夹。最简单的包,就是一个作为文件夹的模块。下面我们来看一个例子,建立一个叫做package  的文件夹,在其中创建index.js ,内容如下:

//package/index.js      exports.hello =  function () {     console.log('Hello world.');   };
然后在 package  之外建立 getpackage.js ,内容如下:
//getpackage.js      var package = require('./ package');      package.hello();

运行 node getpackage.js,控制台将输出结果 Hello world.。


我们使用这种方法可以把文件夹封装为一个模块,即所谓的包。包通常是一些模块的集

合,在模块的基础上提供了更高层的抽象,相当于提供了一些固定接口的函数库。通过定制

package.json,我们可以创建更复杂、更完善、更符合规范的包用于发布。

3.4.2. package.json

在前面例子中的package  文件夹下,我们创建一个叫做package.json  的文件,内容如

下所示:

{

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

}

然后将 index.js  重命名为interface.js  并放入 lib  子文件夹下。以同样的方式再次调用这个包,依然可以正常使用。

Node.js 在调用某个包时,会首先检查包中 package.json  文件的 main 字段,将其作为包的接口模块,如果 package.json  或 main 字段不存在,会尝试寻找index.js  或 index.node  作为包的接口。

3.5 Node.js 包管理器

Node.js包管理器,即npm 是Node.js  官方提供的包管理工具,它已经成了Node.js  包的标准发布平台,用于Node.js  包的发布、传播、依赖控制。npm 提供了命令行工具,使你可以方便地下载、安装、升级、删除包,也可以让你作为开发者发布并维护包。

  3.5.1. 获取一个包

使用 npm  安装包的命令格式为:

npm [install/i][package_name]

例如你要安装 express ,可以在命令行运行:

$ npm install express

或者:

$ npm i express

随后你会看到以下安装信息:

npm http GEThttps://registry.npmjs.org/express

npm http 304https://registry.npmjs.org/express

npm http GEThttps://registry.npmjs.org/mime/1.2.4

npm http GEThttps://registry.npmjs.org/mkdirp/0.3.0

npm http GEThttps://registry.npmjs.org/qs

npm http GEThttps://registry.npmjs.org/connect

npm http 200https://registry.npmjs.org/mime/1.2.4

npm http 200https://registry.npmjs.org/mkdirp/0.3.0

npm http 200https://registry.npmjs.org/qs

npm http GEThttps://registry.npmjs.org/mime/-/mime-1.2.4.tgz

npm http GEThttps://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz

npm http 200https://registry.npmjs.org/mime/-/mime-1.2.4.tgz

npm http 200https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz

npm http 200https://registry.npmjs.org/connect

npm http GEThttps://registry.npmjs.org/formidable

npm http 200https://registry.npmjs.org/formidable

express@2.5.8./node_modules/express

-- mime@1.2.4

-- mkdirp@0.3.0

-- qs@0.4.2

-- connect@1.8.5

此时 express 就安装成功了,并且放置在当前目录的 node_modules  子目录下。npm 在npm 之于 Node.js ,就像  pip  之于 Python,gem  之于 Ruby ,pear  之于 PHP,CPAN  之于 Perl ……同时也像apt-get 之于 Debian/Ubutnu ,yum 之于 Fedora/RHEL/CentOS ,homebrew 之于 Mac OS X 。获取 express 的时候还将自动解析其依赖,并获取 express   依赖的 mime、mkdirp、qs  和  connect。

3.5.2. 本地模式和全局模式

npm 在默认情况下会从http://npmjs.org搜索或下载包,将包安装到当前目录的node_modules子目录下。

在使用 npm 安装包的时候,有两种模式:本地模式和全局模式。默认情况下我们使用  npm install命令就是采用本地模式,即把包安装到当前目录的 node_modules  子目录下。Node.js 的  require 在加载模块时会尝试搜寻 node_modules 子目录,因此使用 npm  本地模式安装的包可以直接被引用。

npm 还有另一种不同的安装模式被成为全局模式,使用方法为: npm [install/i] -g [package_name] 与本地模式的不同之处就在于多了一个参数-g。我们在  介绍 supervisor 那个小节中使用了 npm  install -gsupervisor  命令,就是以全局模式安装supervisor 。

3.5.3包的发布

npm 可以非常方便地发布一个包,比 pip 、gem 、pear  要简单得多。在发布之前,首先需要让我们的包符合 npm 的规范,npm 有一套以 CommonJS 为基础包规范,但与 CommonJS 并不完全一致,其主要差别在于必填字段的不同。通过使用 npm init 可以根据交互式问答产生一个符合标准的package.json,例如创建一个名为 module 的目录,然后在这个目录中运行npm init :

$ npm init;根据提示完成输入后就可以了;

这样就在module 目录中生成一个符合npm  规范的 package.json  文件。创建一个 index.js  作为包的接口,一个简单的包就制作完成了。

在发布前,我们还需要获得一个账号用于今后维护自己的包,使用  npm adduser  根据提示输入用户名、密码、邮箱,等待账号创建完成。完成后可以使用  npm whoami 测验是否已经取得了账号。 接下来,在package.json  所在目录下运行 npmpublish ,稍等片刻就可以完成发布了。打开浏览器,访问 http://search.npmjs.org/  就可以找到自己刚刚发布的包了。现在我们可以在世界的任意一台计算机上使用 npm install byvoidmodule 命令来安装它。 如果你的包将来有更新,只需要在 package.json  文件中修改  version 字段,然后重新使用 npm publish   命令就行了。如果你对已发布的包不满意(比如我们发布的这个毫无意义的包),可以使用  npm unpublish   命令来取消发布。



来自: http://blog.csdn.net//u011067360/article/details/17710071