node.js概述

关于node.js

Node.js 是服务器端的 JavaScript 运行环境,它具有无阻塞(non-blocking)和事件驱动(event-driven)等的特色,Node.js 采用V8引擎,同样,Node.js实现了类似 Apache 和 nginx 的web服务,让你可以通过它来搭建基于 JavaScript的Web App。
Node.js采用C++语言编写而成,是一个跨平台的Javascript的运行环境(解释器),这意味着你可以编写系统级或者服务器端的Javascript代码,交给Node.js来解释执行。node.js是对Google V8引擎的封装和优化。V8引擎执行Javascript的速度非常快,性能非常好。同时还提供了很多系统级的API,如文件操作、网络编程等。浏览器端的Javascript代码在运行时会受到各种安全性的限制,对客户系统的操作有限。相比之下,Node.js则是一个全面的后台运行时,为Javascript提供了其他语言能够实现的许多功能。
node.js使得javascript扩展到服务器端,统一了WEB开发前后端的语言。
Node.js采用事件驱动、异步编程,为网络服务而设计。重要的优势在于,充分利用了系统资源,执行代码无须阻塞等待某种操作完成,有限的资源可以用于其他的任务。此类设计非常适合于后端的网络服务编程。
node.js有大量的插件,社区蓬勃,可以实现从web服务器到文档服务器到命令行工具等五花八门的功能,实为全能平台。
缺陷:
node.js是为服务器端响应大量用户的并发请求设计的,对于单用户/多任务型应用不太理想。比如在提供图形界面的同时进行某些计算。node.js适用于I/O密集型而不是计算你密集型应用。受制于它的单线程特点,对于计算密集型的应用,例如某个事件的回调函数要进行复杂的计算,那么事件循环中的所有请求都必须等待。另外,由于node.js的控制流不是线性的,它被一个个事件拆散,所以,在处理复杂的逻辑时会变得过于复杂。
综上所述,node.js适用于逻辑简单但访问频繁的任务:
(1)提供web server;
(2)访问数据库;
(3)访问文件系统(读写磁盘);
(4)与操作系统交互;
(5)运行C/C++扩展。
总之,就是后端I/O的部分。不是I/O的部分,包括业务逻辑的执行,应该交给前端MVC框架来完成。

学习node.js主要就是学习它的API。


node.js下的编程规范

1.两格缩进:以应付过多的嵌套。
2.使用尾括号,如:
function func(boolVar) {
if (boolVar) {
console.log('True');
} else {
console.log('False');
}
};
3.行宽:为了保证在任何设备上都可以方便地阅读,建议把行宽限制为80个字符。
4.永远使用var 定义变量。确保每个语句定义一个变量,不要使用逗号同时定义多个变量。
5.使用小驼峰法命名所有变量、函数和属性。下横线命名所有事件。但类的名字使用大驼峰法。
6.引号:建议需要使用引号的地方一律使用双引号。因为JSON指定使用双引号。
7.相等:在JS中使用===代表等号,在CS中使用==。
8.回调函数:回调函数的名字在CS中一律用cb代表。node的API和其他第三方模块大多约定回调函数的第一个参数是err,因为错误肯定会被输出,所以不用显式地写if(err)...语句了。除非你要显示专门的出错信息。

9.所有代码都预先编译为javascript再放到node.js下运行。

node.js的安装

首先要选择node.js的版本。node.gyp(编译C/C++扩展的工具)对版本有一定要求(v0.8.9),但考虑到它只是在编译扩展时采用,而且最好安装在目标平台上(Windows/Linux/MacOS)用。所以,平时就放心大胆地用node的最新版本好了(基本是在Ubuntu下开发)。
自动安装:现代的ubuntu系统如12.0都可以通过软件仓库来自动安装node.js.或用命令apt-get install nodejs
手动安装:登陆官网http://nodejs.org, 下载最新安装包。解压到/software文件夹里,执行标准编译安装命令:
$ ./configure
$ make
$ sudo make install
5分钟左右就编译安装完了,已自带npm。(也可以通过make uninstall 卸载)
前提是系统中有编译器(如G++)、python(2.6+)等编译环境。参见《nodeJS+开发指南》。
由于可编译扩展的老版本,又需要功能最多的新版本,所以要在一个系统上保持多个node.js版。这时可使用多版本包管理器--n。官网https://github.com/visionmedia/n
如果你已经安装好了 Node.js 和 npm 环境,就可以直接使用 npm install -g n 命令来安装 n。
在终端中运行 n --help 即可看到它的使用说明。运行sudo n 显示所有已安装的node.js版本。在终端中运行 n 0.10.5 即可切换到node v0.10.5。
进入命令行模式: 直接输入node回车即可。
运行文件:输入node 文件名即可。

在node.js中运行和组织coffeescript
利用coffeescript的监控工具实时把CS代码转化为JS代码,在node.js中只用JS代码(因为要与其他第三方库合作以及模块之间调用的问题)


node.js的编程要领

异步式IO和事件驱动。
传统服务器端的问题:阻塞(blocking)
比如,在整个数据查询的过程中,当前程序进程往往只是在等待结果的返回,这就造成了进程的阻塞。对于高并 发,I/O密集行的网络应用中,一方面进程很长时间处于等待状态,另一方面为了应付新的请求不断的增加新的进程。这样的浪费会导致系统支持的用户数远远小于后端数据服务能够支撑的,成为了系统的瓶颈。而且这样的系统也特别容易被慢链接攻击(客户端故意不接收或减缓接收数据,加长进程等待时间)。
Node.js 最大的特点就是异步式 I/O(或者非阻塞 I/O)与事件紧密结合的编程模式。为了解决阻塞问题,Node.js引入事件处理机制解决这个问题。在查询请求发起之前注册数据加载事件的响应函数,请求发出之后立即将进程交出,而当数据返回后再触发这个事件并在预定好的事件响应函数中继续处理数据。 我们看到若按照这个思路解决阻塞问题,首先我们要提供一套高效的异步事件调度机制。而主要用于处理浏览器端的各种交互事件的JavaScript,相对于其他语言,至少有两个关键点特别适合完成这个任务:一是它是函数式语言,这个特性使得为事件指定回调函数变得很容易。特别是JavaScript还支持匿名函数。二是它支持闭包,可在函数运行时保持上下文状态。
实际上node.js的各种API都会发出各种事件,使我们指定的回调函数在接收到事件时会被自动调用。而闭包可以让回调函数在运行时能够访问到主函数定义时的所处作用域内的所有变量。
我们看到node.js通过JavaScript函数式语言特性、匿名函数支持和闭包很漂亮的解决了同步编程到异步编程转化过程中遇到的一系列最重要的问题。但node.js是否就是最好的?非也,node.js的主要问题是:(1)闭包保持的变量耗费内存;(2)异步编程在逻辑上不太好理解(这个问题后面还要说)。
异步编程模式与传统的同步式 I/O 线性的编程思路有很大的不同,因为控制流很大程度上要靠事件和回调函数来组织,一个逻辑要拆分为若干个单元。Node.js 的异步编程接口习惯是以函数的最后一个参数为回调函数,通常一个函数只有一个回调函数。回调函数是实际参数中第一个是 err,其余的参数是其他返回的内容。如果没有发生错误,err 的值会是 null 或undefined。如果有错误发生,err 通常是 Error 对象的实例。


事件驱动 events

events 是 Node.js 最重要的模块,没有“之一”,原因是 Node.js 本身架构就是事件式的,而它提供了唯一的接口,所以堪称 Node.js 事件编程的基石。events 模块不仅用于用户代码与 Node.js 下层事件循环的交互,还几乎被所有的模块依赖。
事件发射器
events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就是事件发射与事件监听器功能的封装。
EventEmitter 的每个事件由一个事件名和若干个参数组成,事件名是一个字符串,通常表达一定的语义。对于每个事件,EventEmitter 支持若干个事件监听器。当事件发射时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递。
让我们以下面的例子解释这个过程:
var events = require('events');
var emitter = new events.EventEmitter();
emitter.on('someEvent', function(arg1, arg2) {
console.log('listener1', arg1, arg2);
});
emitter.on('someEvent', function(arg1, arg2) {
console.log('listener2', arg1, arg2);
});
emitter.emit('someEvent', 'byvoid', 1991);
运行的结果是:
listener1 byvoid 1991
listener2 byvoid 1991
以上例子中, emitter 为事件 someEvent 注册了两个事件监听器,然后发射了
someEvent 事件。运行结果中可以看到两个事件监听器回调函数被先后调用。
这就是EventEmitter最简单的用法。接下来我们介绍一下EventEmitter常用的API。
 EventEmitter.on(event, listener) 为指定事件注册一个监听器,接受一个字
符串 event 和一个回调函数 listener。
 EventEmitter.emit(event, [arg1], [arg2], [...]) 发射 event 事件,传
递若干可选参数到事件监听器的参数表。
 EventEmitter.once(event, listener) 为指定事件注册一个单次监听器,即
监听器最多只会触发一次,触发后立刻解除该监听器。
 EventEmitter.removeListener(event, listener) 移除指定事件的某个监听
器,listener 必须是该事件已经注册过的监听器。
4.4 文件系统 fs
65
 EventEmitter.removeAllListeners([event]) 移除所有事件的所有监听器,
如果指定 event,则移除指定事件的所有监听器。
大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它。包括 fs、net、http 在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。
为什么要这样做呢?原因有两点。首先,具有某个实体功能的对象实现事件符合语义,事件的监听和发射应该是一个对象的方法。其次 JavaScript 的对象机制是基于原型的,支持部分多重继承,继承 EventEmitter 不会打乱对象原有的继承关系。
EventEmitter 定义了一个特殊的事件 error,它包含了“错误”的语义,我们在遇到异常的时候通常会发射 error 事件。当 error 被发射时,EventEmitter 规定如果没有响应的监听器,Node.js 会把它当作异常,退出程序并打印调用栈。
我们一般要为会发射 error事件的对象设置监听器,避免遇到错误后整个程序崩溃。

异步编程的问题

node.js的大量I/O操作是异步的,异步会带来一个大问题:第一个操作还没有执行完就先执行了第二个,如果第二个操作依赖于第一个操作的结果,或者在执行顺序上有严格的要求,那就会产生错误。比如还没有读取完文件内容就关闭了文件。
解决方案是要么采用同步版本(sync),要么采用函数嵌套的形式。
下面采用嵌套的方式打开一个文件,获取其文件描述符,读出其内容,再关闭之。
var fs = require('fs');


fs.open('file.txt', flages = 'r', function(err,fd) {
    if (err) throw error;
    else {
console.log("opened.");
var id = fd;
fs.readFile('file.txt', 'utf-8', function(err, data) {
   if (err) throw error;
   else {
console.log(data);
fs.close(id, function(err) {
           if (err) throw error;
           else {
       console.log("file closeed.");
                    }
                });
            }
});
    }
});
可见,fs.open中嵌套着fs.readFile,里面又嵌套着fs.close。这只是个三层的嵌套。
当操作数据库时,经常要执行更多的顺序操作:打开数据库-打开集合-查找记录-查找另一条记录-将两条记录的值进行某种运算-写入数据库-关闭数据库。这如果用嵌套的写法,会形成非常深的嵌套。如:
var p_client = new Db('integration_tests_20', new Server("127.0.0.1", 27017, {}), {'pk':CustomPKFactory});
p_client.open(function(err, p_client) {
  p_client.dropDatabase(function(err, done) {
    p_client.createCollection('test_custom_key', function(err, collection) {
      collection.insert({'a':1}, function(err, docs) {
        collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, function(err, cursor) {
          cursor.toArray(function(err, items) {
            test.assertEquals(1, items.length);
            p_client.close();
          });
        });
      });
    });
  });
});
除了深层嵌套不直观外,这也不利于单元测试。还有循环陷阱的问题,即循环还没有执行完,就跳到后面的代码去执行了。这个问题可以用加计数器来解决,如:
var fs = require('fs');


fs.readdir(".", function (err, files) {
  var count = files.length,
      results = {};
  files.forEach(function (filename) {
    fs.readFile(filename, function (data) {
      results[filename] = data;
      count--;
      if (count <= 0) {
        // Do something once we know all the files are read.
      }
    });
  });
});
利用计数器count确保所有的文件都打开了才做something else。
但更严重的问题是在异步编程下,传统的顺序执行逻辑被打散,程序的逻辑关系不太容易被看清。是否考虑使用Jscex这类类库?参见http://www.infoq.com/cn/articles/jscex-javascript-asynchronous-programming
总结的技巧包括:
在异步编程中,需要把依赖于异步函数(需要其执行结果或者达到某种状态)的代码放在对应的回调函数中。
异步函数后面的代码会立即执行,所以在编程时需要通盘考虑,以免出现意外之外的运行结果。
并发运行的相同异步函数如果协作完成任务,需要添加代码判断执行状态,并且需要把所有异步函数完成后执行的代码放在判断条件的语句块里。
对于异步函数的顺序循环处理(目的是代码复用)可以通过定时器机制或者事件回调函数等方法来实现,但不能采用传统的循环语句模式。
“函数套函数”(通常是异步函数)的方式需要开发人员对代码结构有清晰的理解,以免造成代码编写错误,如在内部异步函数中试图影响外部函数的执行等问题。


node.js的API

最重要的就是学习API。在node.js官网上列举了如下API:
Assertion Testing   用于单元测试
Buffer              用于操作二进制流数据的缓存
C/C++ Addons        C/C++扩展
Child Processes     创建子进程
Cluster             创建node进程簇(实现类似多进程的功能)
Crypto              用于创建证书(用于安全连接/加解密等)
Debugger            node的debug工具
DNS                 DNS解析工具
Domain              将不同的IO操作打包成一组
Events              事件工具
File System         对文件系统的操作
Globals             全局对象
HTTP                高效的web服务器和客户端,提供HTTP服务
HTTPS               安全HTTPS 
Modules             模块解决方案
Net                 异步的操作网络端口的方案
OS                  提供操作系统信息
Path                操作和转换文件路径
Process             进程接口,可控制进程和了解进程的各种状况
Punycode            用于处理punycode编码。
Query Strings       用于操作字符串查询
Readline            用于逐行读取流中的文本
REPL                可交互式运行javascript的工具(常用于调试)
STDIO               标准输出入(如打印到控制台的console.log语句等)
Stream              用于操作流
String Decoder      将buffer中的内容解码为string
Timers              定时器功能
TLS/SSL             使用openSSL提供传输层的安全
TTY                 控制台操作
UDP/Datagram        用于操作UDP传输(使用datagram socket)
URL                 解析URL
Utilities           各种工具集
VM                  用于编译和运行javascript
ZLIB                用于压缩和解压缩文件(Gzip/Gunzip格式)


Global 全局对象

JavaScript 中有一个特殊的对象,称为全局对象(Global Object)
,它及其所有属性都可以在程序的任何地方访问,即全局变量。在浏览器 JavaScript 中,通常 window 是全局对象,
而 Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global对象的属性。
我们在 Node.js 中能够直接访问到对象通常都是 global 的属性, 如console、process、require等。


console 打印接口

最常用的输出信息的方法。
console.log 同 console.info   向标准输出(stdout)输出信息
可以使用console.log('count: %d', count);这样的形式输出变量。

console.error 同 console.warn 向标准错误(stderr)输出信息

console.time(label)  和 console.timeEnd(label)
对一个事件启动计时器,如下面的例子:
console.time('100-elements');
for (var i = 0; i < 100; i++) {
  console.log('count:%d', i);
}
console.timeEnd('100-elements');
对一个打印事件启动计时器,最后统计出打印100次的事件是5ms。


OS 操作系统接口

提供了访问操作系统的基本功能函数。
os.platform()  返回操作系统平台的名字(如Linux, Windows)
os.type() 返回操作系统的名字
os.hostname() 返回主机名
os.arch() 返回体系架构(如ia32)
os.release() 返回操作系统内核版本号(如2.6.32)
os.cpus() 返回每核CPU的参数(型号/主频/用于基础进程的毫秒数等)
os.totalmem() 返回总内存数
os.freemem() 返回自由内存数
os.networkInterfaces() 返回网络接口(网卡的名称/地址/IP等)
os.EOL 返回系统换行符(如 '\n')
os.tmpdir()返回系统临时文件的文件夹
os.endianness() 返回系统大小尾特征(如'LE')


Porcess 进程接口

process.pid  返回进程的ID

process.version 返回当前node的版本

process.versions 返回当前node及其各个依赖项的版本
{ http_parser: '1.0',
  node: '0.10.4',
  v8: '3.14.5.8',
  ares: '1.9.0-DEV',
  uv: '0.10.3',
  zlib: '1.2.3',
  modules: '11',
  openssl: '1.0.1e' }


process.execPath 返回node.js的执行文件的路径
如:/usr/local/bin/node


process.cwd() 返回当前目录
如:/home/swedepan/workspace/nodetest


process.config 返回当前编译环境(即config.gypi设定的环境)


process.env 返回当前系统环境设置(很复杂)


process.memoryUsuage() 返回node对于内存的使用
如: rss: 4935680,
  heapTotal: 1826816,
  heapUsed: 650472 }
heapTotal 和 heapUsed 代表 V8 的内存使用。


process.setuid() 和process.getuid() 设置和返回进程的用户ID(只对POSIX系统有效)
如:
if (process.getuid && process.setuid) {
  console.log('Current uid: ' + process.getuid());
  try {
    process.setuid(501);
    console.log('New uid: ' + process.getuid());
  }
  catch (err) {
    console.log('Failed to set uid: ' + err);
  }
}


process.exit(code) 退出进程,返回code。
如:process.exit(1); 将退出进程后返回1.


process.kill(pid,[signal]) 杀死某进程,返回一个信号
如:
process.on('SIGHUP', function() {
  console.log('Got SIGHUP signal.');
});


setTimeout(function() {
  console.log('Exiting.');
  process.exit(0);
}, 100);


process.kill(process.pid, 'SIGHUP');


process.chdir(directory)切换当前目录到指定目录


process.stdin() 读取标准输入
process.stdout() 写入标准输出
如:
process.stdin.resume();  //必须现有这句开启stdin
process.stdin.setEncoding('utf8');  //适合输入中文
process.stdin.on('data', function(data) {
  process.stdout.write('read from console: ' + data.toString()); //把一切输入转化为字符串
});


process.nextTick(callback) 为事件循环设置一项任务,Node.js 会在
下次事件循环调响应时调用 callback。
初学者很可能不理解这个函数的作用,有什么任务不能在当下执行完,需要交给下次事件循环响应来做呢?我们讨论过,Node.js 适合 I/O 密集型的应用,
而不是计算密集型的应用,因为一个 Node.js 进程只有一个线程,因此在任何时刻都只有一个事件在执行。如果这个事件占用大量的 CPU 时间,执行事件循环中的下一个事件就需要等待很久,因此 Node.js 的一个编程原则就是尽量缩短每个事件的执行时间。process.nextTick() 提供了一个这样的
工具,可以把复杂的工作拆散,变成一个个较小的事件。
function doFirst(args, callback) {
somethingComplicated(args);
callback();
}
doSecond(function onEnd() {
compute();
});

我们假设 compute() 和 somethingComplicated() 是两个较为耗时的函数,以上
的程序在调用 doFirst() 时会先执行 somethingComplicated(),然后立即调用
回调函数,在 onEnd() 中又会执行 compute()。下面用 process.nextTick() 改写上
面的程序:
function doFirst(args, callback) {
somethingComplicated(args);
process.nextTick(callback);
}
doSecond(function onEnd() {
compute();
});
改写后的程序会把上面耗时的操作拆分为两个事件,减少每个事件的执行时间,提高事件响应速度。不要使用 setTimeout(fn,0)代替 process.nextTick(callback),
前者比后者效率要低得多。


File System 文件系统

fs 模块是文件操作的封装,它提供了文件的读取、写入、更名、删除、遍历目录、链接等 POSIX 文件系统操作。与其他模块不同的是,fs 模块中所有的操作都提供了异步的和同 步 的 两 个 版 本 , 例 如 读 取 文 件 内 容 的 函 数 有 异 步 的 fs.readFile() 和 同 步 的fs.readFileSync()。我们以几个函数为代表,介绍 fs 常用的功能,并列出 fs 所有函数的定义和功能。

读取文件 fs.read(fd,buffer,offset,length,position,[callback(err, bytesRead, buffer)]) 
        fs.readSync(fd, buffer, offset,length, position)

写入文件 fs.write(fd,buffer,offset,length,position, [callback(err, bytesWritten, buffer)])
        fs.writeSync(fd, buffer, offset, length, position)
                 
读取文件内容 fs.readFile(filename,[encoding],[callback(err, data)])
           fs.readFileSync(filename,[encoding])

写入文件内容 fs.writeFile(filename, [callback(err)]) 
           fs.writeFileSync(filename, data, [encoding])

删除文件 fs.unlink(path, [callback(err)]) 

创建目录 fs.mkdir(path, [mode], [callback(err)]) 
       fs.mkdirSync(path, [mode])

删除目录 fs.rmdir(path, [callback(err)]) 
        fs.rmdirSync(path)

读取目录 fs.readdir(path, [callback(err, files)]) 
        fs.readdirSync(path)

获取真实路径 fs.realpath(path, [callback(err,resolvedPath)]) 
           fs.realpathSync(path)
                   
更名 fs.rename(path1, path2, [callback(err)]) 
     fs.renameSync(path1, path2)

其他fs命令见node API


下面详解主要命令:
fs.readFile(filename,[encoding],[callback(err,data)]) 是最简单的读取文件的函数。
它接受一个必选参数 filename,表示要读取的文件名。第二个参数 encoding是可选的,表示文件的字符编码。callback 是回调函数,用于接收文件的内容。如果不指
定 encoding,则 callback 就是第二个参数。回调函数提供两个参数 err 和 data,err 表示有没有错误发生,data 是文件内容。如果指定了 encoding,data 是一个解析后的字符串,否则 data 将会是以 Buffer 形式表示的二进制数据。
例如以下程序,我们从 content.txt 中读取数据,但不指定编码:
var fs = require('fs');
fs.readFile('content.txt', function(err, data) {
if (err) {
console.error(err);
} else {
console.log(data);
}
});
假设 content.txt 中的内容是 UTF-8 编码的 Text 文本文件示例,运行结果如下:
<Buffer 54 65 78 74 20 e6 96 87 e6 9c ac e6 96 87 e4 bb b6 e7 a4 ba e4 be 8b>
这个程序以二进制的模式读取了文件的内容,data 的值是 Buffer 对象。如果我们给fs.readFile 的 encoding 指定编码:
var fs = require('fs');
fs.readFile('content.txt', 'utf-8', function(err, data) {
if (err) {
console.error(err);
} else {
console.log(data);
}
});
那么运行结果则是:
Text 文本文件示例
当读取文件出现错误时,err 将会是 Error 对象。如果 content.txt 不存在,运行前面
的代码则会出现以下结果:
{ [Error: ENOENT, no such file or directory 'content.txt'] errno: 34, code: 'ENOENT',
path: 'content.txt' }
强大的readFile功能可以打开文本文件、JSON文件、图片,但打开doc、pdf文件会乱码。
fs.readFileSync是打开文件的同步版本,其用法是fs.readFileSync(filename, [coding]。如果编码格式不指定,则以二进制模式打开。
注意,这就不用回调函数了。其值以函数返回值的形式返回。
result = fs.readFileSync("file.txt", "utf-8");
console.log(result);


写文件 fs.writeFile(filename, data, [options], callback)
其中,data是要写的数据(字符串),options是参数,包括encoding(默认是utf-8), mode(默认是0666),flag(默认是w)。例子:
var fs = require('fs');


fs.writeFile('file.txt', 'Hello Node', function (err) {
  if (err) throw err;
  console.log('It\'s saved!');
});


这样会把整个文件重写(全覆盖)。如果要追加写入,应用appendFile命令(\n表示换行):
fs.appendFile('file.txt', '\n data to append',  function (err) {
  if (err) throw err;
  console.log('It\'s saved!');
});


新建文件夹fs.mkdir(path, [mode], callback)
下面的例子在当前文件夹里新建了一个文件夹
var fs = require('fs');


fs.mkdir('./new', function(err) {
    if (err) console.error(err);
    console.log('It\'s made!');
});
如果把里面的fs.mkdir改成fs.rmdir就变成删除文件夹了。注意:如果文件夹里面有文件则无法删除。


读取文件夹 fs.readdir(path, callback)
这回调函数中有两个参数err和fils,files是一个数组,包含了文件夹下所有的文件名(除了.和..文件)。
var fs = require('fs');
fs.readdir('./', function(err, files) {
    if (err) {
     console.error(err);
    }
    else
     console.log(files);
});
这个例子返回了当前文件夹下的所有文件,连同子文件夹。


fs.open 打开文件
fs.open(path, flags, [mode], [callback(err, fd)])是 POSIX open 函数的
封装,与 C 语言标准库中的 fopen 函数类似。它接受两个必选参数,path 为文件的路径,
flags 可以是以下值。
r :以读取模式打开文件。
r+ :以读写模式打开文件。
w :以写入模式打开文件,如果文件不存在则创建。
w+ :以读写模式打开文件,如果文件不存在则创建。
a :以追加模式打开文件,如果文件不存在则创建。
a+ :以读取追加模式打开文件,如果文件不存在则创建。
mode 参数用于创建文件时给文件指定权限,默认是 0666(所有者:读写,同组人:读写,其他用户:读写)。
回调函数返回的是fd--文件描述符,内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。


文件关闭 fs.close(fd,callback) 注意:使用文件描述符来定位文件。


删除文件 fs.unlink(path, callback)
下例删除了hello.cc文件
var fs = require('fs');


fs.unlink('hello.cc', function(err) {
    if (err) console.error(err);
    console.log('It\'s removed!');
});


文件重命名 fs.rename(path1, path2, [callback])
var fs=require('fs');
fs.rename('D:/test.js','D:/test1.js',function(err){
    if(err){
       console.log(err);  
    }else{
       console.log('renamed complete');
     }
})


Path 路径模块



该模块包括了一些处理文件路径的功能,可以通过require('path')方法来使用它。该模块提供了如下的方法:
(返回当前路径要用process模块中的process.cwd())


path.normalize(p)
该方法用于标准化一个字符型的路径,请注意'..' 与 '.' 的使用。
当发现有多个斜杠(/)时,系统会将他们替换为一个斜杠;如果路径末尾中包含有一个斜杠,那么系统会保留这个斜杠。在Windows中,上述路径中的斜杠(/)要换成反斜杠(\)。


示例:


path.normalize('/foo/bar//baz/asdf/quux/..')
// returns
'/foo/bar/baz/asdf'


path.join([path1], [path2], [...])
该方法用于合并方法中的各参数并得到一个标准化合并的路径字符串。


示例:


node> require('path').join(
...   '/foo', 'bar', 'baz/asdf', 'quux', '..')
'/foo/bar/baz/asdf'


path.resolve([from ...], to)
将to参数解析为绝对路径。
如果参数 to当前不是绝对的,系统会将from 参数按从右到左的顺序依次前缀到to上,直到在from中找到一个绝对路径时停止。如果遍历所有from中的路径后,系统依然没有找到一个绝对路径,那么当前工作目录也会作为参数使用。最终得到的路径是标准化的字符串,并且标准化时系统会自动删除路径末尾的斜杠,但是如果获取的路径是解析到根目录的,那么系统将保留路径末尾的斜杠。
你也可以将这个方法理解为Shell中的一组cd命令。


path.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile')
就类似于:


cd foo/bar
cd /tmp/file/
cd ..
cd a/../subfile
pwd


该方法与cd命令的区别在于该方法中不同的路径不一定存在,而且这些路径也可能是文件。
示例:
path.resolve('/foo/bar', './baz')
// returns
'/foo/bar/baz'
 
path.resolve('/foo/bar', '/tmp/file/')
// returns
'/tmp/file'
 
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif')
// if currently in /home/myself/node, it returns
'/home/myself/node/wwwroot/static_files/gif/image.gif'
path.dirname(p)
该方法返回一个路径的目录名,类似于Unix中的dirname命令。


示例:
path.dirname('/foo/bar/baz/asdf/quux')
// returns
'/foo/bar/baz/asdf'
path.basename(p, [ext])
该方法返回一个路径中最低一级目录名,类似于Unix中的 basename命令。


示例:
path.basename('/foo/bar/baz/asdf/quux.html')
// returns
'quux.html'
 
path.basename('/foo/bar/baz/asdf/quux.html', '.html')
// returns
'quux'
path.extname(p)
该方法返回路径中的文件扩展名,即路径最低一级的目录中'.'字符后的任何字符串。如果路径最低一级的目录中'没有'.' 或者只有'.',那么该方法返回一个空字符串。

示例:
path.extname('index.html')
// returns
'.html'
 
path.extname('index')
// returns
''
path.exists(p, [callback])
该方法用于测试参数p中的路径是否存在。然后以true 或者 false的方式调用callback参数。示例:


path.exists('/etc/passwd', function (exists) {
  util.debug(exists ? "it's there" : "no passwd!");
});


readline 提供处理用户行级输入的交互式接口

需要先require('readline');
如:
var readline = require('readline');


var rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});


rl.question('What is your favorite food?', function(answer) {
  console.log('Oh, so your favorite food is ' + answer);


  rl.close();
});
将提示用户输入他们喜爱的食物,然后将其答案打印出来。
用readline可以实现按命令菜单控制程序部分执行的调试系统。如:
var readline = require('readline'),
    rl = readline.createInterface(process.stdin, process.stdout);


rl.setPrompt('test> ');
rl.prompt();


rl.on('line', function(line) {
  switch(line.trim()) {
    case 'help':
      console.log('open db : 1');
      console.log('save data : 2');
      console.log('close db : 3');
      break;
    case '1':
      console.log('Now you are openning a database.');
      break;
    case '2':
      console.log('Save some data...');
      break;
    case '3':
      console.log('Droping a database.');
      break;
    default:
      console.log('Say what? This command is not exist `' + line.trim() + '`');
      console.log('open db : 1');
      console.log('save data : 2');
      console.log('close db : 3');
      break;
  }
  rl.prompt();
})
rl.on('SIGINT', function() {
  rl.question('Are you sure you want to exit?', function(answer) {
    if (answer.match(/^y(es)?$/i)) rl.close();
    else rl.prompt();
  });
});


这个小程序可以打印出命令菜单,然后根据用户的选项执行某些命令。最后按^C退出。其中:
rl.setPrompt('test> ') 是设光标控制符显示的样式
rl.prompt();  进入读入等待状态,显示光标
rl.on('line', function(line) 基于用户的回车事件,执行line函数
rl.on('SIGINT', function() 基于用户的^C事件,执行回调函数
上面这个例子的CS版本
readline = require("readline")
rl = readline.createInterface(process.stdin, process.stdout)


rl.setPrompt("test> ")
rl.prompt()


rl.on('line', cb = (line) ->
  switch(line.trim()) 
    when "help" 
      console.log """
                  open db   : 1
                  save data : 2
                  close db  : 3
                  """
    when "1" 
      console.log("Now you are openning a database.")
      
    when "2" 
      console.log("Save some data...")
      
    when "3" 
      console.log("Droping a database.")
      
    else
      console.log("Say what? This command is not exist `" + line.trim() + "`")
      console.log """
                  open db   : 1
                  save data : 2
                  close db  : 3
                  """
  rl.prompt()
)


rl.on('SIGINT', cb = () ->
  rl.question("Are you sure you want to exit?", cb = (answer) ->
    if (answer.match(/^y(es)?$/i)) 
      rl.close()
    else 
      rl.prompt()
  )
)

REPL Node自带的控制台工具

可生成交互式运行-解释JS的窗口。还可集成在其他程序中使其带有控制台功能。
首先要require("repl")
下面的小程序实现了JS交互窗口功能
var repl = require("repl");
    
repl.start({
  prompt: "node via stdin> ",
  input: process.stdin,
  output: process.stdout
});
运行这个程序,在闪烁的node via stdin>后面输入任何JS命令,回车就可以看结果了。
更酷的是,还可以和net模块结合,实现通过TCP等端口通讯的远程交互系统。
官网的例子:
var net = require("net"),
    repl = require("repl");


connections = 0;


repl.start({
  prompt: "node via stdin> ",
  input: process.stdin,
  output: process.stdout
});


net.createServer(function (socket) {
  connections += 1;
  repl.start({
    prompt: "node via Unix socket> ",
    input: socket,
    output: socket
  }).on('exit', function() {
    socket.end();
  })
}).listen("/tmp/node-repl-sock");


net.createServer(function (socket) {


  connections += 1;
  repl.start({
    prompt: "node via TCP socket> ",
    input: socket,
    output: socket
  }).on('exit', function() {
    socket.end();
  });
}).listen(5001);
实现了一个通过socket端口和TCP端口(5001)远程通讯的例子。TELNET命令可用于远程连接。


timers  定时器

    Timers
        setTimeout(callback, delay, [arg], [...])
        clearTimeout(timeoutId)
        setInterval(callback, delay, [arg], [...])
        clearInterval(intervalId)


Timers
setTimeout(callback, delay, [arg], [...]) 
在delay毫秒后执行一次回调函数(callback)。返回一个定时器id(timeoutId),此定时器id可用于clearTimeout()。可以选择传递到回调函数。
重要提示:回调函数在delay毫秒内很可能不能调用。Node.js不保证回调函数激活的执行时间,此时也不能安排其他事情。回调函数将在靠近设置的延时时间调用。
例子:
function a () {                                          //这是一个打印函数
console.log("this is function a in executing");
};


console.log("start now!");
setTimeout(a, 1000) ;        //停了1000毫秒后,执行a函数。
有很多人习惯把要执行的函数写在里面:
setTimeout(function() {
  console.log('Foo');
}, 1000);
但我觉得这样不如分开写清晰。


clearTimeout(timeoutId) 
阻止停止目标timeout的执行。


setInterval(callback, delay, [arg], [...]) 
计划在重复执行回调函数,时间间隔为delay毫秒。返回一个clearInterval()可用的intervalId。可以选择设置回调函数的参数。
例如:
function a () {
console.log("this is function a in executing");
};


console.log("start now!");


setInterval(a, 1000);
这会每隔1秒执行一次a函数。


clearInterval(intervalId) 
停止setInterval()
可以把计数器和停止条件写在要重复执行的函数中间,来达到“执行了XX次后停止”的效果。如:


// Setting and clearing an interval after 5 times execution.


var counter = 0;


function a () {
console.log('counter=%d', counter);
counter++;
if (counter >= 5) {
    clearInterval(interval);
    };
};


var interval = setInterval(a, 1000);


Utility 工具集

需要先导入  var util = require('util');


格式化工具
util.format(format, [...])     前面是格式字符串,后面是要格式化的东西
格式字符串由如下占位符组成:
%s - 字符串.
%d - 数字 (整数或浮点数均可).
%j - JSON.
% - 百分号 ('%').
作用是使多个变量按格式字符串的样式排列起来。如下例:
var util = require('util');
var a = "temperature";
var b = 36
var result = util.format('%s:%d', a, b);
console.log(result)
结果输出temperature:36


类型判断工具
共有四个:
util.isArray(object)  判断是否数组
例子:
util.isArray([])
  // true
util.isArray(new Array)
  // true
util.isArray({})
  // false
 
util.isRegExp(object) 判断是否正则表达式
例子:
util.isRegExp(/some regexp/)
  // true
util.isRegExp(new RegExp('another regexp'))
  // true
util.isRegExp({})
  // false


util.isDate(object)  判断是否日期
util.isDate(new Date())
  // true
util.isDate(Date())
  // false (without 'new' returns a String)
util.isDate({})
  // false


util.isError(object) 判断是否是错误对象
util.isError(new Error())
  // true
util.isError(new TypeError())
  // true
util.isError({ name: 'Error', message: 'an error occurred' })
  // false


调试类工具
util.debug(string) 将字符串输出到标准错误
util.error([...])  将所有参数输出到标准错误
util.puts([...])   把所有参数分行打印到标准输出 
util.print([...])  把所有参数不分行打印到标准输出 
util.log(string)   打印字符串,前面加上时间戳
util.inspect(object,[showHidden],[depth],[colors]) 将对象转换为字符串返回.这个很有用!!!
属性:showHidden 是否显示隐藏内容(默认否),depth 递归深度(默认是2级,null就是无限级),colors 是否显示颜色(默认是否),customInspect是否支持自定义显示(这个要配套util.inspect.styles 和 util.inspect.colors 对象使用).

http

Node.js 标准库提供了 http协议操作模块,其中封装了一个高效的 HTTP 服务器和一个简易的HTTP 客户端。使用时需要先require('http')
http.Server 是一个基于事件的 HTTP 服务器(Web Server),它的核心由 Node.js 下层 C++部分实现,而接口由 JavaScript 封装,兼顾了高性能与简易性。http.request 则是一个HTTP 客户端工具,用于向 HTTP 服务器发起请求。
要想深入了解http模块的使用,需要了解HTTP协议。请参见相关文章。
重要的一点:http模块既可以搭建WEB远程服务器,也可以充当浏览器-本地资源交互的机制,实现webapp。


1.http.Server
http.Server 是 http 模块中的 HTTP 服务器对象。它提供了一套封装级别很低的 API,仅仅是流控制和简单的消息解析,所有的高层功能都要通过它的
接口来实现。
http.Server 是一个基于事件的 HTTP 服务器,所有的请求都被封装为独立的事件,开发者只需要对它的事件编写响应函数即可实现 HTTP 服务器的所有功能。它继承自
EventEmitter,提供了以下几个事件:
-request:当客户端请求到来时,该事件被触发,提供两个参数 req 和res,分别是http.ServerRequest 和 http.ServerResponse 的实例,表示请求和响应信息。
-connection :当 TCP 连接建立时,该事件被触发,提供一个参数 socket ,为net.Socket 的实例。 connection 事件的粒度要大于 request ,因为客户端在
Keep-Alive 模式下可能会在同一个连接内发送多次请求。
-close :当服务器关闭时,该事件被触发。注意不是在用户连接断开时。
除此之外还有 checkContinue、upgrade、clientError 事件,通常我们不需要关心,只有在实现复杂的 HTTP 服务器的时候才会用到。
一个标准例子:
var http = require('http');


var server = new http.Server();
server.on('request', function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Node.js</h1>');
res.end('<p>Hello World</p>');
});
server.listen(3000);
console.log("HTTP server is listening at port 3000.");


但是由于这种作业太常见了,node专门给了一个快捷方式:
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.");


看,这么简单就实现了一个WEB SERVER!


在上述代码中,回调函数 function(req, res)由request事件触发,有两个参数,其一是req(request),代表请求的内容。这个可以用浏览器在URL地址栏的?后面写上,配合url.parse来解析。一般都是标准请求,不用管。其二是res(response),代表返回的信息,决定了浏览器能看到什么。它有三个重要的成员函数,用于返回响应头、响应内容以及结束
请求。
(1)response.writeHead(statusCode, [headers]):向请求的客户端发送响应头。statusCode 是 HTTP 状态码,如 200 (请求成功)、404 (未找到)等。headers是一个类似关联数组的对象,表示响应头的每个属性。该函数在一个请求内最多只
能调用一次,如果不调用,则会自动生成一个响应头。
(2)response.write(data, [encoding]):向请求的客户端发送响应内容。data 是一个 Buffer 或字符串,表示要发送的内容。如果 data 是字符串,那么需要指定encoding 来说明它的编码方式,默认是 utf-8。在 response.end 调用之前,response.write 可以被多次调用。
(3)response.end([data], [encoding]):结束响应,告知客户端所有发送已经完成。当所有要返回的内容发送完毕的时候,该函数 必须 被调用一次。它接受两个可
选参数,意义和 response.write 相同。如果不调用该函数,客户端将永远处于等待状态。


2. HTTP 客户端
http 模块提供了两个函数 http.request 和 http.get,功能是作为客户端向 HTTP服务器发起请求。可以作为文本型的浏览器。
其中http.request是标准写法:
var http = require('http');


var options = {                //请求的参数
host: 'localhost',
port: 3000,
path: '/',
method: 'GET'
};


var req = http.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (data) { //接到数据后显示出来
console.log(data);
});
});
req.end();
上述例子请求localhost:3000/ 下的全部内容,并在接到后在客户端显示出来。
var http = require('http');


http.get({host: 'localhost'}, function(res) {
res.setEncoding('utf8');
res.on('data', function (data) {
console.log(data);
});
});


 http 模块还提供了一个更加简便的方法http.get(options, callback),它是 http.request 的简化版,
唯一的区别在于http.get默认处理GET请求:自动将请求方法设为了 GET 请求,同时不需要手动调用 req.end()。
在实践中常用的是http.Server。客户端一般由浏览器来充当了。


真正的WEB SERVER需要建立路由,返回不同的页面文件,解析各种后缀的文件,定义各种错误信息和参数,是比较复杂的。所以要依赖框架。
下面的例子是一个简单的WEB SERVER,它在本地8888端口定义了一个http服务器,浏览器一旦访问,就返回/WebRoot/jstest.html文件。
//------------------------------------------------ 


//server.js 


// 一个演示Web服务器 
//------------------------------------------------ 

//请求模块 


var libHttp = require('http'); //HTTP协议模块 
var libUrl=require('url'); //URL解析模块 
var libFs = require("fs"); //文件系统模块 
var libPath = require("path"); //路径解析模块 


//依据路径获取返回内容类型字符串,用于http返回头 

var funGetContentType=function(filePath){ 
var contentType=""; 


//使用路径解析模块获取文件扩展名 

var ext=libPath.extname(filePath); 
switch(ext){ 
    case ".html": 
        contentType= "text/html"; 
        break; 
    case ".js": 
contentType="text/javascript"; 
break; 
case ".css": 
contentType="text/css"; 
break; 
    case ".gif": 
        contentType="image/gif"; 
        break; 
    case ".jpg": 
        contentType ="image/jpeg"; 
        break; 
    case ".png":
        contentType="image/png"; 
        break; 
    case ".ico": 
        contentType="image/icon"; 
        break; 
    default: 
        contentType="application/octet-stream"; 
    } 


    return contentType; //返回内容类型字符串 



//Web服务器主函数,解析请求,返回Web内容 

var funWebSvr = function (req, res){ 
var reqUrl=req.url; //获取请求的url 


//向控制台输出请求的路径 


console.log(reqUrl); 


//使用url解析模块获取url中的路径名 


var pathName = libUrl.parse(reqUrl).pathname; 
if (libPath.extname(pathName)=="") { 
//如果路径没有扩展名 
    pathName+="/"; //指定访问目录 



if (pathName.charAt(pathName.length-1)=="/"){ 
//如果访问目录
    pathName+="index.html"; //指定为默认网页 



//使用路径解析模块,组装实际文件路径 (假定WebRoot是文件根目录)

var filePath = libPath.join("./WebRoot",pathName); 
//判断文件是否存在 
libPath.exists(filePath,function(exists){ 
    if(exists){//文件存在 
//在返回头中写入内容类型 
        res.writeHead(200, {"Content-Type": funGetContentType(filePath) }); 


//创建只读流用于返回,使用二进制流格式以适应图片

        var stream = libFs.createReadStream(filePath, "binary", {flags : "r", encoding : 'utf8'}); 

//指定如果流读取错误,返回404错误 
        stream.on("error", function() { 
            res.writeHead(404); 
            res.end("<h1>404 Read Error</h1>"); 
        }); 


//连接文件流和http返回流的管道,用于返回实际Web内容 
        stream.pipe(res); 
    } 


    else {                    //文件不存在,返回404错误 
            res.writeHead(404, {"Content-Type": "text/html"}); 
            res.end("<h1>404 Not Found</h1>"); 
        } 
    }); 



//创建一个http服务器 

var webSvr=libHttp.createServer(funWebSvr); 


//指定服务器错误事件响应 
webSvr.on("error", function(error) { 
    console.log(error); //在控制台中输出错误信息 
}); 

//开始侦听8888端口 
webSvr.listen(8888,function(){
//向控制台输出服务启动的信息 
    console.log('[WebSvr][Start] running at http://127.0.0.1:8888/'); 
});




模块化开发

开发一个具有一定规模的程序不可能只用一个文件,通常需要把各个功能拆分、封装,然后组合起来,模块正是为了实现这种方式而诞生的。Node.js 提供了模块(Module)和包(Package)机制。模块就是一个文件,可以使用require 函数来调用其他模块。包是实现了某个功能模块的集合,用于发布和维护(可以作为一个文件夹)。
Node.js 的模块和包机制的实现参照了 CommonJS 的标准,但并未完全遵循。

1.模块

模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个Node.js 文件就是一个模块,这个文件可能是 JavaScript 代码、JSON 或者编译过的 C/C++ 扩展。Node.js的API都是核心模块,如我们曾经用到了 var http = require('http'),其中 http是 Node.js 的一个核心模块,其内部是用 C++ 实现的,外部用 JavaScript 封装。我们通过require 函数获取了这个模块,然后才能使用其中的对象。
除了核心模块之外,我们可以自己编写外部模块。
1.1创建模块
在 Node.js 中,创建一个模块非常简单,因为一个文件就是一个模块,我们要关注的问题仅仅在于如何在其他文件中获取这个模块。Node.js 提供了 exports 和 require 两个对象,其中 exports 用于公开模块的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。


让我们以一个例子来了解。
//module.js
//使用export暴露需要供其他模块调用的方法


var name


exports.setName = function (theName){
    name = theName;
};


exports.sayHello = function() {
console.log ("hello" + name);
};


//getmodule.js


var myModule = require ('./module')  //与模块放在同一文件夹下。如果在一个单独的文件夹下则应写为require ('./module/module')


myModule.setName("Pan");
myModule.sayHello();


模块的导入只支持js文件,所以CS写的源文件要编译为JS文件后再测试。但问题是当使用require语句时,只能require JS文件(因为node.js 10.6起就取消了requre.extention功能),所以,CS只适合写单个文件/测试单个文件,当连接各个模块时,就要先编译为JS文件,再测试了。
require不会重复加载模块,无论调用require多少次,获得的都是同一模块。


对象的输出
如果不是想仅仅输出某个方法(函数),而是要输出整个对象(函数),可以用module.export = 对象名 的方法。如:
//module.js


function Hello() {
  var name


  this.setName = function (theName){    //要使用this
    name = theName;
  };


  this.sayHello = function() {
console.log ("hello" + name);
  };
};


module.exports = Hello;


//getmodule.js


var Hello = require ('./module')


var hello = new Hello();    //必须将对象实例化


hello.setName("Pan");
hello.sayHello();


由于require只能引进整个对象,然后引用它的方法,所以要引进多个对象,就不能用module.exports = Hello这样的写法输出整个模块,而要用exports.Hello = Hello;这样的方法输出一个对象。
下面的例子输出了两个对象,并在另外一个文件中引用。
//module.js
function Hello() {
  var name


  this.setName = function (theName){    //要使用this
    name = theName;
  };


  this.sayHello = function() {
console.log ("hello " + name);
  };
};


exports.Hello = Hello;


function Hello2() {
  var name


  this.setName = function (theName){    //要使用this
    name = theName;
  };


  this.sayHello = function() {
console.log ("hello world " + name);
  };
};


//getmodule.js
var Hello = require ('./module').Hello
var Hello2 = require ('./module').Hello2


var hello = new Hello();    //必须将对象实例化


hello.setName("Pan");
hello.sayHello();


var hello2 = new Hello2();    //必须将对象实例化


hello2.setName("Wang");
hello2.sayHello();
exports.Hello2 = Hello2;


2.包

包是在模块基础上更深一步的抽象,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 规范。
Node.js 的包是一个目录,其中包含一个 JSON 格式的包说明文件 package.json。严格符合 CommonJS 规范的包应该具备以下特征:
 package.json 必须在包的顶层目录下;
 二进制文件应该在 bin 目录下;
 JavaScript 代码应该在 lib 目录下;
 文档应该在 doc 目录下;
 单元测试应该在 test 目录下。
Node.js 对包的要求并没有这么严格,只要顶层目录下有 package.json,并符合一些规范
即可。当然为了提高兼容性,我们还是建议你在制作包的时候,严格遵守 CommonJS 规范。
最简单的包,就是一个作为文件夹的模块。下面我们来看一个例子,建立一个叫
做 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 作为包的接口。
package.json 是 CommonJS 规定的用来描述包的文件,完全符合规范的 package.json 文件应该含有以下字段。
 name:包的名称,必须是唯一的,由小写英文字母、数字和下划线组成,不能包含空格。
 description:包的简要说明。
 version:符合语义化版本识别 规范的版本字符串。
 keywords:关键字数组,通常用于搜索。
 maintainers:维护者数组,每个元素要包含 name、email (可选) web (可选)字段。
 contributors:贡献者数组,格式与maintainers相同。包的作者应该是贡献者
数组的第一个元素。
 bugs:提交bug的地址,可以是网址或者电子邮件地址。
 licenses:许可证数组,每个元素要包含 type (许可证的名称)和 url (链接到许可证文本的地址)字段。
 repositories:仓库托管地址数组,每个元素要包含 type(仓库的类型, git )
 dependencies:包的依赖,一个关联数组,由包名称和版本号组成。
下面是一个完全符合 CommonJS 规范的 package.json 示例:


{
"name": "mypackage",
"description": "Sample package for CommonJS. This package demonstrates the required
elements of a CommonJS package.",
"version": "0.7.0",
"keywords": [
"package",
"example"
],
"maintainers": [
{
"name": "Bill Smith",
"email": "bills@example.com",
}
],
"contributors": [
{
"name": "BYVoid",
"web": "http://www.byvoid.com/"
}
],
"bugs": {
"mail": "dev@example.com",
"web": "http://www.example.com/bugs"
},
"licenses": [
{
"type": "GPLv2",
"url": "http://www.example.org/licenses/gpl.html"
}
],
"repositories": [
{
"type": "git",
"url": "http://github.com/BYVoid/mypackage.git"
}
],
"dependencies": {
"webkit": "1.2",
"ssl": {
"gnutls": ["1.0", "2.0"],
"openssl": "0.9.8"
}
}
}



3.npm 包管理器

Node.js包管理器,即npm是 Node.js 官方提供的包管理工具,它已经成了 Node.js 包的
标准发布平台,用于 Node.js 包的发布、传播、依赖控制。npm 提供了命令行工具,使你可
以方便地下载、安装、升级、删除包,也可以让你作为开发者发布并维护包。
3.1. 获取一个包
使用 npm 安装包的命令格式为:
npm [install/i] [package_name]
例如你要安装 express,可以在命令行运行:
$ npm install express
或者:
$ npm i express
随后你会看到以下安装信息:
npm http GET https://registry.npmjs.org/express
......
此时 express 就安装成功了,并且放置在当前目录的 node_modules 子目录下。npm 在获取 express 的时候还将自动解析其依赖,并获取 express 依赖的 mime、mkdirp、qs 和 connect。
3.2. 本地模式和全局模式
npm在默认情况下会从http://npmjs.org搜索或下载包,将包安装到当前目录的node_modules。在使用 npm 安装包的时候,
有两种模式:
本地模式和全局模式。
默认情况下我们使用 npm即把包安装到当前目录的 node_modules 子目录下。
Node.jsinstall命令就是采用本地模式,的 require 在加载模块时会尝试搜寻 node_modules 子目录,因此使用 npm 本地模式安装的包可以直接被引用。
npm 还有另一种不同的安装模式被成为全局模式,使用方法为:
npm [install/i] -g [package_name]
与本地模式的不同之处就在于多了一个参数 -g。
我们在安装coffeescript中使用了 npm install -g coffee-script 命令,就是以全局模式安装coffee-script。
为什么要使用全局模式呢?是因为本地模式不会注册 PATH 环境变量。举例说明,我们安装
coffeescript是为了在命令行中运行它,这时就需要在 PATH环境变量中注册 coffeescript。npm 本地模式仅仅是把包安装到 node_modules 子目录下,其中
的 bin 目录没有包含在 PATH 环境变量中,不能直接在命令行中调用。而当我们使用全局模式安装时,npm 会将包安装到系统目录,
譬如 /usr/local/lib/node_modules/,同时 package.json 文件中 bin 字段包含的文件会被链接到 /usr/local/bin/。/usr/local/bin/ 是在PATH 环境变量中默认定义的,因此就可以直接在命令行中运行coffescript test.cs命令了。
创建全局链接
npm 提供了一个有趣的命令 npm link,它的功能是在本地包和全局包之间创建符号链接。我们说过使用全局模式安装的包不能直接通过 require 使用,但通过 npm link命令可以打破这一限制。举个例子,我们已经通过 npm install -g express 安装了 express, 这时在工程的目录下运行命令:
$ npm link express
./node_modules/express -> /usr/local/lib/node_modules/express我们可以在 node_modules 子目录中发现一个指向安装到全局的包的符号链接。通过这
种方法,我们就可以把全局包当本地包来使用了。npm link 命令不支持 Windows。
除了将全局的包链接到本地以外,使用 npm link命令还可以将本地的包链接到全局。使用方法是在包目录( package.json 所在目录)中运行 npm link 命令。如果我们要开发一个包,利用这种方法可以非常方便地在不同的工程间进行测试。
3.3包的发布
npm 可以非常方便地发布一个包,比 pip、gem、pear 要简单得多。在发布之前,首先需要让我们的包符合 npm 的规范,npm 有一套以 CommonJS 为基础包规范,
通过使用 npm init 可以根据交互式问答产生一个符合标准的 package.json,例如创建一个名为 project 的目录,然后在这个
目录中运行npm init:
$ npm init
Package name: mymodule
Description: A module for xxx.
Package version: (0.0.0) 0.0.1
Project homepage: (none) http://www.myspace.com/
......
这样就在 project 目录中生成一个符合 npm 规范的 package.json 文件。创建一个index.js 作为包的接口,一个简单的包就制作完成了。我们可以把它发布node.js的全球包管理中心npm上(https://npmjs.org/)。发布很简单,在 package.json 所在目录下运行 npm publish,稍等片刻就可以完成发布了。
打开浏览器,访问 http://search.npmjs.org/ 就可以找到自己刚刚发布的包了。现在我们可以在世界的任意一台计算机上使用 npm install mymodule 命令来安装它。
如果你的包将来有更新,只需要在 package.json 文件中修改 version 字段,然后重新使用 npm publish 命令就行了。如果你对已发布的包不满意,可以使用 npm unpublish 命令来取消发布。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值