您的位置:首页 > Web前端 > Node.js

Node.js快速入门

2013-12-31 10:25 399 查看
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 命令来取消发布。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: