小试Node 核心模块

Node核心模块提供了很多方便的系统级的API,如文件、数据流、网络、进程等。Node 的特点是采用事件驱动、非阻塞/异步IO模型,所以它提供的大部分API都是基于事件模型、异步的编程风格。下面让我们来深入了解Node的核心模块,熟悉Node的异步编程风格。

4.1 全局变量相关模块

本小节介绍的这些对象在全局范围内均可使用,我们可以在任何位置访问这些对象。

4.1.1 Global Objects

global {Object}global(全局)对象可以说是ECMAScript中最特别的一个对象,这是一个看不见摸不着仿佛不存在但确又存在的对象。不属于任何其他对象的属性和方法,其实都是它的属性和方法。换句话说,所有在全局作域中定义的属性和方法,都是global对象的属性。ECMAScript对于如何访问global对象并没有给出标准定义,但是浏览器都不约而同是将这个全局对象作为window对象的一部分加以实现的。在浏览器中顶级作用域就是全局作用域,在全局作用域下通过var something即定义了一个全局变量,也可以说window多了一个something的属性。而Node中并不如此,顶级作用域并非是全局作用域,在Node的一个模块中(即一个文件中)通过var something 定义的变量仅仅作用于该模块。process {Object}进程对象,在4.1.5中会详细描述。console {Object}标准输出对象,在4.1.2中会详细描述。Buffer {Object}缓冲区对象,在4.1.6中会详细描述。__filename {String}当前正在被执行的脚本的文件名。这是一个完整的绝对路径。dirname {String}当前正在被执行的脚本的目录名。module {Object}指向当前模块对象的引用。require() {Function}加载模块的方法。require.resolve() {Function}使用内部方法require的机制查找一个模块的位置,而不是加载模块。如果查找模块成功,会返回filename,否则抛出一个找不到模块的错误。require.cache {Object}模块缓存对象。模块被首次加载会以键值对的形式缓存在此对象中,键为__filename,值为module。如果模块缓存对象中的某一个键值被删除,再次加载此模块时会被重新加载。require.paths {Array}查找模块搜索路径数组。我们可以通过修改该数组自定义搜索路径。这个变量在0.5.x版本被移除。exports {Object}模块导出对象,在4.1.4中会详细描述。exports是module.exports引用的拷贝,指向同一对象。module、require、exports、__filename和__dirname实际上并不真正是全局的,确切的说对于每个模块是局部的。setTimeout(cb, ms) {Function}clearTimeout(t) {Function}setInterval(cb, ms) {Function}clearInterval(t) {Function}定时器方法,在4.1.3中会详细描述。

4.1.2 STDIO

console {Object}大部分浏览器都提供了标准输出对象提供打印输出功能。console这个对象与浏览器中的标准输出对象相似,也提供打印输出的功能。console.log() {Function}console.info() {Function}console.warn() {Function}console.error() {Function}输出一行标准信息或标准错误到控制台。这些方法可以接收多个参数。例如:var count = 1;console.log('count: %d', count);//count: 1如果方法检查到第一个参数没有格式化元素,那么会对每一个参赛调用util.inspect方法并打印输出其返回值。util.inspect方法在4.5.1中会详细描述。console.dir(obj) {Function}调用util.inspect方法并打印输出返回值。console.time(label) {Function}console.timeEnd(label) {Function}统计一段程序运行所需时间的方法。例如:console.time('100-elements');for (var i = 0; i < 100; i++) {;}console.timeEnd('100-elements');// 100-elements: 0msconsole.trace(message) {Function}在当前位置打印堆栈轨迹的方法。此方法后的代码会继续正常运行。console.assert(expression, [message]) {Function}断言某个表达式是否为true的方法,同断言方法assert.ok()。

4.1.3 Timers

setTimeout(callback, delay, [arg], [...]) {Function}该方法用于在指定的delay毫秒后单次调用callback回调函数,并可以设定要传递给回调函数的参数,返回值timeoutId可用于clearTimeout()。clearTimeout(timeoutId) {Function}清楚指定timeoutId的定时器。setInterval(callback, delay, [arg], [...]) {Function}该方法用于在指定的delay毫秒后多次重复调用callback回调函数,并可以设定要传递给回调函数的参数,返回值intervalId可用于clearInterval ()。clearInterval(intervalId) {Function}清楚指定intervalId的定时器。代码示例:var timeoutId = setTimeout(function(o, b){console.log("This is callback");//This is callbackconsole.log(o.a);//1console.log(b);//2}, 3000, {"a":1}, 2);

clearTimeout(timeoutId);

4.1.4 Modules

module.exports {Object} module.exports对象是用于向外导出方法或者变量以供外部使用。在初始时,module.exports对象就是一个空对象{},向外导出的方法和变量都将作为module.exports对象的属性。需要注意的是,module.exports需要直接快速的赋值,不能在任何回调函数中完成这个操作。以下这样的使用,会产生不期望的效果:x.js:setTimeout(function() {module.exports = { a: "hello" };}, 0);y.js:var x = require('./x');console.log(x.a);//undefined我们也可以使用exports对象来完成模块导出操作。exports是module.exports引用的拷贝,指向同一对象。在使用时,我们需要避免这样的错误:a.js:module.exports = {a: 1};exports.b = 2;b.js:var a = require('./a');console.log(a);//{ a: 1 }这里输出的并不是预期的{ a: 1, b: 2 }。仔细分析一下就可以明白产生这样不是预期结果的原因:module.exports和exports是引用到的同一个对象,module.exports = {}这样的赋值操作修改了module.exports的引用,但是exports的引用并没有改变,调用require()方法的时候所引用的是module.exports 对象,自然exports变量的引用就会被忽略掉了。这种问题的解决方法也很简单,一种方法是同时重写两者的引用,一种是从不重写二者的引用。exports = module.exports = { a: 1, b: 2 };

exports.a = 1;exports.b = 2;module.require(id) {Function}加载指定模块标识符id的方法。module.id {String}模块的标识符。一般为一个完整的绝对路径。module.filename {String}模块文件的文件名。这是一个完整的绝对路径。module.loaded {Boolean}模块是否加载完成。module.parent {Object}加载该模块的父模块对象。module.children {Array}模块加载的子模块对象数组。require.main {Object}主模块对象。我们可以使用require.main === module来判断是否是主模块。

4.1.5 Process

process {Object}进程对象。它是一个EventEmitter的实例。Event: 'exit'当进程退出时,此事件被触发。这是一个检查模块状态的好时机(比较单元测试)。由于在exit事件触发回调函数完成后主事件循环将终止,所以计时器不会生效。监听exit事件的示例,onexit.js:process.on('exit', function () {process.nextTick(function () {console.log('This will not run');});console.log('About to exit.');});Event: 'uncaughtException'当发生一个未处理的异常时,此事件会被触发。如果该异常有这样一个监听器,那么不执行默认行为(即打印错误堆栈信息并结束应用程序的执行)。监听uncaughtException事件的示例,uncaughtException.js:process.on('uncaughtException', function (err) {console.log('Caught exception: ' + err);});

setTimeout(function () {console.log('This will still run.');}, 500);

// Intentionally cause an exception, but don't catch it.nonexistentFunc();console.log('This will not run.');注意,uncaughtException事件是一种非常粗糙的异常处理机制。我们可以在程序中使用try/catch来更好的控制程序流程。特别是对于在服务器端程序,因为它要持续运行,所以uncaughtException事件是个很有用的安全机制。Signal Events当进程接收到信号时,此事件会被触发。具体信号名请查看sigaction(2)中的标准POSIX信号名称列表,比如SIGINT、SIGUSR1等。监听SIGINT信号的示例,SIGINT.js:// Start reading from stdin so we don't exit.process.stdin.resume();

process.on('SIGINT', function () {console.log('Got SIGINT. Press Control-D to exit.');});发送SIGINT信号最简单的方法是使用CTRL+C,大多数情况下这会终止应用程序的执行。process.stdout {Object}标准输出流对象。console.log方法的定义:console.log = function (d) {process.stdout.write(d + '\n');};process.stderr {Object}错误输出流对象。process.stdout 和process.stderr不像Node中的其他数据流,它们在写入时通常阻塞。他们在被指向到普通文件或者TTY文件描述符时是阻塞的;指向到数据通道时是非阻塞的。process.stdin {Object}标准输入流对象。默认情况下标准输入流会阻塞程序,要读取输入内容需要调用方法process.stdin.resume()。下面是打开标准输入并监听它的两个事件的示例,stdin.js:process.stdin.resume();process.stdin.setEncoding('utf8');

process.stdin.on('data', function (chunk) {process.stdout.write('data: ' + chunk);});

process.stdin.on('end', function () {process.stdout.write('end');});process.argv {Array}命令行参数数组。第一个参数是"node",第二个参数是JavaScript文件的绝对路径。接下来是附加的命令行参数。argv.js:// print process.argvprocess.argv.forEach(function (val, index, array) {console.log(index + ': ' + val);});上面的代码将产生如下输出:$ node process-2.js one two=three four0: node1: /Users/mjr/work/node/process-2.js2: one3: two=three4: fourprocess.execPath {String}进程可执行文件的绝对路径。process.cwd() {Function}返回进程的当前工作目录。process.chdir(directory) {Function}改变进程的当前工作目录,失败时抛出异常。chdir.jsconsole.log('Starting directory: ' + process.cwd());try {process.chdir('/tmp');console.log('New directory: ' + process.cwd());}catch (err) {console.log('chdir: ' + err);}process.env {Object}装有用户环境变量的对象。process.exit([code]) {Function}使用指定的退出代码进程退出进程。如果不指定参数,方法将使用表示成功的代码0。退出程序并返回错误状态的示例,exit.js:process.exit(1);执行node的shell将会得到返回值1。process.getgid() {Function}获取进程的群组标识(详见getgid(2))。这是一个数字的群组ID,不是群组名称。process.setgid(id) {Function}设置进程的群组标识(详见setgid(2))。参数可以是一个数字ID或者群组名字符串。如果指定了一个群组名,这个方法会阻塞等待将群组名解析为数字ID。gid.js:console.log('Current gid: ' + process.getgid());try {process.setgid(501);console.log('New gid: ' + process.getgid());}catch (err) {console.log('Failed to set gid: ' + err);}process.getuid() {Function}获取进程的用户ID(详见getuid(2))。这是一个数字用户ID,不是用户名。process.setuid(id) {Function}设置进程的用户ID(详见setuid(2))。参数可以是一个数字ID或者用户名字符串。如果指定了一个用户名,那么该方法会阻塞等待将用户名解析为数字ID。uid.js: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.version {String}一个编译内置的属性,用于显示Node的版本。version.js:console.log('Version: ' + process.version);process.versions {Object}用于显示Node依赖库的版本的对象。versions.js:console.log(process.versions);输出如下:{ node: '0.6.19',v8: '3.6.6.25',ares: '1.7.5-DEV',uv: '0.6',openssl: '0.9.8r' }process.installPrefix {String}一个编译内置的属性,用于显示Node的安装目录的前缀NODE_PREFIX。installPrefix.js:console.log('Prefix: ' + process.installPrefix);process.kill(pid, [signal]) {Function}向一个进程发送信号,参数pid为进程ID,参数signal是一个描述要发送信号的字符串,如“SIGINT”或者“SIGUSR1”。如果不指定,默认发送“SIGTERM”信号。更多信息请查看kill(2)。注意,虽然此方法名为process.kill,但是它仅仅用于发送信号,就像kill系统调用一样。发送的信号除了可以结束目标进程外,还可以完成其他的操作。发送信号的示例,kill.js:process.on('SIGHUP', function () {console.log('Got SIGHUP signal.');});

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

process.kill(process.pid, 'SIGHUP');process.pid {String}进程PID。process.title {String}设置、获取ps命令中显示的名称。process.arch {String}运行程序的处理器架构:“arm”、“ia32”、“x64”。process.platform {String}程序运行的平台,如“linux2”、“darwin”等。process.memoryUsage() {Function}返回一个描述Node进程内存使用情况的对象。memoryUsage.js:console.log(process.memoryUsage());输出如下:{ rss: 8552448, heapTotal: 2546784, heapUsed: 1234140 }heapTotal和heapUsed指V8内存使用情况。process.nextTick(callback) {Function}在事件循环的下一轮执行callback回调函数。这个方法不是setTimeout(fn, 0)的别名,它更加高效。nextTick.js:process.nextTick(function () {console.log('nextTick callback');});process.umask([mask]) {Function}设置或读取进程的文件创建模式掩码,子进程会从父进程继承这个掩码。如果使用此方法设置新的掩码,则它返回旧的掩码,否则返回当前掩码。umask.js:var oldmask, newmask = 0644;oldmask = process.umask(newmask);console.log('Changed umask from: ' + oldmask.toString(8) + ' to ' + newmask.toString(8));process.uptime() {Function}返回Node进程运行的时间,单位为秒。uptime.js:console.log(process.uptime());

4.1.6 Buffer

Buffer {Class}纯JavaScript语言对Unicode很友好,但是难以处理二进制数据。在处理TCP数据流和文件时不可避免地需要操作字节流。Node提供一些方法来创建、操作和接收二进制数据。原始的数据被存放在Buffer的实例中。Buffer类似于一个整数数组,不同之处在于它和V8内存堆之外分配的一段内存数据相对应。Buffer对象的大小不能动态调整。Buffer是全局的,不需要在使用前还需每次"require('buffer')"后才能够使用它,并有多种不同的方法实例化。Buffer和JavaScript中的String对象之间的转换需要指定编码方式。如下是Node支持的编码方式:'ascii' - 仅对应7位的ASCII数据。这种编码方式速度很快,它会删除字节的高位。'utf-8' - Unicode字符。许多网页和其他文档使用这种编码方式。'ucs2' - 2字节的,低字节序编码Unicode字符。它只能编码BMP(基本多语言面,U+0000-U+FFFF)字符。'base64' - base64字符串编码。'binary' - 一种只使用每个字符前8个字节将原始二进制数据编码进字符串的方式。这个方式已经废弃,应当尽量使用buffer对象。这个编码将在未来版本中移除。'hex' - 将一个字节编码为两个16进制字符。new Buffer(size) {ClassMethod}创建指定大小的Buffer对象。new Buffer(array) {ClassMethod}用数组新建Buffer对象。new Buffer(str, [encoding]) {ClassMethod}新建一个保存指定字符串的Buffer对象。字符编码默认'utf-8'。buf.write(string, [offset], [length], [encoding]) {Function}使用指定的编码方式将字符串从指定偏移量开始写入Buffer,然后返回实际写入的大小。编码默认是'utf-8',偏移量默认是0,length为写入的字节长度。如果Buffer空间不足,则只会写入部分字符串。这种方式不会出现一个字符被拆分成半个字符被写入。下面是将一个utf-8字符串写入buffer,然后打印出来的示例,write.js:buf = new Buffer(256);len = buf.write('\u00bd + \u00bc = \u00be', 0);console.log(len + " bytes: " + buf.toString('utf8', 0, len));//12 bytes: ½ + ¼ = ¾buf.toString([encoding], [start], [end]) {Function}使用指定的编码方式将Buffer从指定起始位置到指定结束位置解码为字符串并返回。编码默认是'utf-8',起始位置默认是0,结束位置默认为Buffer的长度。buf[index] {Byte}获取或设置位于索引字节的值。由于返回值为单个的字节,因此其范围应该在0x00到0xff(16进制)或者0到255(10进制)之间。下面是将一个ASCII字符串复制进Buffer,每次一个字节,index.js:str = "node.js";buf = new Buffer(str.length);for (var i = 0; i < str.length ; i++) {buf[i] = str.charCodeAt(i);}console.log(buf);Buffer.isBuffer(obj) {ClassMethod}验证传入的对象是否是Buffer的一个实例。Buffer.byteLength(string, [encoding]) {ClassMethod}返回字符串的实际字节数。这个方法和String.prototype.length不同,后者返回字符串的字符数。byteLength.js:str = '\u00bd + \u00bc = \u00be';

console.log(str + ": " + str.length + " characters, " +Buffer.byteLength(str, 'utf8') + " bytes");

// ½ + ¼ = ¾: 9 characters, 12 bytesbuf.length {Number}Buffer的大小(以字节为单位)。请注意,这个不是存放内容的大小,而是分配buffer对象的内存大小。这个大小不随Buffer中存放内容的多少而改变。length.js:buf = new Buffer(1234);

console.log(buf.length);// 1234buf.write("some string", "ascii", 0);console.log(buf.length);// 1234buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd]) {Function}在两个Buffer之间执行内存拷贝。targetStart和sourceStart默认为0,sourceEnd默认为buf.length。例如,新建连个buffer对象,然后将buf1中16至19字节拷贝到buf2中第8字节开始的空间中。copy.js:buf1 = new Buffer(26);buf2 = new Buffer(26);

for (var i = 0 ; i < 26 ; i++) {buf1[i] = i + 97; // 97 is ASCII abuf2[i] = 33; // ASCII !}

buf1.copy(buf2, 8, 16, 20);console.log(buf2.toString('ascii', 0, 25));// !!!!!!!!qrst!!!!!!!!!!!!!buf.slice([start], [end]) {Function}返回指定起始位置和结束位置的原Buffer引用的分片。当修改新Buffer时也将会被体现在原Buffer上,因为两个Buffer指向同一段内存地址。例如,使用字母表简历一个Buffer对象,并分片出一个新的Buffer,然后修改原始Buffer的一个字节。slice.js:var buf1 = new Buffer(26);

for (var i = 0 ; i < 26 ; i++) {buf1[i] = i + 97; // 97 is ASCII a}

var buf2 = buf1.slice(0, 3);console.log(buf2.toString('ascii', 0, buf2.length));// abcbuf1[0] = 33;// ASCII !console.log(buf2.toString('ascii', 0, buf2.length));// !bcbuf.fill(value, [offset], [end]) {Function}用指定的值填充Buffer。如果偏移量offset(默认0)和结束位置end(默认buf.length)都没有值,它将填充整个Buffer。fill.js:var b = new Buffer(50);b.fill("h");console.log(b);buf.inspect()返回Buffer的字符串表示,每个字节用十六进制表示。当调用console.dir的时候打印的就是这个方法返回的结果。当Buffer超长(默认为50)时,会用...省略超长的内容。buffer.INSPECT_MAX_BYTES {Number}这个数值会在调用buffer.inspect()方法时被用到,默认为50,我们可以直接对这个属性赋值来修改其值的大小。注意,这是buffer核心模块的一个属性,需要通过调用require('buffer')返回的对象来访问,它不是全局的Buffer或者Buffer实例的属性。SlowBuffer {Class}SlowBuffer 这个类主要是内部使用。JavaScript程序应该使用Buffer,避免使用SlowBuffer 。SlowBuffer 不是全局的,需要导入后才可以使用。为了避免频繁的给Buffer对象分配小内存块,对于创建小于8k的Buffer,其实是从一个池中分片得来的,只有大于8k的Buffer才会每次都新创建一个SlowBuffer实例。Buffer.poolSize {Number}Buffer类创建的池大小,大于此值则每次new一个SlowBuffer,否则从池中slice返回一个Buffer,如果池剩余空间不够,则新创建一个SlowBuffer做为池。我们可以直接对这个属性赋值来修改池的大小。除了上述方法外,Buffer模块还提供了许多读取和写入各种数值类型的方法,我们可以在Node的官方文档Buffer模块(http://www.nodejs.org/api/buffer.html)中查看到,这里就不再详细描述。

4.2 事件模块

4.2.1 Events

events.EventEmitter {Class}事件驱动是Node一个很重要的特点。Node中有很多对象都会触发事件,比如一个进程退出时会触发事件。所有能够触发事件的对象都是EventEmitter的实例。我们可以通过require("events")来访问这个模块,通过调用require('events').EventEmitter可以使用事件触发器类。通常,事件名称采用驼峰式命名法,不过目前没有对事件名称作任何限制,也就是说任何字符串都可以被用作事件名。Event: 'newListener'可以将函数注册给对象,使其在事件触发时执行,此类函数被称作监听器。当新的事件监听器被添加时,事件触发器都会触发'newListener'事件。Event: 'error'当事件触发器遇到错误时,会触发'error'事件。这个事件特殊的地方在于:如果没有监听器处理这个事件,它将会输出堆栈信息,并退出应用程序。emitter.addListener(event, listener) {Function}emitter.on(event, listener) {Function}这两个方法都可以将一个监听器添加到指定的事件监听器数组的末尾。server.on('connection', function (stream) {console.log('someone connected!');});emitter.once(event, listener) {Function}这个方法可以添加一次性的监听器。该监听器在事件触发第一次时执行,随后被移除。server.once('connection', function (stream) {console.log('Ah, we have our first user!');});emitter.removeListener(event, listener) {Function}将监听器从指定的事件的监听器数组中移除出去。非常值得注意的是:这个操作将会改变监听器数组的下标。var callback = function(stream) {console.log('someone connected!');};server.on('connection', callback);// ...server.removeListener('connection', callback);emitter.removeAllListeners([event]) {Function}将指定事件的所有监听器从监听器数组中移除。emitter.setMaxListeners(n) {Function}默认情况下,当事件触发器注册了超过10个以上的监听器时,系统会打印警告信息。这个默认配置将有助于我们查找内存泄漏问题。很显然并不是所有的事件触发器都需要进行10个监听器的限制,此方法允许我们手动设置该数量值。如果这个值被设置为0,意味着没有数量限制。emitter.listeners(event) {Function}返回指定事件的监听器数组。我们可以对该数组进行操作,比如删除监听器等。server.on('connection', function (stream) {console.log('someone connected!');});var listeners = server.listeners('connection');console.log(util.inspect(listeners);// [ [Function] ]emitter.emit(event, [arg1], [arg2], [...]) {Function}顺序执行监听器列表中的每个监听器函数并传递相应的参数。

4.3 文件和数据流相关模块

4.3.1 Stream

Stream(流)是一个抽象接口,Node中有很多实例对象实现了这个接口。例如,请求http服务器的request是一个流。所有的流都是EventEmitter的实例。流可以分为可读的、可写的或者既可读又可写。所以下面分两部分来介绍流,一部分为可读流,另一部分来可写流。Readable Stream(可读流)具有下述的事件、成员和方法。Event: 'data' function (data) { }'data'事件的回调函数参数默认情况下是一个Buffer对象。如果使用了setEncoding()则参数为一个字符串。Event: 'end' function () { }此事件在流中遇到EOF(TCP中为FIN)时被触发,表示流不会再有数据(不会触发'data事件')。如果流同时也是可写的,那它还可以继续写入。Event: 'error' function (exception) { }此事件在读取数据出错时被触发。Event: 'close' function () { }当底层的文件描述符被关闭时触发此事件,并不是所有的流都会触发这个事件。例如,一个http请求就不会触发'close'事件。Event: 'fd' function (fd) { }当流读取到文件描述符信息时触发此事件。一个文件数据流包含两部分信息:文件描述符信息和文件数据信息。此事件只支持Unix流,其他类型的流不会触发此事件。stream.readable {Boolean}这是一个布尔值,默认为true。当读取流遇到错误('error'事件)或者读到结尾('end'事件)或者调用destroy()方法后,该值会设置为false。stream.setEncoding(encoding) {Function}调用此方法后,'data'事件的回调函数参数会由默认的Buffer变为字符串。这个方法的参数编码类型科研室'utf-8'、'ascii'或'base64'。stream.pause() {Function}暂停'data'事件的触发。stream.resume() {Function}恢复被pause()方法暂停的'data'事件。stream.destroy() {Function}关闭底层的文件描述符。流将不会再触发任何事件。stream.pipe(destination, [options]) {Function}这是Stream.prototype(Stream原型对象)的一个方法,所有Stream对象都可以使用它。这个方法用于将这个可读流与流向目标连接起来,传入这个流中数据将会写入的流向目标中。通过在必要时暂停和恢复流,使来源流和目的流得以保持同步。以模拟Unix系统的cat命令为例:process.stdin.resume();process.stdin.pipe(process.stdout);默认情况下,当来源流的'end'事件出发时,目的流的end()方法会被调用,此时目的流将不再可写入。要在这种情况下为了保持目的流仍然可写入,可将options参数设置为{ end: false}。这使process.stdout保存打开状态,因此"Goodbye"可以在end事件发生后被写入。process.stdin.resume();

process.stdin.pipe(process.stdout, { end: false });

process.stdin.on("end", function() {process.stdout.write("Goodbye\n");});注意:如果来源流不支持pause()和resume()方法,pipe此方法将在来源流对象上增加这两个方法的简单定义,使其可以触发'pause'和'resume'事件。Writable Stream(可写流)具有下述的事件、成员和方法。Event: 'drain' function () { }当在write()方法被调用并返回false后触发此事件。此事件被触发说明缓冲区已空,再次写入是安全的。Event: 'error' function (exception) { }此事件在发生错误的时候被触发,回调函数接受一个异常参数exception。Event: 'close' function () { }底层文件描述符被关闭时触发此事件。Event: 'pipe' function (src) { }当此可写流作为参数传给一个可读流的pipe方法时触发此事件。stream.writable {Boolean}这是一个布尔值,默认为true。当读取流遇到错误('error'事件)或者end()/destroy()方法被调用后,该值会设置为false。stream.write(string, [encoding], [fd]) {Function}使用指定编码encoding将字符串string写入到流中。如果字符串被成功写入内核缓冲区,此方法返回true。如果内核缓冲区已满,此方法返回false,数据将在未来被写出。当内核缓冲区再次被清空后,'drain'事件将被触发。Encoding参数默认为'utf-8'。如果指定了可选参数fd,它将被作为一个文件描述符通过流传送。此功能仅被Unix流所支持,对于其他流此操作将被忽略而没有任何提示。当使用此方法传送一个文件描述符时,如果在流没有清空前关闭此文件描述符,将造成传送一个无效(已关闭)FD的风险。stream.write(buffer) {Function}与上面的write方法类似,写入一个Buffer对象。stream.end() {Function}使用EOF或FIN结束一个流的输出。stream.end(string, encoding) {Function}以指定的字符编码encoding写入一个字符串string,然后使用EOF或FIN结束流的输出。这对降低数据包传输量有所帮助。stream.end(buffer) {Function}与上面的end方法类似,写入一个Buffer对象,然后使用EOF或FIN结束流的输出。stream.destroy() {Function}关闭底层的文件描述符。流将不会再触发任何事件。stream.destroySoon() {Function}清空当前的写队列(写操作完成),然后关闭文件描述符。

4.3.2 Readline

Readline模块可以帮助我们一行一行地读取一个流(如STDIN)。我们可以调用require('readline')来使用此模块。注意:一旦我们调用这个模块,Node程序不会终止,直到我们关闭这个接口和STDIN流。如何正常终止我们的程序,请看下面的例子。createInterface.js:var rl = require('readline');

var i = rl.createInterface(process.stdin, process.stdout, null);i.question("What do you think of node.js?", function(answer) {// TODO: Log the answer in a databaseconsole.log("Thank you for your valuable feedback.");

// These two lines together allow the program to terminate. // Without them, it would run forever.i.close();process.stdin.destroy();});rl.createInterface(input, output, completer) {Function}创建一个读行接口,方法接收的参数是输入input与输出output两个流。参数completer是一个方法,用于自动完成。当接收到一个子串,它会返回[[substr1, substr2, ...], originalsubstring]。Completer方法也可以在异步模式下运行,这样它需要接收两个参数:function completer(linePartial, callback) { callback(null, [['123'], linePartial]); }createInterface这个方法常与process.stdin和 process.stdout并用,以接收用户输入。Interface {Class}用于stdin和stdout这两个流进行一行一行读取流操作的抽象接口类。rl.setPrompt(prompt, length) {Function}设置提示。比如当你在Node命令行模式(在终端上直接运行node命令不带任何参数即可进入命令行模式)上时,你会看到这样的提示'>'。rl.prompt() {Function}用当前的提示信息新起一行以供用户输入,并准备好从用户输入读取一行。rl.question(query, callback) {Function}先显示提示信息query,然后调用callback这个回调函数来响应用户的输入。rl.close() {Function}关闭TTY。rl.pause() {Function}暂停TTY。rl.resume() {Function}恢复TTY。rl.write() {Function}写入TTY。Event: 'line' function (line) {}当在流中接收到\n就会触发此事件。通常当用户敲打回车或者退出就会触发此事件。这是一个很好的钩子去监听用户的输入。例如这样监听每一行的输入:rl.on('line', function (cmd) {console.log('You just typed: '+cmd);});Event: 'close' function () {}当在流中接收到C(SIGINT)或D(EOT)就会触发此事件。这是一个探知用户已经完成使用应用程序的很好的方式。例如可以这样监听close事件,并随后退出程序:rl.on('close', function() {console.log('goodbye!');process.exit(0);});这里有一个如何融合使用前面介绍的事件和方法模拟出一个简单的命令行交互的例子,readline.js:var readline = require('readline'),rl = readline.createInterface(process.stdin, process.stdout),prefix = 'OHAI> ';

rl.on('line', function(line) {switch(line.trim()) {case 'hello':console.log('world!');break;default:console.log('Say what? I might have heard ' + line.trim() + '');break;}rl.setPrompt(prefix, prefix.length);rl.prompt();}).on('close', function() {console.log('Have a great day!');process.exit(0);});console.log(prefix + 'Good to see you. Try typing stuff.');rl.setPrompt(prefix, prefix.length);rl.prompt();

4.3.3 TTY

在Linux中,TTY也许是跟终端有关系的最为混乱的术语。TTY是TeleTYpe的一个老缩写。Teletypes,或者teletypewriters,原来指的是电传打字机,是通过串行线用打印机键盘通过阅读和发送信息的东西,和古老的电报机区别并不是很大。之后,当计算机只能以批处理方式运行时(当时穿孔卡片阅读器是唯一一种使程序载入运行的方式),电传打字机成为唯一能够被使用的“实时”输入/输出设备。最终,电传打字机被键盘和显示器终端所取代,但在终端或TTY接插的地方,操作系统仍然需要一个程序来监视串行端口。TTY也可以简单理解为一个程序监视物理的TTY/终端接口。我们可以调用require('tty')来使用此模块。tty.js:var tty = require('tty');process.stdin.resume();tty.setRawMode(true);process.stdin.on('keypress', function(char, key) {if (key && key.ctrl && key.name == 'c') {console.log('graceful exit');process.exit();}});tty.isatty(fd) {Function}这个方法返回一个布尔值。这取决于文件描述符fd是否和本终端有关联,有关联返回true,否则返回false。tty.setRawMode(mode) {Function}参数mode是一个布尔值,可以被设置为true或false。此方法可以改变当前进程的stdin(标准输入)为原始设备方式或默认方式。

4.3.4 Path

Path模块包含很多用于处理文件路径的小工具。几乎所有方法仅仅是执行字符串的转换,并没有去文件系统检查是否是有效的路径。Path.exists和path.existsSync这两个方法是例外,很容易发现这两个方法是在访问文件系统。我们可以调用require('path')来使用此模块。path.normalize(p)该方法用于标准化一个字符型的路径。请注意'..'和'.'的使用。当发现有多个斜杠(/)时,系统会将他们替换为一个斜杠;如果路径末尾中包含有一个斜杠,那么系统会保留这个斜杠。在Windows中,上述路径中的斜杠(/)要换成反斜杠(\)。normalize.js:var path = require('path');var string = '/foo/bar//baz/asdf/quux/..';string = path.normalize(string);console.log(string);// returns '/foo/bar/baz/asdf'path.join([path1], [path2], [...])该方法用于合并传入的参数得到一个标准化的路径字符串。join.js:var path = require('path');var string = path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');console.log(string);// returns '/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/barcd /tmp/file/cd ..cd a/../subfilepwd该方法与cd命令的区别在于该方法中不同的路径不一定存在,而且这些路径也可能是文件。resolve.js:var path = require('path');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.relative(from, to)解析参数,返回相对路径。有时我们有两个绝对路径,我们需要获得从一个到另一个的相对路径。这实际上相当于path.resolve的反向转换,就像这样:path.resolve(from, path.relative(from, to)) == path.resolve(to)relative.js:var path = require('path');path.relative('C:\orandea\test\aaa', 'C:\orandea\impl\bbb');// returns '..\..\impl\bbb'

path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb');// returns '../../impl/bbb'path.dirname(p)该方法返回一个路径的目录名,类似于Unix中的dirname命令。dirname.js:var path = require('path');path.dirname('/foo/bar/baz/asdf/quux')// returns '/foo/bar/baz/asdf'path.basename(p, [ext])该方法返回一个路径中最低一级目录名,类似于Unix中的 basename命令。basename.js:var path = require('path');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)该方法返回路径中的文件扩展名,即路径最低一级的目录中'.'字符后的任何字符串。如果路径最低一级的目录中'没有'.' 或者只有'.',那么该方法返回一个空字符串。extname.js:var path = require('path');path.extname('index.html');// returns '.html'

path.extname('index');// returns ''path.exists(p, [callback])该方法用于测试参数p中的路径是否存在。然后以true或者false作为参数调用callback回调函数。exists.js:var path = require('path');var util = require('util');path.exists('/etc/passwd', function (exists) {util.debug(exists ? "it's there" : "no passwd!");});path.existsSync(p)path.exists的同步方法。

4.3.5 File System

文件的I/O是由标准POSIX函数封装而成。需要使用require('fs')访问这个模块。所有的方法都提供了异步和同步两种方式。异步形式下,方法的最后一个参数需要传入一个执行完成时的回调函数。传给回调函数的参数取决于具体的异步方法,但第一个参数总是保留给异常对象。如果操作成功,那么该异常对象就变为null或者undefined。这里是一个异步调用的例子:var fs = require('fs');fs.unlink('/tmp/hello', function (err) {if (err) throw err;console.log('successfully deleted /tmp/hello');});这里是进行相同操作的同步调用的例子:var fs = require('fs');fs.unlinkSync('/tmp/hello')console.log('successfully deleted /tmp/hello');由于异步方法调用无法保证执行的顺序,所以下面的代码容易导致出现错误。fs.rename('/tmp/hello', '/tmp/world', function (err) {if (err) throw err;console.log('renamed complete');});fs.stat('/tmp/world', function (err, stats) {if (err) throw err;console.log('stats: ' + JSON.stringify(stats));});这样做有可能导致fs.stat在fs.rename之前执行,正确的做法是链式调用回调函数。fs.rename('/tmp/hello', '/tmp/world', function (err) {if (err) throw err;fs.stat('/tmp/world', function (err, stats) {if (err) throw err;console.log('stats: ' + JSON.stringify(stats));});});当需要频繁操作时,强烈建议使用异步方法。同步方式在其完成之前将会阻塞当前的整个进程,即搁置所有连接。

Node会跳到上层目录完成同样的动作,知道模块被找到,或者到达根目录为止。例如,如果在文件'/home/ry/projects/foo.js'中调用require('bar.js'),Node会在下列位置查找,顺序如下:/home/ry/projects/node_modules/bar.js/home/ry/node_modules/bar.js/home/node_modules/bar.js/node_modules/bar.jsNode允许用户在独立的文件夹中方便的组织程序,为这个文件夹指定一个单一的入口后可以把这个文件夹当作模块被加载。

4.3.6 Crypto

有三种方式可以将文件夹作为require函数的参数。第一种方式是在该文件夹中创建package.json文件,指定一个main模块,一个简单的package.json文件会是这样:{ "name" : "some-library","main" : "./lib/some-library.js" }如果此文件位于./some-library文件夹,加载这个文件夹可以调用require('./some-library'),这时会尝试加载./some-library/lib/some-library.js。如果Node在该文件夹下没有找到package.json这个文件,那么Node将依次尝试加载改文件夹下的index.js或index.node文件。例如,如果上面的例子找不到package.json,那么会依次尝试加载:./some-library/index.js./some-library/index.node

4.3.7 ZLIB

Node模块在首次加载成功后会被缓存起来,这意味着同一模块每次调用require方法得到的是完全相同的对象。

整理后记:这四篇文章以前是想整理一本node书籍的,但是由于琐事没坚持下来。


以上内容是否对您有帮助:
停课不停学活动
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号

意见反馈
返回顶部