NodeJS 初体验

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 调试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值