ZLIB
最后更新于:2022-04-01 01:59:26
### 稳定度: 2 - 稳定
要获取这个模块,你可以通过:
~~~
var zlib = require('zlib');
~~~
它提供了`Gzip/Gunzip`,`Deflate/Inflate`和`DeflateRaw/InflateRaw`类的绑定。每个类都有相同的选项,并且都是可读/可写流。
#### 例子
可以通过将一个`fs.ReadStream`的数据导入一个`zlib`流,然后导入一个`fs.WriteStream`,来压缩或解压缩一个文件。
~~~
var gzip = zlib.createGzip();
var fs = require('fs');
var inp = fs.createReadStream('input.txt');
var out = fs.createWriteStream('input.txt.gz');
inp.pipe(gzip).pipe(out);
~~~
通过使用便捷方法,可以在一个步骤里完成压缩或解压缩数据。
~~~
var input = '.................................';
zlib.deflate(input, function(err, buffer) {
if (!err) {
console.log(buffer.toString('base64'));
}
});
var buffer = new Buffer('eJzT0yMAAGTvBe8=', 'base64');
zlib.unzip(buffer, function(err, buffer) {
if (!err) {
console.log(buffer.toString());
}
});
~~~
如果要在HTTP客户端或服务器上使用这个模块,在请求时需要带上`accept-encoding`头,在响应时需要带上`content-encoding`头。
注意,这些例子都只是非常简单的展示了一些基本的概念。`zlib`编码的开销是非常昂贵的,并且结果需要被缓存。更多关于速度/内存/压缩的权衡,请参阅下文的`内存使用调优`。
~~~
// client request example
var zlib = require('zlib');
var http = require('http');
var fs = require('fs');
var request = http.get({ host: 'izs.me',
path: '/',
port: 80,
headers: { 'accept-encoding': 'gzip,deflate' } });
request.on('response', function(response) {
var output = fs.createWriteStream('izs.me_index.html');
switch (response.headers['content-encoding']) {
// or, just use zlib.createUnzip() to handle both cases
case 'gzip':
response.pipe(zlib.createGunzip()).pipe(output);
break;
case 'deflate':
response.pipe(zlib.createInflate()).pipe(output);
break;
default:
response.pipe(output);
break;
}
});
// server example
// Running a gzip operation on every request is quite expensive.
// It would be much more efficient to cache the compressed buffer.
var zlib = require('zlib');
var http = require('http');
var fs = require('fs');
http.createServer(function(request, response) {
var raw = fs.createReadStream('index.html');
var acceptEncoding = request.headers['accept-encoding'];
if (!acceptEncoding) {
acceptEncoding = '';
}
// Note: this is not a conformant accept-encoding parser.
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
if (acceptEncoding.match(/\bdeflate\b/)) {
response.writeHead(200, { 'content-encoding': 'deflate' });
raw.pipe(zlib.createDeflate()).pipe(response);
} else if (acceptEncoding.match(/\bgzip\b/)) {
response.writeHead(200, { 'content-encoding': 'gzip' });
raw.pipe(zlib.createGzip()).pipe(response);
} else {
response.writeHead(200, {});
raw.pipe(response);
}
}).listen(1337);
~~~
#### zlib.createGzip([options])
根据一个`options`,返回一个新的`Gzip`对象。
#### zlib.createGunzip([options])
根据一个`options`,返回一个新的`Gunzip`对象。
#### zlib.createDeflate([options])
根据一个`options`,返回一个新的`Deflate`对象。
#### zlib.createInflate([options])
根据一个`options`,返回一个新的`Inflate`对象。
#### zlib.createDeflateRaw([options])
根据一个`options`,返回一个新的`DeflateRaw`对象。
#### zlib.createInflateRaw([options])
根据一个`options`,返回一个新的`InflateRaw`对象。
#### zlib.createUnzip([options])
根据一个`options`,返回一个新的`Unzip`对象。
#### Class: zlib.Zlib
这个类未被`zlib`模块暴露。它之所以会出现在这里,是因为它是`compressor/decompressor`类的基类。
#### zlib.flush([kind], callback)
`kind`默认为`zlib.Z_FULL_FLUSH`。
冲刷等待中的数据。不要轻率地调用这个方法,过早的冲刷会给压缩算法带来消极影响。
#### zlib.params(level, strategy, callback)
动态地更新压缩等级和压缩策略。只适用于`deflate`算法。
#### zlib.reset()
将`compressor/decompressor`重置为默认值。只使用于`inflate`和`deflate`算法。
#### Class: zlib.Gzip
使用`gzip`压缩数据。
#### Class: zlib.Gunzip
解压一个`gzip`流。
#### Class: zlib.Deflate
使用`deflate`压缩数据。
#### Class: zlib.Inflate
解压一个`deflate`流。
#### Class: zlib.DeflateRaw
使用`deflate`压缩数据,不添加`zlib`头。
#### Class: zlib.InflateRaw
解压一个原始`deflate`流。
#### Class: zlib.Unzip
通过自动探测头信息,解压`Gzip`或`Deflate`压缩流。
#### 便捷方法
所有的方法接受一个字符串或一个`buffer`作为第一个参数,并且第二个参数是一个可选的 `zlib`类的配置,并且会以`callback(error, result)`的形式执行提供的回调函数。
每一个方法都有一个同步版本,除去回调函数,它们接受相同的参数。
#### zlib.deflate(buf[, options], callback)
#### zlib.deflateSync(buf[, options])
使用`Deflate`压缩一个字符串。
#### zlib.deflateRaw(buf[, options], callback)
#### zlib.deflateRawSync(buf[, options])
使用`DeflateRaw`压缩一个字符串。
#### zlib.gzip(buf[, options], callback)
#### zlib.gzipSync(buf[, options])
使用`Gzip`压缩一个字符串。
#### zlib.gunzip(buf[, options], callback)
#### zlib.gunzipSync(buf[, options])
使用`Gunzip`压缩一个字符串。
#### zlib.inflate(buf[, options], callback)
#### zlib.inflateSync(buf[, options])
使用`Inflate`压缩一个字符串。
#### zlib.inflateRaw(buf[, options], callback)
#### zlib.inflateRawSync(buf[, options])
使用`InflateRaw`压缩一个字符串。
#### zlib.unzip(buf[, options], callback)
#### zlib.unzipSync(buf[, options])
使用`Unzip`压缩一个字符串。
#### Options
每一个类都接受一个`options`对象。所有的`options`对象都是可选的。
注意一些选项只与压缩相关,会被解压缩类忽略:
- flush (默认:`zlib.Z_NO_FLUSH`)
- chunkSize (默认:`16*1024`)
- windowBits
- level (仅用于压缩)
- memLevel (仅用于压缩)
- strategy (仅用于压缩)
- dictionary (仅用于`deflate/inflate`,默认为空目录)
参阅`http://zlib.net/manual.html#Advanced`中`deflateInit2`和`inflateInit2`的描述来获取更多信息。
#### 内存使用调优
来自`zlib/zconf.h`,将其修改为`io.js`的用法:
默认的内存要求(字节)为:
~~~
(1 << (windowBits+2)) + (1 << (memLevel+9))
~~~
换言之:`windowBits=15`的128K 加上 `menLevel = 8`(默认值)的128K 加上其他小对象的一些字节。
例子,如果你想要将默认内存需求从256K减少至128K,将选项设置为:
~~~
{ windowBits: 14, memLevel: 7 }
~~~
当然,它会降低压缩等级(没有免费的午餐)。
`inflate`的内存需求(字节)为:
~~~
1 << windowBits
~~~
换言之:`windowBits=15`(默认值)的32K加上其他小对象的一些字节。
这是内部输出缓冲外的`chunkSize`大小,默认为16K。
`zlib`压缩的速度动态得受设置的压缩等级的影响。高的等级会带来更好地压缩效果,但是花费的时间更长。低的等级会带来更少的压缩效果,但是更快。
通常,更高的内存使用选项意味着`io.js`会调用`zlib`更少次数,因为在一次单独的写操作中它可以处理更多的数据。所以,这是影响速度和内存占用的另一个因素。
#### 常量
所有在`zlib.h`中定义的常量,都也被定义在了`require('zlib')`中。大多数操作中,你都将不会用到它们。它们出现在这里只是为了让你对它们的存在不套感到惊讶。该章节几乎完全来自`zlib`文件。更多详情请参阅`http://zlib.net/manual.html#Constants`。
允许的冲刷值:
~~~
zlib.Z_NO_FLUSH
zlib.Z_PARTIAL_FLUSH
zlib.Z_SYNC_FLUSH
zlib.Z_FULL_FLUSH
zlib.Z_FINISH
zlib.Z_BLOCK
zlib.Z_TREES
~~~
`compression/decompression`函数的返回码。负值代表错误,正值代表特殊但是正常的事件:
~~~
zlib.Z_OK
zlib.Z_STREAM_END
zlib.Z_NEED_DICT
zlib.Z_ERRNO
zlib.Z_STREAM_ERROR
zlib.Z_DATA_ERROR
zlib.Z_MEM_ERROR
zlib.Z_BUF_ERROR
zlib.Z_VERSION_ERROR
~~~
压缩等级:
~~~
zlib.Z_NO_COMPRESSION
zlib.Z_BEST_SPEED
zlib.Z_BEST_COMPRESSION
zlib.Z_DEFAULT_COMPRESSION
~~~
压缩策略:
~~~
zlib.Z_FILTERED
zlib.Z_HUFFMAN_ONLY
zlib.Z_RLE
zlib.Z_FIXED
zlib.Z_DEFAULT_STRATEGY
~~~
`data_type`域的可能值:
~~~
zlib.Z_BINARY
zlib.Z_TEXT
zlib.Z_ASCII
zlib.Z_UNKNOWN
~~~
`deflate`压缩方法(当前版本只支持这一个):
~~~
zlib.Z_DEFLATED
~~~
用于初始化`zalloc`,`zfree`,`opaque`:
~~~
zlib.Z_NULL
~~~
VM
最后更新于:2022-04-01 01:59:24
# 执行`JavaScript`
### 稳定度: 2 - 稳定
要获取这个模块,你可以通过:
~~~
var vm = require('vm');
~~~
`JavaScript`代码会被编译且立刻执行 或 编译,保存,并且稍后执行。
#### vm.runInThisContext(code[, options])
`vm.runInThisContext()`编译代码,运行它,然后返回结果。运行中的代码不能访问本地作用域,但是可以访问当前的全局对象。
使用`vm.runInThisContext`和`eval`运行相同代码的例子:
~~~
var vm = require('vm');
var localVar = 'initial value';
var vmResult = vm.runInThisContext('localVar = "vm";');
console.log('vmResult: ', vmResult);
console.log('localVar: ', localVar);
var evalResult = eval('localVar = "eval";');
console.log('evalResult: ', evalResult);
console.log('localVar: ', localVar);
// vmResult: 'vm', localVar: 'initial value'
// evalResult: 'eval', localVar: 'eval'
~~~
`vm.runInThisContext`不能访问本地作用域,所以`localVar`没有改变。`eval`可以访问本地作用域,所以`localVar`改变了。
这种情况下,`vm.runInThisContext`更像是一个间接的`eval`调用,像`(0,eval)('code')`。但是,它还有以下这些额外的选项:
- filename: 允许你控制提供在堆栈追踪信息中的文件名。
- displayErrors: 是否在抛出异常前向`stderr`打印任何的错误,并且造成错误的行会被高亮。会捕获编译代码时的语法错误和编译完的代码运行时抛出的异常。默认为`true`。
- timeout: 在关闭之前,允许代码执行的时间(毫秒)。如果超时,一个错误被会抛出。
#### vm.createContext([sandbox])
如果指定了一个`sandbox`对象,则将`sandbox`“上下文化”,这样它才可以被`vm.runInContext`或`script.runInContext`使用。在脚本内部,`sandbox`将会是全局对象,保留了它自己所有的属性,并且包含内建对象和标准全局对象的所有函数。在由`vm`模块运行的脚本之外的地方,`sandbox`将不会被改变。
如果没有指定`sandbox`对象,将会返回一个你可以使用的新的,无内容的`sandbox`对象。
这个函数在创建被用来运行多个脚本的沙箱时十分有用,例如,如果你正在模拟一个web浏览器,则可以创建一个代表了`window`全局对象的沙箱,然后在沙箱内运行所有的`<script>`标签。
#### vm.isContext(sandbox)
返回一个沙箱是否已经通过调用`vm.createContext`上下文化。
#### vm.runInContext(code, contextifiedSandbox[, options])
`vm.runInContext`编译代码,然后将其在`contextifiedSandbox`中运行,然后返回结果。运行的代码不能访问本地作用域。`contextifiedSandbox`必须通过`vm.createContext`上下文化;它被用来当做代码的全局对象。
`vm.runInContext`的选项和`vm.runInThisContext`相同。
例子:编译并执行不同的脚本,在同一个已存在的上下文中。
~~~
var util = require('util');
var vm = require('vm');
var sandbox = { globalVar: 1 };
vm.createContext(sandbox);
for (var i = 0; i < 10; ++i) {
vm.runInContext('globalVar *= 2;', sandbox);
}
console.log(util.inspect(sandbox));
// { globalVar: 1024 }
~~~
注意,运行不受信任的代码是一个十分棘手的工作,需要十分小心。`vm.runInContext`是十分有用的,但是为了安全的运行不受信任的代码,还是将它们放在另一个单独的进程中为好。
#### vm.runInNewContext(code[, sandbox][, options])
`vm.runInNewContext`编译代码,接着,如果传递了`sandbox`则上下文化`sandbox`,如果没有就创建一个新的已上下文化的沙箱,然后将沙箱作为全局对象运行代码并返回结果。
`vm.runInNewContext`的选项和`vm.runInThisContext`相同。
例子:编译并执行一个 自增一个全局变量然后设置一个新的全局变量 的代码。这些全局变量包含在沙箱中。
~~~
var util = require('util');
var vm = require('vm');
var sandbox = {
animal: 'cat',
count: 2
};
vm.runInNewContext('count += 1; name = "kitty"', sandbox);
console.log(util.inspect(sandbox));
// { animal: 'cat', count: 3, name: 'kitty' }
~~~
注意,运行不受信任的代码是一个十分棘手的工作,需要十分小心。`vm.runInNewContext`是十分有用的,但是为了安全的运行不受信任的代码,还是将它们放在另一个单独的进程中为好。
#### vm.runInDebugContext(code)
`vm.runInDebugContext`编译代码,然后将它们在V8调试上下文中执行。主要的用途是访问V8调试对象:
~~~
var Debug = vm.runInDebugContext('Debug');
Debug.scripts().forEach(function(script) { console.log(script.name); });
~~~
注意,调试上下文和对象与V8的调试实现联系紧密,它们可能在没有事先提醒的情况就发生改变(或被移除)。
调试对象也可以通过`--expose_debug_as= switch`被暴露。
#### Class: Script
一个包含预编译代码,然后将它们运行在指定沙箱中的类。
#### new vm.Script(code, options)
创建一个编译代码但不执行它的新`Script`类。也就是说,一个创建好的`vm.Script`对象代表了它的编译完毕的代码。这个脚本已经通过下文的方法在晚些时候被调用多次。返回的脚本没有被绑定在任何的全局对象上。它可以在每次运行前被绑定,所以只在那次运行时有效。
`options`可以有以下属性:
- filename: 允许你控制提供在堆栈追踪信息中的文件名。
- displayErrors: 是否在抛出异常前向`stderr`打印任何的错误,并且造成错误的行会被高亮。会捕获编译代码时的语法错误和运行时由脚本的方法的配置所控制的代码抛出的错误。
#### script.runInThisContext([options])
与`vm.runInThisContext`相似,但是是一个预编译的`Script`对象的方法。`script.runInThisContext`运行脚本被编译完毕的代码,然后返回结果。运行中的代码不能访问本地作用域,但是可以访问当前的全局对象。
一个使用`script.runInThisContext`来编译一次代码,然后运行多次的例子:
~~~
var vm = require('vm');
global.globalVar = 0;
var script = new vm.Script('globalVar += 1', { filename: 'myfile.vm' });
for (var i = 0; i < 1000; ++i) {
script.runInThisContext();
}
console.log(globalVar);
// 1000
~~~
`options`可以有以下属性:
-
displayErrors: 是否在抛出异常前向`stderr`打印任何的错误,并且造成错误的行会被高亮。只会应用于运行中代码的执行错误;创建一个有语法错误的`Script`实例是不可能的,因为构造函数会抛出异常。
-
timeout: 在关闭之前,允许代码执行的时间(毫秒)。如果超时,一个错误被会抛出。
#### script.runInContext(contextifiedSandbox[, options])
与`vm.runInContext`相似,但是是一个预编译的`Script`对象的方法。`script.runInContext`运行脚本被编译完毕的代码,然后返回结果。运行中的代码不能访问本地作用域。
`script.runInContext`的选项和`script.runInThisContext`相同。
例子:编译一段 自增一个全局对象并且创建一个全局对象 的代码,然后执行多次,这些全局对象包含在沙箱中。
~~~
var util = require('util');
var vm = require('vm');
var sandbox = {
animal: 'cat',
count: 2
};
var context = new vm.createContext(sandbox);
var script = new vm.Script('count += 1; name = "kitty"');
for (var i = 0; i < 10; ++i) {
script.runInContext(context);
}
console.log(util.inspect(sandbox));
// { animal: 'cat', count: 12, name: 'kitty' }
~~~
注意,运行不受信任的代码是一个十分棘手的工作,需要十分小心。`script.runInContext`是十分有用的,但是为了安全的运行不受信任的代码,还是将它们放在另一个单独的进程中为好。
#### script.runInNewContext([sandbox][, options])
与`vm.runInNewContext`相似,但是是一个预编译的`Script`对象的方法。如果传递了`sandbox`,`script.runInNewContext`将上下文化`sandbox`,如果没有就创建一个新的已上下文化的沙箱,然后将沙箱作为全局对象运行代码并返回结果。运行中的代码不能访问本地作用域。
`script.runInNewContext`的选项和`script.runInThisContext`相同。
例子:编译一段 设置一个全局对象 的代码,然后在不同的上下文中多次执行它。这些全局对象包含在沙箱中。
~~~
var util = require('util');
var vm = require('vm');
var sandboxes = [{}, {}, {}];
var script = new vm.Script('globalVar = "set"');
sandboxes.forEach(function (sandbox) {
script.runInNewContext(sandbox);
});
console.log(util.inspect(sandboxes));
// [{ globalVar: 'set' }, { globalVar: 'set' }, { globalVar: 'set' }]
~~~
注意,运行不受信任的代码是一个十分棘手的工作,需要十分小心。`script.runInNewContext`是十分有用的,但是为了安全的运行不受信任的代码,还是将它们放在另一个单独的进程中为好。
V8
最后更新于:2022-04-01 01:59:22
### 稳定度: 2 - 稳定
这个模块暴露了`io.js`内建的指定版本的V8的事件和接口。这些接口受上游(upstream)变化的影响,所以没有被稳定索引(stability index)所覆盖。
#### getHeapStatistics()
返回一个包含以下属性的对象。
~~~
{
total_heap_size: 7326976,
total_heap_size_executable: 4194304,
total_physical_size: 7326976,
used_heap_size: 3476208,
heap_size_limit: 1535115264
}
~~~
#### setFlagsFromString(string)
设置额外的V8命令行标识。请谨慎使用;在虚拟机启动后改变设定可能会产生不可预测的行为,包括程序崩溃或数据丢失。或者它也可能什么都没有做。
当前`io.js`可用的V8选项,在运行`iojs --v8-options`命令的输出中显示。一个非官方,社区维护的配置列表:`https://github.com/thlorenz/v8-flags/blob/master/flags-0.11.md`。
用处:
~~~
// Print GC events to stdout for one minute.
var v8 = require('v8');
v8.setFlagsFromString('--trace_gc');
setTimeout(function() { v8.setFlagsFromString('--notrace_gc'); }, 60e3);
~~~
Utilities
最后更新于:2022-04-01 01:59:20
### 稳定度: 2 - 稳定
这些功能在模块`'util'`中,通过`require('util')`来使用它们。
`util`模块主要的设计意图是满足`io.js`内部API的需要。但是许多工具对于你的程序也十分有用。如果你发现这些功能不能满足你的需要,那么鼓励你编写自己的工具集。我们对任何`io.js`内部功能不需要的功能,都不感兴趣。
#### util.debuglog(section)
- section String 需要被调试的程序节点
- Returns: Function 日志处理函数
这个方法被用来在`NODE_DEBUG`环境变量存在的情况下,创建一个有条件写入`stderr`的函数。如果`section`名出现在环境变量中,那么返回的函数与`console.error()`类似。否则,返回空函数。
例子:
~~~
var debuglog = util.debuglog('foo');
var bar = 123;
debuglog('hello from foo [%d]', bar);
~~~
如果程序在`NODE_DEBUG=foo`环境下运行,那么输出将是:
~~~
FOO 3245: hello from foo [123]
~~~
`3245`是进程id。如果这个环境变量没有设置,那么将不会打印任何东西。
你可以通过逗号设置多个`NODE_DEBUG`环境变量。例如,`NODE_DEBUG=fs,net,tls`。
#### util.format(format[, ...])
使用第一个参数,像`printf`一样的格式输出格式化字符串。
第一个参数是一个包含了0个或更多占位符的字符串。每个占位符都被其后的参数所替换。支持的占位符有:
- %s - 字符串
- %d - 数字(整数和浮点数)
- %j - JSON。如果参数包含循环引用,则返回字符串`'[Circular]'`。
- %% - 单独的百分比符号(`'%'`),它不消耗一个参数。
如果占位符没有对应的参数,那么占位符将不被替换。
~~~
util.format('%s:%s', 'foo'); // 'foo:%s'
~~~
如果参数多余占位符,那么额外的参数会被转换成字符串(对于对象和链接,使用`util.inspect()`),并且以空格连接。
~~~
util.format('%s:%s', 'foo', 'bar', 'baz'); // 'foo:bar baz'
~~~
如果第一个参数不是格式化字符串,那么`util.format()`将会返回一个以空格连接的所有参数的字符串。每一个参数都被调用`util.inspect()`来转换成字符串。
~~~
util.format(1, 2, 3); // '1 2 3'
~~~
#### util.log(string)
在控制台输出带有时间戳的信息。
~~~
require('util').log('Timestamped message.');
~~~
#### util.inspect(object[, options])
返回一个代表了`object`的字符串,在调试时很有用。
一个可选的`options`对象可以被传递以下属性来影响字符串的格式:
-
showHidden - 如果设置为`true`,那么对象的不可枚举属性也会被显示。默认为`false`。
-
depth - 告诉`inspect`格式化对象时需要递归的次数。这对于巨大的复杂对象十分有用。默认为`2`。传递`null`表示无限递归。
-
colors - 如果为`true`,那么输出会带有ANSI颜色代码风格。默认为`false`。颜色是可以自定义的,参阅下文。
-
customInspect - 如果为`false`,那么定义在被检查对象上的`inspect(depth, opts)`函数将不会被调用。默认为`false`。
一个检查`util`对象所有属性的例子:
~~~
var util = require('util');
console.log(util.inspect(util, { showHidden: true, depth: null }));
~~~
参数值可以提供了它们自己的`inspect(depth, opts)`函数,当被调用时它们会收到当前的递归深度值,以及其他传递给`util.inspect()`的选项。
##### 自定义`util.inspect`颜色
`util.inspect`的有颜色的输出(如果启用)可以全局的通过`util.inspect.styles`和`util.inspect.colors`对象来自定义。
`util.inspect.styles`是通过`util.inspect.colors`设置每个风格一个颜色的映射。高亮风格和它们的默认值为`number (yellow) boolean (yellow) string (green) date (magenta) regexp (red) null (bold) undefined (grey) special`。这时的唯一方法(cyan) * `name (intentionally no styling)`。
预定义颜色有`white, grey, black, blue, cyan, green, magenta, red 和 yellow`。他们都是`bold`,`italic`,`underline`和`inverse`代码。
##### 自定义对象的`inspect()`函数
对象也可以自己定义`inspect(depth)`函数,`util.inspect()`将会调用它,并且输出它的结果:
~~~
var util = require('util');
var obj = { name: 'nate' };
obj.inspect = function(depth) {
return '{' + this.name + '}';
};
util.inspect(obj);
// "{nate}"
~~~
你也可以完全返回另一个对象,并且返回的字符串是由这个返回对象格式化而来的,这也`JSON.stringify()`相似:
~~~
var obj = { foo: 'this will not show up in the inspect() output' };
obj.inspect = function(depth) {
return { bar: 'baz' };
};
util.inspect(obj);
// "{ bar: 'baz' }"
~~~
#### util.isArray(object)
`Array.isArray`的内部别名。
如果`object`是一个数组则返回`true`,否则返回`false`。
~~~
var util = require('util');
util.isArray([])
// true
util.isArray(new Array)
// true
util.isArray({})
// false
~~~
#### util.isRegExp(object)
如果`object`是一个正则表达式则返回`true`,否则返回`false`。
~~~
var util = require('util');
util.isRegExp(/some regexp/)
// true
util.isRegExp(new RegExp('another regexp'))
// true
util.isRegExp({})
// false
~~~
#### util.isDate(object)
如果`object`是一个日期则返回`true`,否则返回`false`。
~~~
var util = require('util');
util.isDate(new Date())
// true
util.isDate(Date())
// false (without 'new' returns a String)
util.isDate({})
// false
~~~
#### util.isError(object)
如果`object`是一个错误对象则返回`true`,否则返回`false`。
~~~
var util = require('util');
util.isError(new Error())
// true
util.isError(new TypeError())
// true
util.isError({ name: 'Error', message: 'an error occurred' })
// false
~~~
#### util.isBoolean(object)
如果`object`是一个布尔值则返回`true`,否则返回`false`。
~~~
var util = require('util');
util.isBoolean(1)
// false
util.isBoolean(0)
// false
util.isBoolean(false)
// true
~~~
#### util.isNull(object)
如果`object`是严格的`null`则返回`true`,否则返回`false`。
~~~
var util = require('util');
util.isNull(0)
// false
util.isNull(undefined)
// false
util.isNull(null)
// true
~~~
#### util.isNullOrUndefined(object)
如果`object`是一`null`或`undefined`则返回`true`,否则返回`false`。
~~~
var util = require('util');
util.isNullOrUndefined(0)
// false
util.isNullOrUndefined(undefined)
// true
util.isNullOrUndefined(null)
// true
~~~
#### util.isNumber(object)
如果`object`是一个数字则返回`true`,否则返回`false`。
~~~
var util = require('util');
util.isNumber(false)
// false
util.isNumber(Infinity)
// true
util.isNumber(0)
// true
util.isNumber(NaN)
// true
~~~
#### util.isString(object)
如果`object`是一个字符串则返回`true`,否则返回`false`。
~~~
var util = require('util');
util.isString('')
// true
util.isString('foo')
// true
util.isString(String('foo'))
// true
util.isString(5)
// false
~~~
#### util.isSymbol(object)
如果`object`是一个`Symbol`则返回`true`,否则返回`false`。
~~~
var util = require('util');
util.isSymbol(5)
// false
util.isSymbol('foo')
// false
util.isSymbol(Symbol('foo'))
// true
~~~
#### util.isUndefined(object)
如果`object`是`undefined`则返回`true`,否则返回`false`。
~~~
var util = require('util');
var foo;
util.isUndefined(5)
// false
util.isUndefined(foo)
// true
util.isUndefined(null)
// false
~~~
#### util.isObject(object)
如果`object`严格的是一个对象而不是一个函数,则返回`true`,否则返回`false`。
~~~
var util = require('util');
util.isObject(5)
// false
util.isObject(null)
// false
util.isObject({})
// true
util.isObject(function(){})
// false
~~~
#### util.isFunction(object)
如果`object`是一个函数则返回`true`,否则返回`false`。
~~~
var util = require('util');
function Foo() {}
var Bar = function() {};
util.isFunction({})
// false
util.isFunction(Foo)
// true
util.isFunction(Bar)
// true
~~~
#### util.isPrimitive(object)
如果`object`是一个基本值则返回`true`,否则返回`false`。
~~~
var util = require('util');
util.isPrimitive(5)
// true
util.isPrimitive('foo')
// true
util.isPrimitive(false)
// true
util.isPrimitive(null)
// true
util.isPrimitive(undefined)
// true
util.isPrimitive({})
// false
util.isPrimitive(function() {})
// false
util.isPrimitive(/^$/)
// false
util.isPrimitive(new Date())
// false
~~~
#### util.isBuffer(object)
如果`object`是一个`buffer`则返回`true`,否则返回`false`。
~~~
var util = require('util');
util.isBuffer({ length: 0 })
// false
util.isBuffer([])
// false
util.isBuffer(new Buffer('hello world'))
// true
~~~
#### util.inherits(constructor, superConstructor)
将一个构造函数所有的原型方法继承到到另一个中。构造函数的原型将会被设置为一个超类创建的新对象。
为了方便起见,超类可以通过`constructor.super_`来访问。
~~~
var util = require("util");
var events = require("events");
function MyStream() {
events.EventEmitter.call(this);
}
util.inherits(MyStream, events.EventEmitter);
MyStream.prototype.write = function(data) {
this.emit("data", data);
}
var stream = new MyStream();
console.log(stream instanceof events.EventEmitter); // true
console.log(MyStream.super_ === events.EventEmitter); // true
stream.on("data", function(data) {
console.log('Received data: "' + data + '"');
})
stream.write("It works!"); // Received data: "It works!"
~~~
#### util.deprecate(function, string)
标记一个方法为不应再使用。
~~~
var util = require('util');
exports.puts = util.deprecate(function() {
for (var i = 0, len = arguments.length; i < len; ++i) {
process.stdout.write(arguments[i] + '\n');
}
}, 'util.puts: Use console.log instead');
~~~
默认返回一个被运行时会发出一次警告的,修改后的函数。
如果`--no-deprecation`被设置,那么这个函数将为空。可以在运行时通过`process.noDeprecation`布尔值配置(只有在模块被加载前设置,才会有效)。
如果`--trace-deprecation`被设置,当被弃用的API第一次被使用时,会向控制台打印一个警告和堆栈信息。可以在运行时通过`process.traceDeprecation`布尔值配置。
如果`--throw-deprecation`被设置,那么当被弃用的API被使用时,应用会抛出一个错误。可以在运行时通过`process.throwDeprecation`布尔值配置。
`process.throwDeprecation`的优先级高于`process.traceDeprecation`。
#### util.debug(string)
> 稳定度: 0 - 弃用: 使用`console.error()`代替。
被弃用,`console.error`的前身。
#### util.error([...])
> 稳定度: 0 - 弃用: 使用`console.error()`代替。
被弃用,`console.error`的前身。
#### util.puts([...])
> 稳定度: 0 - 弃用: 使用`console.log()`代替。
被弃用,`console.log`的前身。
#### util.print([...])
> 稳定度: 0 - 弃用: 使用`console.log()`代替。
被弃用,`console.log`的前身。
#### util.pump(readableStream, writableStream[, callback])
> 稳定度: 0 - 弃用: 使用`readableStream.pipe(writableStream)`代替。
被弃用,`stream.pipe()`的前身。
URL
最后更新于:2022-04-01 01:59:17
### 稳定度: 2 - 稳定
这个模块提供了URL解析和解释的工具。通过`require('url')`使用它。
解释URL为一个含有以下部分或全部属性的对象,依赖于它们是否在URL字符串中存在。任何不存在的部分都不会出现在解释后的对象中。一个下面URL的例子:
`'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'`
- href: 最初传递的全部URL。协议和主机都是小写的。
例子: `'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'`
- protocol: 请求的协议,小写。
例子: `'http:'`
- slashes: 协议要求冒号后有斜杠。
例子: `true` 或 `false`
- host: URL的所有主机部分,包括端口,小写。
例子: `'host.com:8080'`
- auth: URL的认证信息部分。
例子: `'user:pass'`
- hostname: 小写的主机名部分。
例子: `'host.com'`
- port: 主机部分的端口号。
例子: `'8080'`
- pathname: URL的路径部分,在主机之后,在查询之前,包括最前面的斜杠,如果存在的话。不提供解码。
例子: `'/p/a/t/h'`
- search: URL的“查询字符串”部分,包括前导的问号标志。
例子: `'?query=string'`
- path: 路径和查询的连接体。不提供解码。
例子: `'/p/a/t/h?query=string'`
- query: 查询字符串的“参数”部分,或查询字符串被解释后的对象。
例子: `'query=string'` 或 `{'query':'string'}`
- hash: URL的“碎片”部分,包括英镑符号。
例子: `'#hash'`
以下是URL模块提供的方法:
#### url.parse(urlStr[, parseQueryString][, slashesDenoteHost])
接收一个URL字符串,然后返回一个对象。
对第二个参数传递`true`,将使用`querystring`模块来解释查询字符串。如果为`true`,那么最后的对象中一定存在`query`属性,并且`search`属性将总是一个字符串(可能为空)。如果为`false`,那么`query`属性将不会被解释或解码。默认为`false`。
对第三个参数传递`true`,将会把`//foo/bar`解释为`{ host: 'foo', pathname: '/bar' }`,而不是`{ pathname: '//foo/bar' }`。默认为`false`。
#### url.format(urlObj)
接受一个解释完毕的URL对象,返回格式化URL字符串。
以下是格式化过程:
- `href`将会被忽略。
- `path`将会被忽略。
- **协议无论是否有末尾的冒号,都会被同样处理。**
- http,https,ftp,gopher,file协议的后缀是`://`。
- 所有其他如mailto,xmpp,aim,sftp,foo等协议的后缀是`:`。
- **如果协议要求有 `://` ,`slashes`会被设置为`true`**
- 只有之前没有列出的要求有斜线的协议才需要被设置。如`mongodb://localhost:8000/`。
- `auth`会被使用,如果存在的话。
- 只有当缺少`host`时,才会使用`hostname`。
- 只有当缺少`host`时,才会使用`port`。
- `host`将会替代`hostname`和`port`。
- 无论有没有前导 / (斜线),`pathname`都会被相同对待。
- 只有在缺少`search`时,才会使用`query`(对象;参阅`querystring`)。
- **`search`将会替代`query`**
- 无论有没有前导 ?(问号),它都会被相同对待。
- 无论有没有前导 #(英镑符号),`hash`都会被相同对待。
#### url.resolve(from, to)
接受一个基础URL,和一个路径URL,并且带上锚点像浏览器一样解析他们。例子:
~~~
url.resolve('/one/two/three', 'four') // '/one/two/four'
url.resolve('http://example.com/', '/one') // 'http://example.com/one'
url.resolve('http://example.com/one', '/two') // 'http://example.com/two'
~~~
UDP/Datagram
最后更新于:2022-04-01 01:59:15
### 稳定度: 2 - 稳定
数据报`socket`通过`require('dgram')`使用。
重要提示:`dgram.Socket#bind()`的表现在v0.10中被改变,并且现在总是异步的,如果你有像这样的代码:
~~~
var s = dgram.createSocket('udp4');
s.bind(1234);
s.addMembership('224.0.0.114');
~~~
你必须改成这样:
~~~
var s = dgram.createSocket('udp4');
s.bind(1234, function() {
s.addMembership('224.0.0.114');
});
~~~
#### dgram.createSocket(type[, callback])
- type String. `'udp4'`或`'udp6'`,两者之一
- callback Function. 可选,会被添加为`message`事件的监听器
- Returns: `socket`对象
创建一个指定类型的数据报`socket`。可用类型是udp4和udp6.
接受一个可选的回调函数,它会被自动添加为`message`事件的监听器。
如果你想要接收数据报,调用`socket.bind()`。`socket.bind()`将会到`所有网络接口`地址中的一个随机端口(不论udp4和upd6 `socket`,它都可以正常工作)。你可以从`socket.address().address`和`socket.address().port`中获取地址和端口。
#### dgram.createSocket(options[, callback])
- options Object
- callback Function. 会被添加为`message`事件的监听器
- Returns: `socket`对象
`options`对象必须包含一个`type`属性,可是udp4或udp6。还有一个可选的`reuseAddr`布尔值属性。
当`reuseAddr`为`true`时,`socket.bind()`会重用地址,甚至是当另一个进程已经在这之上绑定了一个`socket`时。默认为`false`。
接受一个可选的回调函数,它会被自动添加为`message`事件的监听器。
如果你想要接收数据报,调用`socket.bind()`。`socket.bind()`将会到`所有网络接口`地址中的一个随机端口(不论udp4和upd6 `socket`,它都可以正常工作)。你可以从`socket.address().address`和`socket.address().port`中获取地址和端口。
#### Class: dgram.Socket
`dgram.Socket`类封装了数据报的功能。它必须被`dgram.createSocket(...)`创建。
#### Event: 'message'
- msg Buffer object. 消息
- rinfo Object. 远程地址信息
当在`socket`中一个新的数据报可用时触发。`msg`是一个`buffer`并且`rinfo`是一个包含发送者地址信息的对象:
~~~
socket.on('message', function(msg, rinfo) {
console.log('Received %d bytes from %s:%d\n',
msg.length, rinfo.address, rinfo.port);
});
~~~
#### Event: 'listening'
当一个`socket`开始监听数据报时触发。在UDP `socket`被创建时触发。
#### Event: 'close'
在一个`socket`通过`close()`被关闭时触发。这个`socket`中不会再触发新的`message`事件。
#### Event: 'error'
- exception Error object
当错误发生时触发。
#### socket.send(buf, offset, length, port, address[, callback])
- buf Buffer object or string. 要被发送的信息。
- offset Integer. 信息在`buffer`里的初始偏移位置。
- length Integer. 信息的字节数。
- port Integer. 目标端口。
- address String. 目标主机或IP地址。
- callback Function. 可选,当信息被发送后调用。
对于UDP `socket`,目标端口和地址都必须被指定。`address`参数需要提供一个字符串,并且它会被DNS解析。
如果`address`被忽略,或者是一个空字符串。将会使用`'0.0.0.0'`或`'::0'`。这取决于网络配置,这些默认值 可能会 或 可能不会 正常工作;所以最好还是明确指定目标地址。
如果一个`socket`先前没有被调用`bind`来绑定,它将会赋于一个随机端口数并且被绑定到“所有网络接口”地址(udp4 `socket`为`'0.0.0.0'`,udp6则为`'::0'`)。
一个可选的回调函数可以被指定,用来检测DNS错误,或决定重用`buf`对象是否安全。注意,DNS查找至少会延迟一个事件循环。唯一能确定数据报被发送的方法就是使用一个回调函数。
出于对多字节字符的考虑,`offset`和`length`将会根据字节长度而不是字符位置被计算。
一个向`localhost`上的一个随机端口发送UDP报文的例子:
~~~
var dgram = require('dgram');
var message = new Buffer("Some bytes");
var client = dgram.createSocket("udp4");
client.send(message, 0, message.length, 41234, "localhost", function(err) {
client.close();
});
~~~
##### UDP数据报大小的注意事项
IPv4/v6数据报的最大大小取决于`MTU`(最大传输单位),和`Payload Length`字段大小。
-
`Payload Length`是16字节宽的,意味着一个正常的负载不能超过64K 八位字节,包括网络头和数据(65,507 字节 = 65,535 − 8 字节 UDP 头 − 20 字节 IP 头);对于环回接口总是`true`,但是如此大的数据报对于大多数主机和网络来说都是不现实的。
-
`MTU`是指定的链路层技术支持的报文的最大大小。对于所有连接,IPv4允许最小`MTU`为68八位字节,而推荐的IPv4 `MTU`是576(通常作为拨号类应用的推荐`MTU`),无论它们是完整的还是以碎片形式到达。
-
对于IPv6,最小MTU是1280八位字节,但是,允许的最小`buffer`重组大小是1500八位字节。68八位字节非常小,所以大多数的当前链路层技术的最小`MTU`都是1500(如`Ethernet`)。
注意,不可能提前知道一个报文可能经过的每一个连接`MTU`,并且通常不能发送一个大于(接收者)`MTU`的数据报(报文会被默默丢弃,不会通知源头:这个数据没有到达已定的接收方)。
#### socket.bind(port[, address][, callback])
- port Integer
- address String, 可选
- callback Function 可选,没有参数。当绑定完毕后触发。
对于UDP `socket`,监听一个具名的端口和一个可选的地址上的数据报。如果`address`没有被指定,操作系统将会试图监听所有端口。在绑定完毕后,`listening`事件会被吃法,并且回调函数(如果指定了)会被调用。同时指定`listening`事件的监听器和`callback`没有危险,但是不是很有用。
一个绑定的数据报`socket`将会保持`io.js`进程的运行,来接受数据报。
如果绑定失败,一个`error`事件会产生。极少数情况下(例如绑定一个关闭的`socket`),这个方法会抛出一个错误。
一个监听41234端口的UDP服务器:
~~~
var dgram = require("dgram");
var server = dgram.createSocket("udp4");
server.on("error", function (err) {
console.log("server error:\n" + err.stack);
server.close();
});
server.on("message", function (msg, rinfo) {
console.log("server got: " + msg + " from " +
rinfo.address + ":" + rinfo.port);
});
server.on("listening", function () {
var address = server.address();
console.log("server listening " +
address.address + ":" + address.port);
});
server.bind(41234);
// server listening 0.0.0.0:41234
~~~
#### socket.bind(options[, callback])
- **options Object** - 必选,支持以下属性:
- port Number - 必须
- address String - 可选
- exclusive Boolean - 可选
- callback Function - 可选
`options`的`prot`和`address`属性,以及可选的回调函数,与`socket.bind(port, [address], [callback])`中它们的表现一致。
如`exclusive`为`false`(默认),那么集群的工作进程将会使用相同的底层句柄,允许共享处理连接的职责。当为`true`时,句柄不被共享,企图共享端口会导致一个错误。一个监听一个`exclusive`端口的例子:
~~~
socket.bind({
address: 'localhost',
port: 8000,
exclusive: true
});
~~~
#### socket.close([callback])
关闭底层`socket`,并且停止监听新数据。如果提供了回调函数,它会被添加为`close`事件的监听器。
#### socket.address()
返回一个包含`socket`地址信息的对象。对于UDP `socket`,这个对象将会包含`address`,`family`和`port`。
#### socket.setBroadcast(flag)
- flag Boolean
设置或清除`SO_BROADCAST``socket`设置。当这个选项被设置,UDP报文将会被送至本地接口的广播地址。
#### socket.setTTL(ttl)
- ttl Integer
设置`IP_TTL``socket`选项。`TTL`的意思是“生存时间(Time to Live)”,但是在这里的上下文中,它值一个报文通过的IP跃点数。每转发报文的路由或网关都会递减`TTL`。如果`TTL`被一个路由递减为`0`,它将不再被转发。改变`TTL`值常用于网络探测器或多播。
`setTTL()`的参数是一个`1`到`225`之间的跃点数。多数系统中的默认值为`64`。
#### socket.setMulticastTTL(ttl)
- ttl Integer
设置`IP_MULTICAST_TTL``socket`选项。`TTL`的意思是“生存时间(Time to Live)”,但是在这里的上下文中,它值一个报文通过的IP跃点数,特别是组播流量。每转发报文的路由或网关都会递减`TTL`。如果`TTL`被一个路由递减为`0`,它将不再被转发。
`setMulticastTTL()`的参数是一个`0`到`225`之间的跃点数。多数系统中的默认值为`1`。
#### socket.setMulticastLoopback(flag)
- flag Boolean
设置或清除`IP_MULTICAST_LOOP``socket`选项。当这个选项被设置,组播报文也将会在本地接口上接收。
#### socket.addMembership(multicastAddress[, multicastInterface])
- multicastAddress String
- multicastInterface String, 可选
告诉内核加入一个组播分组,通过`IP_ADD_MEMBERSHIP``socket`选项。
如果`multicastInterface`没有被指定,那么操作系统将会尝试加入成为所有可用的接口的成员。
#### socket.dropMembership(multicastAddress[, multicastInterface])
- multicastAddress String
- multicastInterface String, 可选
与`addMembership`相反 - 告诉内核离开一个组播分组,通过`IP_DROP_MEMBERSHIP``socket`选项。当`socket`被关闭或进程结束时,它会被内核自动调用。所以大多数应用不需要亲自调用它。
如果`multicastInterface`没有被指定,那么操作系统将会尝试脱离所有可用的接口。
#### socket.unref()
在一个`socket`上调用`unref`将会在它是事件系统中唯一活跃的`socket`时,允许程序退出。如果`socket`已经被`unref`,再次调用将不会有任何效果。
返回一个`socket`。
#### socket.ref()
与`unref`相反,在一个先前被`unref`的`socket`上调用`ref`,那么在它是唯一的剩余的`socket`(默认行为)时,将不允许程序退出。如果`socket`已经被`ref`,再次调用将不会有任何效果。
返回一个`socket`。
TTY
最后更新于:2022-04-01 01:59:13
### Stability: 2 - Stable
`tty`模块主要提供了`tty.ReadStream`和`tty.WriteStream`这两个类。大多数情况下,你都不需要直接使用这个模块。
当`io.js`检测到它运行于TTY上下文中,那么`process.stdin`将会是一个`tty.ReadStream`实例,`process.stdout`将会是一个`tty.WriteStream`实例。测试`io.js`是否运行在TTY上下文中的一个比较好的办法是检查`process.stdout.isTTY`:
~~~
$ iojs -p -e "Boolean(process.stdout.isTTY)"
true
$ iojs -p -e "Boolean(process.stdout.isTTY)" | cat
false
~~~
#### tty.isatty(fd)
如果`fd`关联了终端,就返回`true`,反之返回`false`。
#### tty.setRawMode(mode)
已弃用。使用`tty.ReadStream#setRawMode()`(如`process.stdin.setRawMode()`)代替。
#### Class: ReadStream
一个`net.Socket`子类,代表了一个TTY中的可读部分。一般情况下,在任何`io.js`程序(仅当`isatty(0)`为`true`时)中,`process.stdin`将是仅有的`tty.ReadStream`实例。
#### rs.isRaw
一个被初始化为`false`的布尔值。它代表了`tty.ReadStream`实例的“原始”状态。
#### rs.setRawMode(mode)
`mode`必须为`true`或`false`。它设定`tty.ReadStream`的属性表现得像原始设备或默认值。`isRaw`将会被设置为结果模式(resulting mode)。
#### Class: WriteStream
一个`net.Socket`子类,代表了一个TTY中的可写部分。一般情况下,在任何`io.js`程序(仅当`isatty(1)`为`true`时)中,`process.stdout`将是仅有的`tty.WriteStream`实例。
#### ws.columns
一个表示了TTY当前拥有列数的数字。这个属性会通过`resize`事件被更新。
#### ws.rows
一个表示了TTY当前拥有行数的数字。这个属性会通过`resize`事件被更新。
#### Event: 'resize'
- function () {}
当列属性或行属性被改变时,通过`refreshSize()`被触发。
~~~
process.stdout.on('resize', function() {
console.log('screen size has changed!');
console.log(process.stdout.columns + 'x' + process.stdout.rows);
});
~~~
TLS/SSL
最后更新于:2022-04-01 01:59:10
### 稳定度: 2 - 稳定
通过`require('tls')`来使用这个模块。
`tls`模块使用OpenSSL来提供传输层的安全 和/或 安全`socket`层:已加密的流通信。
TLS/SSL是一种公/私钥架构。每个客户端和每个服务器都必须有一个私钥。一个私钥通过像如下的方式创建:
~~~
openssl genrsa -out ryans-key.pem 2048
~~~
所有的服务器和部分的客户端需要一个证书。证书是被CA签名或自签名的公钥。获取一个证书第一步是创建一个“证书签署请求(Certificate Signing Request)”(CSR)文件。通过:
~~~
openssl req -new -sha256 -key ryans-key.pem -out ryans-csr.pem
~~~
要通过CSR创建一个自签名证书,通过:
~~~
openssl x509 -req -in ryans-csr.pem -signkey ryans-key.pem -out ryans-cert.pem
~~~
另外,你也可以把CSR交给一个CA请求签名。
为了完全向前保密(PFS),需要产生一个 迪菲-赫尔曼 参数:
~~~
openssl dhparam -outform PEM -out dhparam.pem 2048
~~~
创建`.pfx`或`.p12`,通过:
~~~
openssl pkcs12 -export -in agent5-cert.pem -inkey agent5-key.pem \
-certfile ca-cert.pem -out agent5.pfx
~~~
- in: 证书
- inkey: 私钥
- certfile: 将所有`CA certs`串联在一个文件中,就像`cat ca1-cert.pem ca2-cert.pem > ca-cert.pem`。
#### 客户端发起的重新协商攻击的减缓
TLS协议让客户端可以重新协商某些部分的TLS会话。不幸的是,会话重协商需要不相称的服务器端资源,这它可能成为潜在的DOS攻击。
为了减缓这种情况,重新协商被限制在了每10分钟最多3次。当超过阀值时,`tls.TLSSocket`会触发一个错误。阀值是可以调整的:
-
tls.CLIENT_RENEG_LIMIT: 重新协商限制,默认为`3`。
-
tls.CLIENT_RENEG_WINDOW: 重新协商窗口(秒),默认为10分钟。
除非你知道你在做什么,否则不要改变默认值。
为了测试你的服务器,使用`openssl s_client -connect address:port`来连接它,然后键入`R<CR>`(字母`R`加回车)多次。
#### NPN 和 SNI
`NPN`(下个协议协商)和SNI(服务器名称指示)都是TLS握手拓展,它们允许你:
- NPN - 通过多个协议(HTTP,SPDY)使用一个TLS服务器。
- SNI - 通过多个有不同的SSL证书的主机名来使用一个TLS服务器。
#### 完全向前保密
术语“向前保密”或“完全向前保密”描述了一个密钥-协商(如密钥-交换)方法的特性。事实上,它意味着,甚至是当(你的)服务器的私钥被窃取了,窃取者也只能在他成功获得所有会话产生的密钥对时,才能解码信息。
它通过在每次握手中(而不是所有的会话都是同样的密钥)随机地产生用于密钥-协商的密钥对来实现。实现了这个技术的方法被称作“ephemeral”。
目前有两种普遍的方法来实现完全向前保密:
- DHE - 一个 迪菲-赫尔曼 密钥-协商 协议的`ephemeral`版本。
- ECDHE - 一个椭圆曲线 迪菲-赫尔曼 密钥-协商 协议的`ephemeral`版本。
`ephemeral`方法可能有一些性能问题,因为密钥的生成是昂贵的。
#### tls.getCiphers()
返回支持的SSL加密器的名字数组。
例子:
~~~
var ciphers = tls.getCiphers();
console.log(ciphers); // ['AES128-SHA', 'AES256-SHA', ...]
~~~
#### tls.createServer(options[, secureConnectionListener])
创一个新的`tls.Server`实例。`connectionListener`参数被自动添加为`secureConnection`事件的监听器。`options`参数可以有以下属性:
-
pfx: 一个包含`PFX`或`PKCS12`格式的私钥,加密凭证和CA证书的字符串或`buffer`。
-
key: 一个带着`PEM`加密私钥的字符串(可以是密钥数组)(必选)。
-
passphrase: 一个私钥或`pfx`密码字符串。
-
cert: 一个包含了`PEM`格式的服务器证书密钥的字符串或`buffer`(可以是`cert`数组)(必选)。
-
ca: 一个`PEM`格式的受信任证书的字符串或`buffer`数组。如果它被忽略,将使用一些众所周知的“根”CA,像`VeriSign`。这些被用来授权连接。
-
crl : 一个`PEM`编码的证书撤销列表(Certificate Revocation List)字符串或字符串列表。
-
ciphers: 一个描述要使用或排除的加密器的字符串,通过`:`分割。默认的加密器套件是:
~~~
ECDHE-RSA-AES128-GCM-SHA256:
ECDHE-ECDSA-AES128-GCM-SHA256:
ECDHE-RSA-AES256-GCM-SHA384:
ECDHE-ECDSA-AES256-GCM-SHA384:
DHE-RSA-AES128-GCM-SHA256:
ECDHE-RSA-AES128-SHA256:
DHE-RSA-AES128-SHA256:
ECDHE-RSA-AES256-SHA384:
DHE-RSA-AES256-SHA384:
ECDHE-RSA-AES256-SHA256:
DHE-RSA-AES256-SHA256:
HIGH:
!aNULL:
!eNULL:
!EXPORT:
!DES:
!RC4:
!MD5:
!PSK:
!SRP:
!CAMELLIA
~~~
默认的加密器套件更倾向于`Chrome's 'modern cryptography' setting`的GCM加密器,也倾向于PFC的ECDHE和DHE加密器,它们提供了一些向后兼容性。
鉴于`specific attacks affecting larger AES key sizes`,所以更倾向于使用128位的AES而不是192和256位的AES。
旧的依赖于不安全的和弃用的RC4或基于DES的加密器(像IE6)的客户端将不能完成默认配置下的握手。如果你必须支持这些客户端,`TLS推荐规范`可能提供了一个兼容的加密器套件。更多格式细节,参阅`OpenSSL cipher list format documentation`。
- ecdhCurve: 一个描述用于`ECDH`密钥协商的已命名的椭圆的字符串,如果要禁用`ECDH`,就设置为`false`。
默认值为`prime256v1`(NIST P-256)。使用`crypto.getCurves()`来获取一个可用的椭圆列表。在最近的发行版中,运行`openssl ecparam -list_curves`命令也会展示所有可用的椭圆的名字和描述。
-
dhparam: 一个包含了迪菲-赫尔曼参数的字符串或`buffer`,要求有完全向前保密。使用`openssl dhparam`来创建它。它的密钥长度需要大于等于1024字节,否则会抛出一个错误。强力推荐使用2048或更多位,来获取更高的安全性。如果参数被忽略或不合法,它会被默默丢弃并且`DHE`加密器将不可用。
-
handshakeTimeout: 当SSL/TLS握手在这个指定的毫秒数后没有完成时,终止这个链接。默认为120秒。
当握手超时时,`tls.Server`会触发一个`clientError`事件。
-
honorCipherOrder : 选择一个加密器时,使用使用服务器的首选项而不是客户端的首选项。默认为`true`。
-
requestCert: 如果设置为`true`,服务器将会向连接的客户端请求一个证书,并且试图验证这个证书。默认为`true`。
-
rejectUnauthorized: 如果设置为`true`,服务器会拒绝所有没有在提供的CA列表中被授权的客户端。只有在`requestCert`为`true`时这个选项才有效。默认为`false`。
-
NPNProtocols: 一个可用的`NPN`协议的字符串或数组(协议应该由它们的优先级被排序)。
-
SNICallback(servername, cb): 当客户端支持`SNI TLS`扩展时,这个函数会被调用。这个函数会被传递两个参数:servername和cb。`SNICallback`必须执行`cb(null, ctx)`,`ctx`是一个`SecureContext`实例(你可以使用`tls.createSecureContext(...)`来获取合适的`SecureContext`)。如果`SNICallback`没有被提供 - 默认的有高层次API的回调函数会被使用(参阅下文)。
-
sessionTimeout: 一个指定在TLS会话标识符和TLS会话门票(tickets)被服务器创建后的超时时间。更多详情参阅`SSL_CTX_set_timeout`。
-
ticketKeys: 一个由16字节前缀,16字节hmac密钥,16字节AEC密钥组成的48字节`buffer`。你可以使用它在不同的`tls`服务器实例上接受`tls`会话门票。
注意:会在`cluster`模块工作进程间自动共享。
-
sessionIdContext: 一个包含了会话恢复标识符的字符串。如果`requestCert`为`true`,默认值是通过命令行生成的MD5哈希值。否则,就将不提供默认值。
-
secureProtocol: 将要使用的SSL方法,举例,`SSLv3_method`将强制使用SSL v3。可用的值取决于OpenSSL的安装和`SSL_METHODS`常量中被定义的值。
下面是一个简单应答服务器的例子:
~~~
var tls = require('tls');
var fs = require('fs');
var options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
// This is necessary only if using the client certificate authentication.
requestCert: true,
// This is necessary only if the client uses the self-signed certificate.
ca: [ fs.readFileSync('client-cert.pem') ]
};
var server = tls.createServer(options, function(socket) {
console.log('server connected',
socket.authorized ? 'authorized' : 'unauthorized');
socket.write("welcome!\n");
socket.setEncoding('utf8');
socket.pipe(socket);
});
server.listen(8000, function() {
console.log('server bound');
});
~~~
或
~~~
var tls = require('tls');
var fs = require('fs');
var options = {
pfx: fs.readFileSync('server.pfx'),
// This is necessary only if using the client certificate authentication.
requestCert: true,
};
var server = tls.createServer(options, function(socket) {
console.log('server connected',
socket.authorized ? 'authorized' : 'unauthorized');
socket.write("welcome!\n");
socket.setEncoding('utf8');
socket.pipe(socket);
});
server.listen(8000, function() {
console.log('server bound');
});
~~~
你可以通过`openssl s_client`来连接服务器:
~~~
openssl s_client -connect 127.0.0.1:8000
~~~
#### tls.connect(options[, callback])
#### tls.connect(port[, host][, options][, callback])
根据给定的 端口和主机(旧API)或 `options.port`和`options.host` 创建一个新的客户端连接。如果忽略了主机,默认为`localhost`。`options`可是一个含有以下属性的对象:
-
host: 客户端应该连接到的主机。
-
port: 客户端应该连接到的端口。
-
socket: 根据给定的`socket`的来建立安全连接,而不是创建一个新的`socket`。如果这个选项被指定,`host`和`port`会被忽略。
-
path: 创建到`path`的unix `socket`连接。如果这个选项被指定,`host`和`port`会被忽略。
-
pfx: 一个`PFX`或`PKCS12`格式的包含了私钥,证书和CA证书的字符串或`buffer`。
-
key: 一个`PEM`格式的包含了客户端私钥的字符串或`buffer`(可以是密钥的数组)。
-
passphrase: 私钥或`pfx`的密码字符串。
-
cert: 一个`PEM`格式的包含了证书密钥的字符串或`buffer`(可以是密钥的数组)。
-
ca: 一个`PEM`格式的受信任证书的字符串或`buffer`数组。如果它被忽略,将使用一些众所周知的CA,像`VeriSign`。这些被用来授权连接。
-
ciphers: 一个描述了要使用或排除的加密器,由`:`分割。使用的默认加密器套件与`tls.createServer`使用的一样。
-
rejectUnauthorized: 若被设置为`true`,会根据提供的CA列表来验证服务器证书。当验证失败时,会触发`error`事件;`err.code`包含了一个OpenSSL错误码。默认为`true`。
-
NPNProtocols: 包含支持的NPN协议的字符串或`buffer`数组。`buffer`必须有以下格式:`0x05hello0x05world`,第一个字节是下一个协议名的长度(传递数组会更简单:`['hello', 'world']`)。
-
servername: `SNI` TLS 扩展的服务器名。
-
checkServerIdentity(servername, cert): 为根据证书的服务器主机名检查提供了覆盖。必须在验证失败时返回一个错误,验证通过时返回`undefined`。
-
secureProtocol: 将要使用的SSL方法,举例,`SSLv3_method`将强制使用SSL v3。可用的值取决于OpenSSL的安装和`SSL_METHODS`常量中被定义的值。
-
session: 一个`Buffer`实例,包含了TLS会话。
`callback`参数会被自动添加为`secureConnect`事件的监听器。
`tls.connect()`返回一个`tls.TLSSocket`对象。
以下是一个上述应答服务器的客户端的例子:
~~~
var tls = require('tls');
var fs = require('fs');
var options = {
// These are necessary only if using the client certificate authentication
key: fs.readFileSync('client-key.pem'),
cert: fs.readFileSync('client-cert.pem'),
// This is necessary only if the server uses the self-signed certificate
ca: [ fs.readFileSync('server-cert.pem') ]
};
var socket = tls.connect(8000, options, function() {
console.log('client connected',
socket.authorized ? 'authorized' : 'unauthorized');
process.stdin.pipe(socket);
process.stdin.resume();
});
socket.setEncoding('utf8');
socket.on('data', function(data) {
console.log(data);
});
socket.on('end', function() {
server.close();
});
~~~
或
~~~
var tls = require('tls');
var fs = require('fs');
var options = {
pfx: fs.readFileSync('client.pfx')
};
var socket = tls.connect(8000, options, function() {
console.log('client connected',
socket.authorized ? 'authorized' : 'unauthorized');
process.stdin.pipe(socket);
process.stdin.resume();
});
socket.setEncoding('utf8');
socket.on('data', function(data) {
console.log(data);
});
socket.on('end', function() {
server.close();
});
~~~
#### Class: tls.TLSSocket
`net.Socket`实例的包装,替换了内部`socket`的 读/写例程,来提供透明的对 传入/传出数据 的 加密/解密。
#### new tls.TLSSocket(socket, options)
根据已存在的TCP`socket`,构造一个新的`TLSSocket`对象。
`socket`是一个`net.Socket`实例。
`options`是一个可能包含以下属性的对象:
-
secureContext: 一个可选的通过`tls.createSecureContext( ... )`得到的TLS内容对象。
-
isServer: 如果为`true`,TLS `socket`将会在服务器模式(server-mode)下被初始化。
-
server: 一个可选的`net.Server`实例。
-
requestCert: 可选,参阅`tls.createSecurePair`。
-
rejectUnauthorized: 可选,参阅`tls.createSecurePair`。
-
NPNProtocols: 可选,参阅`tls.createServer`。
-
SNICallback: 可选,参阅`tls.createServer`。
-
session: 可选,一个`Buffer`实例,包含了TLS会话。
-
requestOCSP: 可选,如果为`true`,`OCSP`状态请求扩展将会被添加到客户端 hello,并且`OCSPResponse`事件将会在建立安全通信前,于`socket`上触发。
#### tls.createSecureContext(details)
创建一个证书对象,`details`有可选的以下值:
- pfx : 一个含有`PFX`或`PKCS12`编码的私钥,证书和CA证书的字符串或`buffer`。
- key : 一个含有`PEM`编码的私钥的字符串。
- passphrase : 一个私钥或`pfx`密码字符串。
- cert : 一个含有`PEM`加密证书的字符串。
- ca : 一个用来信任的`PEM`加密CA证书的字符串或字符串列表。
- crl : 一个`PEM`加密`CRL`的字符串或字符串列表。
- ciphers: 一个描述需要使用或排除的加密器的字符串。更多加密器的格式细节参阅`http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT`。
- honorCipherOrder : 选择一个加密器时,使用使用服务器的首选项而不是客户端的首选项。默认为`true`。更多细节参阅`tls`模块文档。
如果没有指定`ca`,那么`io.js`将会使用`http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt`提供的默认公共可信任CA列表。
#### tls.createSecurePair([context][, isServer][, requestCert][, rejectUnauthorized])
根据两个流,创建一个新的安全对(secure pair)对象,一个是用来读/写加密数据,另一个是用来读/写明文数据。通常加密的数据是从加密数据流被导流而来,明文数据被用来作为初始加密流的一个替代。
-
credentials: 一个通过`tls.createSecureContext( ... )`得到的安全内容对象。
-
isServer: 一个表明了 是否这个`tls`连接应被作为一个服务器或一个客户端打开 的布尔值。
-
requestCert: 一个表明了 是否服务器应该向连接的客户端请求证书 的布尔值。只应用于服务器连接。
-
rejectUnauthorized: 一个表明了 是否服务器应该拒绝包含不可用证书的客户端 的布尔值。只应用于启用了`requestCert`的服务器。
`tls.createSecurePair()`返回一个带有`cleartext`和 `encrypted`流 属性的对象。
注意:`cleartext`和`tls.TLSSocket`有相同的API。
#### Class: SecurePair
由`tls.createSecurePair`返回。
#### Event: 'secure'
当`SecurePair`成功建立一个安全连接时,`SecurePair`会触发这个事件
与检查服务器的`secureConnection`事件相似,`pair.cleartext.authorized`必须被检查,来确认证书是否使用了合适的授权。
#### Class: tls.Server
这是一个`net.Server`的子类,并且与其有相同的方法。除了只接受源TCP连接,这个类还接受通过TLS或SSL加密的数据。
#### Event: 'secureConnection'
- function (tlsSocket) {}
当一个新连接被成功握手后,这个事件会被触发。参数是一个`tls.TLSSocket`实例。它拥有所有普通流拥有的事件和方法。
`socket.authorized`是一个表明了 客户端是否通过提供的服务器CA来进行了认证 的布尔值。如果`socket.authorized`为`false`,那么`socket.authorizationError`将被设置用来描述授权失败的原因。一个不明显的但是值得提出的点:依靠TLS服务器的设定,未授权的连接可能会被接受。`socket.npnProtocol`是一个包含了被选择的NPN协议的字符串。`socket.servernam`是一个包含了通过SNI请求的服务器名的字符串。
#### Event: 'clientError'
- function (exception, tlsSocket) { }
当安全连接被建立之前,服务器触发了一个`error`事件 - 它会被转发到这里。
`tlsSocket`是错误来自的`tls.TLSSocket`。
#### Event: 'newSession'
- function (sessionId, sessionData, callback) { }
在TLS会话创建时触发。可能会被用来在外部存储会话。`callback`必须最终被执行,否则安全连接将不会收到数据。
注意:这个事件监听器只会影响到它被添加之后建立的连接。
#### Event: 'resumeSession'
- function (sessionId, callback) { }
当客户端想要恢复先前的TLS会话时触发。事件监听器可能会在外部通过`sessionId`来寻找会话,并且在结束后调用`callback(null, sessionData)`。如果会话不能被恢复(例如没有找到),可能会调用`callback(null, null)`。调用`callback(err)`会关闭将要到来的连接并且销毁`socket`。
注意:这个事件监听器只会影响到它被添加之后建立的连接。
#### Event: 'OCSPRequest'
- function (certificate, issuer, callback) { }
当客户端发送一个证书状态请求时触发。你可以解释服务器当前的证书来获取OCSP url和证书id,并且在获取了OCSP响应后执行`callback(null, resp)`,`resp`是一个`Buffer`实例。`certificate`和`issuer`都是一个`Buffer`,即主键和发起人证书的DER代表(DER-representations)。它们可以被用来获取OCSP证书id 和 OCSP末端url。
另外,`callback(null, null)`可以被调用,意味着没有OCSP响应。
调用`callback(err)`,将会导致调用`socket.destroy(err)`。
典型的流程:
1. 客户端连接到服务器,然后发送一个`OCSPRequest`给它(通过`ClientHello`中扩展的状态信息)。
1. 服务器接受请求,然后执行`OCSPRequest`事件监听器(如果存在)。
1. 服务器通过证书或发起人抓取OCSP url,然后向CA发起一个OCSP请求。
1. 服务器从CA收到一个`OCSPResponse`,然后通过回调函数的参数将其返回给客户端。
1. 客户端验证响应,然后销毁`socket`或者进行握手。
注意:`issuer`可以是`null`,如果证书是自签名的或`issuer`不在根证书列表之内(你可以通过`ca`参数提供一个`issuer`)。
注意:这个事件监听器只会影响到它被添加之后建立的连接。
注意:你可能想要使用一些如`asn1.js`的`npm`模块来解释证书。
#### server.listen(port[, hostname][, callback])
从指定的端口和主机名接收连接。如果`hostname`被忽略,服务器会在当IPv6可用时,接受任意IPv6地址(`::`)上的连接,否则为任意IPv4(`0.0.0.0`)上的。将`port`设置为`0`则会赋予其一个随机端口。
这个函数是异步的。最后一个参数`callback`会在服务器被绑定后执行。
更多信息请参阅`net.Server`。
#### server.close([callback])
阻止服务器继续接收新连接。这个函数是异步的,当服务器触发一个`close`事件时,服务器将最终被关闭。可选的,你可以传递一个回调函数来监听`close`事件。
#### server.address()
返回绑定的地址,服务器地址的协议族名和端口通过操作系统报告。更多信息请参阅`net.Server.address()`。
#### server.addContext(hostname, context)
添加安全内容,它将会在如果客户端请求的SNI主机名被传递的主机名匹配(可以使用通配符)时使用。`context`可以包含密钥,证书,CA 和/或 其他任何`tls.createSecureContext`的`options`参数的属性。
#### server.maxConnections
当服务器连接数变多时,设置这个值来拒绝连接。
#### server.connections
服务器上的当前连接数。
#### Class: CryptoStream
> 稳定度: 0 - 弃用。 使用`tls.TLSSocket`替代。
这是一个加密流。
#### cryptoStream.bytesWritten
一个底层`socket`的`bytesWritten`存取器的代理,它会返回写入`socket`的总字节数,包括TLS开销。
#### Class: tls.TLSSocket
这是一个`net.Socket`的包装,但是对写入的数据做了透明的加密,并且要求TLS协商。
这个实例实现了一个双工流接口。它有所有普通流所拥有的事件和方法。
#### Event: 'secureConnect'
在一个新连接成功握手后,这个事件被触发。无论服务器的证书被授权与否,这个监听器都会被调用。测试`tlsSocket.authorized`来 验证服务器证书是否被一个指定CA所签名 取决于用户。如果`tlsSocket.authorized === false`那么错误可以从`tlsSocket.authorizationError`里被发现。如果`NPN`被使用,你可以通过`tlsSocket.npnProtocol`来检查已协商协议。
#### Event: 'OCSPResponse'
- function (response) { }
如果`requestOCSP`选项被设置,这个事件会触发。`response`是一个`buffer`对象,包含了服务器的OCSP响应。
习惯上,`response`是一个来自服务器的CA(包含服务器的证书撤销状态)的已签名对象。
#### tlsSocket.encrypted
静态布尔变量,总是`true`。可能会被用来区分TLS `socket`和普通的`socket`。
#### tlsSocket.authorized
如果对等(peer)证书通过一个指定的CA被签名,那么这个值为`true`。否则为`false`。
#### tlsSocket.authorizationError
对等(peer)的证书没有被验证的原因。这个值只在`tlsSocket.authorized === false`时可用。
#### tlsSocket.getPeerCertificate([ detailed ])
返回了一个代表了对等证书的对象。返回的对象有一些属性与证书的属性一致。如果`detailed`参数被设置为`true`,`issuer`属性的完整链都会被返回,如果为`false`,只返回不包含`issuer`属性的顶端的证书。
例子:
~~~
{ subject:
{ C: 'UK',
ST: 'Acknack Ltd',
L: 'Rhys Jones',
O: 'io.js',
OU: 'Test TLS Certificate',
CN: 'localhost' },
issuerInfo:
{ C: 'UK',
ST: 'Acknack Ltd',
L: 'Rhys Jones',
O: 'io.js',
OU: 'Test TLS Certificate',
CN: 'localhost' },
issuer:
{ ... another certificate ... },
raw: < RAW DER buffer >,
valid_from: 'Nov 11 09:52:22 2009 GMT',
valid_to: 'Nov 6 09:52:22 2029 GMT',
fingerprint: '2A:7A:C2:DD:E5:F9:CC:53:72:35:99:7A:02:5A:71:38:52:EC:8A:DF',
serialNumber: 'B9B0D332A1AA5635' }
~~~
如果`peer`没有提供一个证书,那么会返回`null`或空对象。
#### tlsSocket.getCipher()
返回一个代表了当前连接的加密器名和SSL/TLS协议版本的对象。
例子: `{ name: 'AES256-SHA', version: 'TLSv1/SSLv3' }`
参阅`http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_CIPHERS`中`SSL_CIPHER_get_name()`和`SSL_CIPHER_get_version()`。
#### tlsSocket.renegotiate(options, callback)
初始化TLS重新协商过程。`optios`可以包含以下属性:`rejectUnauthorized`,`requestCert`(详情参阅`tls.createServer`)。一旦重协商成功,`callback(err)`会带着`err`为`null`执行。
注意:可以被用来请求对等(peer)证书在安全连接建立之后。
另一个注意点:当作为服务器运行时,`socekt`在`handshakeTimeout`超时后,会带着一个错误被销毁。
#### tlsSocket.setMaxSendFragment(size)
设置TLS碎片大小的最大值(默认最大值为`16384`,最小值为`512`)。若设置成功返回`true`,否则返回`false`。
更小的碎片大小来减少客户端的缓冲延迟:大的碎片通过TLS层缓冲,直到收到全部的碎片并且它的完整性被验证;大碎片可能会跨越多次通信,并且可能会被报文丢失和重新排序所延迟。但是,更小的碎片增加了额外的TLS框架字节和CPU开销,可能会减少总体的服务器负载。
#### tlsSocket.getSession()
返回`ASN.1`编码的TLS会话,如果没有被协商,返回`undefined`。可以被用在重新连接服务器时,加速握手的建立。
#### tlsSocket.getTLSTicket()
注意:仅在客户端TLS `socket`中工作。仅在调试时有用,因为会话重新使用了给`tls.connect`提供的`session`选项。
返回TLS会话门票(ticket),如果没有被协商,返回`undefined`。
#### tlsSocket.address()
返回绑定的地址,协议族名和端口由底层系统报告。返回一个含有三个属性的对象,例如:`{ port: 12346, family: 'IPv4', address: '127.0.0.1' }`。
#### tlsSocket.remoteAddress
代表了远程IP地址的字符串。例子:`'74.125.127.100'`或`'2001:4860:a005::68'`。
#### tlsSocket.remoteFamily
代表了远程IP协议族的字符串。`'IPv4'`或`'IPv6'`。
#### tlsSocket.remotePort
代表了远程端口数字。例子:`443`。
#### tlsSocket.localAddress
代表了本地IP地址的字符串。
#### tlsSocket.localPort
代表了本地端口的数字。
Timers
最后更新于:2022-04-01 01:59:08
### 稳定度: 3 - 锁定
所有的定时器函数都是全局的。当需要使用它们时,不必通过`require()`。
#### setTimeout(callback, delay[, arg][, ...])
在指定的延时(毫秒)后执行一次回调函数。返回一个可以被调用`clearTimeout()`的`timeoutObject`。可选的,你可以传递回调函数的参数。
需要注意的是,你的回调函数可以不会在精确的在指定的毫秒延时后执行 - `io.js`对回调函数执行的精确时间以及顺序都不作保证。回调函数的执行点会尽量接近指定的延时。
#### clearTimeout(timeoutObject)
阻止一个`timeout`的触发。
#### setInterval(callback, delay[, arg][, ...])
在每次到达了指定的延时后,都重复执行回调函数。返回一个可以被调用`clearInterval()`的`intervalObject`。可选的,你可以传递回调函数的参数。
#### clearInterval(intervalObject)
阻止一个`interval`的触发。
#### unref()
`setTimeout`和`setInterval`的返回值也有一个`timer.unref()`方法,这个方法允许你创建一个 当它是事件循环中的仅剩项时,它不会保持程序继续运行 的定时器。如果一个定时器已经被`unref`,再次调用`unref`不会有任何效果。
在`setTimeout`的情况下,当你调用`unref`时,你创建了一个将会唤醒事件循环的另一个定时器。创建太多这样的定时器会影响时间循环的性能 -- 请明智地使用。
#### ref()
如果你先前对一个定时器调用了`unref()`,你可以调用`ref()`来明确要求定时器要保持程序运行。如果一个定时器已经被`ref`,再次调用`ref`不会有任何效果。
#### setImmediate(callback[, arg][, ...])
在下一次I/O事件循环后,在`setTimeout`和`setInterval`前,“立刻”执行回调函数。返回一个可以被`clearImmediate()`的`immediateObject`。可选的,你可以传递回调函数的参数。
由`setImmediate`创建的回调函数会被有序地排队。每一次事件循环迭代时,整个回调函数队列都会被处理。如果你在一个执行中的回调函数里调用了`setImmediate`,那么这个`setImmediate`中的回调函数会在下一次事件循环迭代时被调用。
#### clearImmediate(immediateObject)
阻止一个`immediate`的触发。
String Decoder
最后更新于:2022-04-01 01:59:06
### 稳定度: 2 - 稳定
通过`require('string_decoder')`来使用这个模块。`StringDecoder`解码一个`buffer`为一个字符串。它是一个`buffer.toString()`的简单接口,但是提供了utf8的额外支持。
~~~
var StringDecoder = require('string_decoder').StringDecoder;
var decoder = new StringDecoder('utf8');
var cent = new Buffer([0xC2, 0xA2]);
console.log(decoder.write(cent));
var euro = new Buffer([0xE2, 0x82, 0xAC]);
console.log(decoder.write(euro));
~~~
#### Class: StringDecoder
接受一个单独的参数,即编码,默认为utf8。
#### decoder.write(buffer)
返回被解码的字符串。
#### decoder.end()
返回遗留在`buffer`中的所有末端字节。
Stream
最后更新于:2022-04-01 01:59:03
### 稳定度: 2 - 稳定
流是一个被`io.js`内部的许多对象所实现的抽象接口。例如一个发往HTTP服务器的请求是一个留,`stdout`也是一个流。流可以是可读的,可写的或双向的。所有的流都是`EventEmitter`实例。
你可以通过`require('stream')`来取货`Stream`的基类。其中包括了`Readable`流,`Writable`流,`Duplex`流和`Transform`流的基类。
此文档分为三个章节。第一章节解释了在你的编程中使用流时需要的API。如果你不需要实现你自己的流式API,你可以在这里停止。
第二章节解释了你在构建你自己的流时需要的API,这些API是为了方便你这么做而设计的。
第三章节深入讲述了流的工作机制,包括一些内部的机制和函数,你不应该去改动它们除非你知道你在做什么。
### 面向流消费者的API
流可以是可读的,可写的,或双工的。
所有的流都是`EventEmitters`。但是它们也各自有一些独特的方法和属性,这取决于它们是可读流,可写流或双工流。
如果一个流同时是可读的和可写的,那么表示它实现了以下所有的方法和事件。所以,这些API同时也涵盖`Duplex`或`Transform`流,即使它们的实现可能有些不同。
在你程序中,为了消费流而去实现流接口不是必须的。如果你确实正在你的程序中实现流接口,请参考下一章节`面向流实现者的API`。
几乎所有`io.js`程序,不论多简单,都使用了流。下面是一个在`io.js`是使用流的例子:
~~~
var http = require('http');
var server = http.createServer(function (req, res) {
// req is an http.IncomingMessage, which is a Readable Stream
// res is an http.ServerResponse, which is a Writable Stream
var body = '';
// we want to get the data as utf8 strings
// If you don't set an encoding, then you'll get Buffer objects
req.setEncoding('utf8');
// Readable streams emit 'data' events once a listener is added
req.on('data', function (chunk) {
body += chunk;
});
// the end event tells you that you have entire body
req.on('end', function () {
try {
var data = JSON.parse(body);
} catch (er) {
// uh oh! bad json!
res.statusCode = 400;
return res.end('error: ' + er.message);
}
// write back something interesting to the user:
res.write(typeof data);
res.end();
});
});
server.listen(1337);
// $ curl localhost:1337 -d '{}'
// object
// $ curl localhost:1337 -d '"foo"'
// string
// $ curl localhost:1337 -d 'not json'
// error: Unexpected token o
~~~
#### Class: stream.Readable
可读流接口是一个你可以从之读取数据的数据源的抽象。换句话说,数据从可读流而来。
除非你指示已经准备好接受数据,否则可读流不会开始发生数据。
可读流有两个“模式”:流动模式和暂停模式。当在流动模式时,数据由底层系统读出,并且会尽快地提供给你的程序。当在暂停模式时,你必须调用`stream.read()`方法来获取数据块。流默认是暂停模式。
注意:如果`data`事件没有被绑定监听器,并且没有导流(pipe)目标,并且流被切换到了流动模式,那么数据将会被丢失。
你可以通过下面任意一个做法切换到流动模式:
-
添加一个`data`事件的监听器来监听数据。
-
调用`resume()`方法来明确开启流动模式。
-
调用`pipe()`方法将数据导入一个可写流。
你可以同意下面任意一种方法切换回暂停模式:
-
如果没有导流(pipe)目标,调用`pause()`方法。
-
如果有导流(pipe)目标,移除所有的`data`事件监听器,并且通过`unpipe()`方法移除所有导流目标。
注意,由于为了向后兼任的原因,移除`data`事件的监听器将不会自动暂停流。同样的,如果有导流目标,调用`pause()`方法将不会保证目标流排空并请求更多数据时保持暂停。
一些内置的可读流例子:
- 客户端的HTTP请求
- 服务端的HTTP响应
- 文件系统读取流
- `zlib`流
- `crypto`流
- tcp sockets
- 子进程的stdout和stderr
- `process.stdin`
#### Event: 'readable'
当一个数据块能可以从流中被读出时,会触发一个`readable`事件。
某些情况下,监听一个`readable`事件会导致一些将要被读出的数据从底层系统进入内部缓冲,如果它没有准备好。
~~~
var readable = getReadableStreamSomehow();
readable.on('readable', function() {
// there is some data to read now
});
~~~
当内部缓冲被排空时,一旦有更多数据,`readable`事件会再次触发。
#### Event: 'data'
- chunk Buffer | String 数据块
为一个没有被暂停的流添加一个`data`事件的监听器会使其切换到流动模式。之后数据会被尽快得传递给用户。
如果你只是想尽快得从流中取得所有数据,这是最好的方式。
~~~
var readable = getReadableStreamSomehow();
readable.on('data', function(chunk) {
console.log('got %d bytes of data', chunk.length);
});
~~~
#### Event: 'end'
当没有更多可读的数据时这个事件会被触发。
注意,除非数据被完全消费,`end`事件才会触发。这可以通过切换到流动模式,或重复调用`read()`方法。
~~~
var readable = getReadableStreamSomehow();
readable.on('data', function(chunk) {
console.log('got %d bytes of data', chunk.length);
});
readable.on('end', function() {
console.log('there will be no more data.');
});
~~~
#### Event: 'close'
当底层资源(如源头的文件描述符)被关闭时触发。不是所有的流都会触发这个事件。
#### Event: 'error'
- Error Object
当接受数据时有错误发生,会触发此事件。
#### readable.read([size])
- size Number 可选,指定读取数据的数量
- Return String | Buffer | null
`read()`方法从内部缓冲中取出数据并返回它。如果没有可用数据,那么将返回`null`。
如果你传递了一个`size`参数,那么它将返回指定字节的数据。如果`size`参数的字节数不可用,那么将返回`null`。
如果你不指定`size`参数,那么将会返回内部缓冲中的所有数据。
这个方法只能在暂定模式中被调用。在流动模式下,这个方法会被自动地重复调用,知道内部缓冲被排空。
~~~
var readable = getReadableStreamSomehow();
readable.on('readable', function() {
var chunk;
while (null !== (chunk = readable.read())) {
console.log('got %d bytes of data', chunk.length);
}
});
~~~
如果这个方法返回一个数据块,那么它也会触发`data`事件。
#### readable.setEncoding(encoding)
- encoding String 使用的编码
- Return: this
调用这个函数会导致流返回指定编码的字符串而不是`Buffer`对象。例如,如果你调用`readable.setEncoding('utf8')`,那么输出的数据将被解释为UTF-8数据,并且作为字符串返回。如果你调用了`readable.setEncoding('hex')`,那么数据将被使用十六进制字符串的格式编码。
该方法可以正确地处理多字节字符。如果你只是简单地直接取出缓冲并且对它们调用`buf.toString(encoding)`,将会导致错位。如果你想使用字符串读取数据,请使用这个方法。
~~~
var readable = getReadableStreamSomehow();
readable.setEncoding('utf8');
readable.on('data', function(chunk) {
assert.equal(typeof chunk, 'string');
console.log('got %d characters of string data', chunk.length);
});
~~~
#### readable.resume()
- Return: this
这个方法将会让可读流继续触发`data`事件。
这个方法将会使流切换至流动模式。如果你不想消费流中的数据,但你想监听它的`end`事件,你可以通过调用`readable.resume()`来打开数据流。
~~~
var readable = getReadableStreamSomehow();
readable.resume();
readable.on('end', function() {
console.log('got to the end, but did not read anything');
});
~~~
#### readable.pause()
- Return: this
这个方法会使一个处于流动模式的流停止触发`data`事件,并切换至暂停模式。所有可用的数据将仍然存在于内部缓冲中。
~~~
var readable = getReadableStreamSomehow();
readable.on('data', function(chunk) {
console.log('got %d bytes of data', chunk.length);
readable.pause();
console.log('there will be no more data for 1 second');
setTimeout(function() {
console.log('now data will start flowing again');
readable.resume();
}, 1000);
});
~~~
#### readable.isPaused()
- Return: Boolean
这个方法会返回流是否被客户端代码所暂停(调用`readable.pause()`,并且没有在之后调用`readable.resume()`)。
~~~
var readable = new stream.Readable
readable.isPaused() // === false
readable.pause()
readable.isPaused() // === true
readable.resume()
readable.isPaused() // === false
~~~
#### readable.pipe(destination[, options])
- destination Writable Stream 写入数据的目标
- **options Object**
- end Boolean 当读取者结束时结束写入者。默认为`true`。
这个方法会取出可读流中所有的数据,并且将之写入指定的目标。这个方法会自动调节流量,所以当快速读取可读流时目标不会溢出。
可以将数据安全地导流至多个目标。
~~~
var readable = getReadableStreamSomehow();
var writable = fs.createWriteStream('file.txt');
// All the data from readable goes into 'file.txt'
readable.pipe(writable);
~~~
这个函数返回目标流,所以你可以链式调用`pipe()`:
~~~
var r = fs.createReadStream('file.txt');
var z = zlib.createGzip();
var w = fs.createWriteStream('file.txt.gz');
r.pipe(z).pipe(w);
~~~
例子,模仿UNIX的`cat`命令:
~~~
process.stdin.pipe(process.stdout);
~~~
默认情况下,当源流触发`end`事件时,目标流会被调用`end()`方法,然后目标就不再是可写的了。将传递`{ end: false }`作为`options`参数,将保持目标流开启。
例子,保持被写入的流开启,所以“Goodbye”可以在末端被写入:
~~~
reader.pipe(writer, { end: false });
reader.on('end', function() {
writer.end('Goodbye\n');
});
~~~
注意,不论指定任何`options`参数,`process.stderr`和`process.stdout`在程序退出前永远不会被关闭。
#### readable.unpipe([destination])
- destination Writable Stream 可选,指定解除导流的流
这方法会移除之前调用`pipe()`方法所设置的钩子。
如果没有指定目标,那么所有的导流都会被移除。
如果指定了目标,但是并没有为目标设置导流,那么什么都不会发生。
~~~
var readable = getReadableStreamSomehow();
var writable = fs.createWriteStream('file.txt');
// All the data from readable goes into 'file.txt',
// but only for the first second
readable.pipe(writable);
setTimeout(function() {
console.log('stop writing to file.txt');
readable.unpipe(writable);
console.log('manually close the file stream');
writable.end();
}, 1000);
~~~
#### readable.unshift(chunk)
- chunk Buffer | String 要插回读取队列开头的数据块。
该方法在许多场景中都很有用,比如一个流正在被一个解析器消费,解析器可能需要将某些刚拉取出的数据“逆消费”回来源,以便流能将它传递给其它消费者。
如果你发现你必须经常在你的程序中调用`stream.unshift(chunk)`,你应该考虑实现一个`Transform`流(参阅下文的面向流实现者的API)。
~~~
// Pull off a header delimited by \n\n
// use unshift() if we get too much
// Call the callback with (error, header, stream)
var StringDecoder = require('string_decoder').StringDecoder;
function parseHeader(stream, callback) {
stream.on('error', callback);
stream.on('readable', onReadable);
var decoder = new StringDecoder('utf8');
var header = '';
function onReadable() {
var chunk;
while (null !== (chunk = stream.read())) {
var str = decoder.write(chunk);
if (str.match(/\n\n/)) {
// found the header boundary
var split = str.split(/\n\n/);
header += split.shift();
var remaining = split.join('\n\n');
var buf = new Buffer(remaining, 'utf8');
if (buf.length)
stream.unshift(buf);
stream.removeListener('error', callback);
stream.removeListener('readable', onReadable);
// now the body of the message can be read from the stream.
callback(null, header, stream);
} else {
// still reading the header.
header += str;
}
}
}
}
~~~
#### readable.wrap(stream)
- stream Stream 一个“旧式”可读流
`Node.js` v0.10 以及之前版本的流没有完全包含如今的所有的流API(更多的信息请参阅下文的“兼容性”)。
如果你正在使用一个老旧的`io.js`库,它触发`data`时间并且有一个仅作查询用途的`pause()`方法,那么你可以调用`wrap()`方法来创建一个使用“旧式”流作为数据源的可读流。
你几乎不会用到这个函数,它的存在仅是为了老旧的`io.js`程序和库交互。
例子:
~~~
var OldReader = require('./old-api-module.js').OldReader;
var oreader = new OldReader;
var Readable = require('stream').Readable;
var myReader = new Readable().wrap(oreader);
myReader.on('readable', function() {
myReader.read(); // etc.
});
~~~
#### Class: stream.Writable
可写流接口是一个你可以向其写入数据的目标的抽象。
一些内部的可写流例子:
- 客户端的http请求
- 服务端的http响应
- 文件系统写入流
- `zlib`流
- `crypto`流
- tcp `socket`
- 子进程`stdin`
- `process.stdout`,`process.stderr`
#### writable.write(chunk[, encoding][, callback])
- chunk String | Buffer 要写入的数据
- encoding String 编码,如果数据块是字符串
- callback Function 当数据块写入完毕后调用的回调函数
- Returns: Boolean 如果被全部处理则返回`true`
该方法向底层系统写入数据,并且当数据被全部处理后调用指定的回调函数。
返回值指示了你是否可以立刻写入数据。如果数据需要被内部缓冲,会返回`false`。否则返回`true`。
返回值经供参考。即使返回`false`,你仍可以继续写入数据。但是,写入的数据将会被缓冲在内存里,所以最好不要这样做。应该在写入更多数据前等待`drain`事件。
#### Event: 'drain'
如果一个`writable.write(chunk)`调用返回了`false`,那么`drain`事件会指示出可以继续向流写入数据的时机。
~~~
// Write the data to the supplied writable stream 1MM times.
// Be attentive to back-pressure.
function writeOneMillionTimes(writer, data, encoding, callback) {
var i = 1000000;
write();
function write() {
var ok = true;
do {
i -= 1;
if (i === 0) {
// last time!
writer.write(data, encoding, callback);
} else {
// see if we should continue, or wait
// don't pass the callback, because we're not done yet.
ok = writer.write(data, encoding);
}
} while (i > 0 && ok);
if (i > 0) {
// had to stop early!
// write some more once it drains
writer.once('drain', write);
}
}
}
~~~
#### writable.cork()
强制滞留所有写入。
滞留的数据会在调用`.uncork()`或`.end()`方法后被写入。
#### writable.uncork()
写入在调用`.cork()`方法所有被滞留的数据。
#### writable.setDefaultEncoding(encoding)
- encoding String 新的默认编码
设置一个可写流的默认编码。
#### writable.end([chunk][, encoding][, callback])
- chunk String | Buffer 可选,写入的数据
- encoding String 编码,如果数据块是字符串
- callback Function 可选,回调函数
当没有更多可写的数据时,调用这个方法。如果指定了回调函数,那么会被添加为`finish`事件的监听器。
在调用了`end()`后调用`write()`会导致一个错误。
~~~
// write 'hello, ' and then end with 'world!'
var file = fs.createWriteStream('example.txt');
file.write('hello, ');
file.end('world!');
// writing more now is not allowed!
~~~
#### Event: 'finish'
当调用了`end()`方法,并且所有的数据都被写入了底层系统,这个事件会被触发。
~~~
var writer = getWritableStreamSomehow();
for (var i = 0; i < 100; i ++) {
writer.write('hello, #' + i + '!\n');
}
writer.end('this is the end\n');
writer.on('finish', function() {
console.error('all writes are now complete.');
});
~~~
#### Event: 'pipe'
- src Readable Stream 对这个可写流进行导流的源可读流
这个事件将会在可读流被一个可写流使用`pipe()`方法进行导流时触发。
~~~
var writer = getWritableStreamSomehow();
var reader = getReadableStreamSomehow();
writer.on('pipe', function(src) {
console.error('something is piping into the writer');
assert.equal(src, reader);
});
reader.pipe(writer);
~~~
#### Event: 'unpipe'
- src Readable Stream 对这个可写流停止导流的源可读流
当可读流对其调用`unpipe()`方法,在源可读流的目标集合中删除这个可写流,这个事件将会触发。
~~~
var writer = getWritableStreamSomehow();
var reader = getReadableStreamSomehow();
writer.on('unpipe', function(src) {
console.error('something has stopped piping into the writer');
assert.equal(src, reader);
});
reader.pipe(writer);
reader.unpipe(writer);
~~~
#### Event: 'error'
- Error object
在写入数据或导流发生错误时触发。
#### Class: stream.Duplex
双工是同时实现了可读流与可写流的借口。它的用处请参阅下文。
内部双工流的例子:
- tcp `socket`
- `zlib`流
- `crypto`流
#### Class: stream.Transform
转换流是一种输出由输入计算所得的栓共流。它们同时集成了可读流与可写流的借口。它们的用处请参阅下文。
内部转换流的例子:
- `zlib`流
- `crypto`流
### 面向流实现者的API
实现所有种类的流的模式都是一样的:
1. 为你的子类继承合适的父类(`util.inherits`非常合适于做这个)。
1. 为了保证内部机制被正确初始化,在你的构造函数中调用合适的父类构造函数。
1. 实现一个或多个特定的方法,参阅下文。
被扩展的类和要实现的方法取决于你要编写的流类的类型:
| 用途 | 类 | 需要实现的方法 |
|-----|-----|-----|
| 只读 | Readable | _read |
| 只写 | Writable | _write, _writev |
| 可读以及可写 | Duplex | _read, _write, _writev |
| 操作被写入数据,然后读出结果 | Transform | _transform, _flush |
在你的实现代码中,非常重要的一点是永远不要调用上文的面向流消费者的API。否则,你在程序中消费你的流接口时可能有潜在的副作用。
#### Class: stream.Readable
`stream.Readable`是一个被设计为需要实现底层的`_read(size)`方法的抽象类。
请参阅上文的面向流消费者的API来了解如何在程序中消费流。以下解释了如果在你的程序中实现可读流。
例子:一个计数流
这是一个可读流的基础例子。它从1到1,000,000递增数字,然后结束。
~~~
var Readable = require('stream').Readable;
var util = require('util');
util.inherits(Counter, Readable);
function Counter(opt) {
Readable.call(this, opt);
this._max = 1000000;
this._index = 1;
}
Counter.prototype._read = function() {
var i = this._index++;
if (i > this._max)
this.push(null);
else {
var str = '' + i;
var buf = new Buffer(str, 'ascii');
this.push(buf);
}
};
~~~
例子:简单协议 v1 (次优)
这类似于上文中提到的`parseHeader`函数,但是使用一个自定义流实现。另外,注意这个实现不将流入的数据转换为字符串。
更好地实现是作为一个转换流实现,请参阅下文更好地实现。
~~~
// A parser for a simple data protocol.
// The "header" is a JSON object, followed by 2 \n characters, and
// then a message body.
//
// NOTE: This can be done more simply as a Transform stream!
// Using Readable directly for this is sub-optimal. See the
// alternative example below under the Transform section.
var Readable = require('stream').Readable;
var util = require('util');
util.inherits(SimpleProtocol, Readable);
function SimpleProtocol(source, options) {
if (!(this instanceof SimpleProtocol))
return new SimpleProtocol(source, options);
Readable.call(this, options);
this._inBody = false;
this._sawFirstCr = false;
// source is a readable stream, such as a socket or file
this._source = source;
var self = this;
source.on('end', function() {
self.push(null);
});
// give it a kick whenever the source is readable
// read(0) will not consume any bytes
source.on('readable', function() {
self.read(0);
});
this._rawHeader = [];
this.header = null;
}
SimpleProtocol.prototype._read = function(n) {
if (!this._inBody) {
var chunk = this._source.read();
// if the source doesn't have data, we don't have data yet.
if (chunk === null)
return this.push('');
// check if the chunk has a \n\n
var split = -1;
for (var i = 0; i < chunk.length; i++) {
if (chunk[i] === 10) { // '\n'
if (this._sawFirstCr) {
split = i;
break;
} else {
this._sawFirstCr = true;
}
} else {
this._sawFirstCr = false;
}
}
if (split === -1) {
// still waiting for the \n\n
// stash the chunk, and try again.
this._rawHeader.push(chunk);
this.push('');
} else {
this._inBody = true;
var h = chunk.slice(0, split);
this._rawHeader.push(h);
var header = Buffer.concat(this._rawHeader).toString();
try {
this.header = JSON.parse(header);
} catch (er) {
this.emit('error', new Error('invalid simple protocol data'));
return;
}
// now, because we got some extra data, unshift the rest
// back into the read queue so that our consumer will see it.
var b = chunk.slice(split);
this.unshift(b);
// and let them know that we are done parsing the header.
this.emit('header', this.header);
}
} else {
// from there on, just provide the data to our consumer.
// careful not to push(null), since that would indicate EOF.
var chunk = this._source.read();
if (chunk) this.push(chunk);
}
};
// Usage:
// var parser = new SimpleProtocol(source);
// Now parser is a readable stream that will emit 'header'
// with the parsed header data.
~~~
#### new stream.Readable([options])
- **options Object**
- highWaterMark Number 在停止从底层资源读取之前,在内部缓冲中存储的最大字节数。默认为16kb,对于`objectMode`则是16
- encoding String 如果被指定,那么缓冲将被利用指定编码解码为字符串,默认为`null`
- objectMode Boolean 是否该流应该表现如一个对象的流。意思是说`stream.read(n)`返回一个单独的对象而不是一个大小为`n`的`Buffer`,默认为`false`
在实现了`Readable`类的类中,请确保调用了`Readable`构造函数,这样缓冲设置才能被正确的初始化。
#### readable._read(size)
- size Number 异步读取数据的字节数
注意:实现这个函数,而不要直接调用这个函数。
这个函数不应该被直接调用。它应该被子类实现,并且仅被`Readable`类的内部方法调用。
所有的可读流都必须实现这个方法用来从底层资源中获取数据。
这个函数有一个下划线前缀,因为它对于类是内部的,并应该直接被用户的程序调用。你应在你的拓展类里覆盖这个方法。
当数据可用时,调用`readable.push(chunk)`方法将之推入读取队列。如果方法返回`false`,那么你应当停止读取。当`_read`方法再次被调用,你应当推入更多数据。
参数`size`仅作查询。“read”调用返回数据的实现可以通过这个参数来知道应当抓取多少数据;其余与之无关的实现,比如TCP或TLS,则可忽略这个参数,并在可用时返回数据。例如,没有必要“等到”`size`个字节可用时才调用`stream.push(chunk)`。
#### readable.push(chunk[, encoding])
- chunk Buffer | null | String 被推入读取队列的数据块
- encoding String 字符串数据块的编码。必须是一个合法的`Buffer`编码,如'utf8'或'ascii'
- return Boolean 是否应该继续推入
注意:这个函数应该被`Readable`流的实现者调用,而不是消费者。
`_read()`函数在至少调用一次`push(chunk)`方法前,不会被再次调用。
`Readable`类通过在`readable`事件触发时,调用`read()`方法将数据推入 之后用于读出数据的读取队列 来工作。
`push()`方法需要明确地向读取队列中插入数据。如果它的参数为`null`,那么它将发送一个数据结束信号(`EOF`)。
这个API被设计为尽可能的灵活。例如,你可能正在包装一个有`pause/resume`机制和一个数据回调函数的低级别源。那那些情况下,你可以通过以下方式包装这些低级别源:
~~~
// source is an object with readStop() and readStart() methods,
// and an `ondata` member that gets called when it has data, and
// an `onend` member that gets called when the data is over.
util.inherits(SourceWrapper, Readable);
function SourceWrapper(options) {
Readable.call(this, options);
this._source = getLowlevelSourceObject();
var self = this;
// Every time there's data, we push it into the internal buffer.
this._source.ondata = function(chunk) {
// if push() returns false, then we need to stop reading from source
if (!self.push(chunk))
self._source.readStop();
};
// When the source ends, we push the EOF-signaling `null` chunk
this._source.onend = function() {
self.push(null);
};
}
// _read will be called when the stream wants to pull more data in
// the advisory size argument is ignored in this case.
SourceWrapper.prototype._read = function(size) {
this._source.readStart();
};
~~~
#### Class: stream.Writable
`stream.Writable`是一个被设计为需要实现底层的`_write(chunk, encoding, callback)`方法的抽象类。
请参阅上文的面向流消费者的API来了解如何在程序中消费流。以下解释了如果在你的程序中实现可写流。
#### new stream.Writable([options])
- **options Object**
- highWaterMark Number `write()`方法开始返回`false`的缓冲级别。默认为16kb,对于`objectMode`流则是`16`
- decodeStrings Boolean 是否在传递给`write()`方法前将字符串解码成`Buffer`。默认为`true`
- objectMode Boolean 是否`write(anyObj)`为一个合法操作。如果设置为`true`你可以写入任意数据而不仅是`Buffer`或字符串数据。默认为`false`
在实现了`Writable`类的类中,请确保调用了`Writable`构造函数,这样缓冲设置才能被正确的初始化。
#### writable._write(chunk, encoding, callback)
- chunk Buffer | String 将要被写入的数据块。除非`decodeStrings`配置被设置为`false`,否则将一直是一个`buffer`
- encoding String 如果数据块是一个字符串,那么这就是编码的类型。如果是一个`buffer`,那么则会忽略它
- callback Function 当你处理完给定的数据块后调用这个函数
所有的`Writable`流的实现都必须提供一个`_write()`方法来给底层资源传输数据。
这个函数不应该被直接调用。它应该被子类实现,并且仅被`Writable`类的内部方法调用。
回调函数使用标准的`callback(error)`模式来表示这个写操作成功或发生了错误。
如果构造函数选项中设置了`decodeStrings`标志,那么数据块将是一个字符串而不是一个`Buffer`,编码将会决定字符串的类型。这个是为了帮助处理编码字符串的实现。如果你没有明确地将`decodeStrings`选项设为`false`,那么你会安全地忽略`encoding`参数,并且数据块是`Buffer`形式。
这个函数有一个下划线前缀,因为它对于类是内部的,并应该直接被用户的程序调用。你应在你的拓展类里覆盖这个方法。
#### writable._writev(chunks, callback)
- chunks Array 将被写入的数据块数组。其中每一个数据都有如下格式:`{ chunk: ..., encoding: ... }`
- callback Function 当你处理完给定的数据块后调用这个函数
注意:这个函数不应该被直接调用。它应该被子类实现,并且仅被`Writable`类的内部方法调用。
这个函数对于你的实现是完全可选的。大多数情况下它是不必的。如果实现,它会被以所有滞留在写入队列中的数据块调用。
#### Class: stream.Duplex
一个“双工”流既是可读的,又是可写的。如TCP`socket`连接。
注意,和你实现`Readable`或`Writable`流时一样,`stream.Duplex`是一个被设计为需要实现底层的`_read(size)`和`_write(chunk, encoding, callback)`方法的抽象类。
由于`JavaScript`并不具备多继承能力,这个类是继承于`Readable`类,并寄生于`Writable`类。所以为了实现这个类,用户需要同时实现低级别的`_read(n)`方法和低级别的`_write(chunk, encoding, callback)`方法。
#### new stream.Duplex(options)
- **options Object** 同时传递给`Writable`和`Readable`构造函数。并且包含以下属性:
- allowHalfOpen Boolean 默认为`true`。如果设置为`false`,那么流的可读的一端结束时可写的一端也会自动结束,反之亦然。
- readableObjectMode Boolean 默认为`false`,为流的可读的一端设置`objectMode`。当`objectMode`为`true`时没有效果。
- writableObjectMode Boolean 默认为`false`,为流的可写的一端设置`objectMode`。当`objectMode`为`true`时没有效果。
在实现了`Duplex`类的类中,请确保调用了`Duplex`构造函数,这样缓冲设置才能被正确的初始化。
#### Class: stream.Transform
“转换”流是一个输出于输入存在对应关系的双工流,如一个`zilib`流或一个`crypto`流。
输出和输出并不需要有相同的大小,相同的数据块数或同时到达。例如,一个哈希流只有一个单独数据块的输出当输入结束时。一个`zlib`流的输出比其输入小得多或大得多。
除了实现`_read()`方法和`_write()`方法,转换流还必须实现`_transform()`方法,并且可选地实现`_flush()`方法(参阅下文)。
#### new stream.Transform([options])
- options Object 同时传递给`Writable`和`Readable`构造函数。
在实现了`Transform`类的类中,请确保调用了`Transform`构造函数,这样缓冲设置才能被正确的初始化。
#### transform._transform(chunk, encoding, callback)
- chunk Buffer | String 将要被写入的数据块。除非`decodeStrings`配置被设置为`false`,否则将一直是一个`buffer`
- encoding String 如果数据块是一个字符串,那么这就是编码的类型。如果是一个buffer,那么则会忽略它
- callback Function 当你处理完给定的数据块后调用这个函数
这个函数不应该被直接调用。它应该被子类实现,并且仅被`Transform`类的内部方法调用。
所有`Transform`流的实现都必须提供一个`_transform`方法来接受输入和产生输出。
在`Transform`类中,`_transform`可以做需要做的任何事,如处理需要写入的字节,将它们传递给可写端,异步I/O,等等。
调用`transform.push(outputChunk)`0次或多次来从输入的数据块产生输出,取决于你想从这个数据块中输出多少数据作为结果。
仅当目前的数据块被完全消费后,才会调用回调函数。注意,对于某些特殊的输入可能会没有输出。如果你将数据作为第二个参数传入回调函数,那么数据将被传递给`push`方法。换句话说,下面的两个例子是相等的:
~~~
transform.prototype._transform = function (data, encoding, callback) {
this.push(data);
callback();
}
transform.prototype._transform = function (data, encoding, callback) {
callback(null, data);
}
~~~
这个函数有一个下划线前缀,因为它对于类是内部的,并应该直接被用户的程序调用。你应在你的拓展类里覆盖这个方法。
#### transform._flush(callback)
- callback Function 当你排空了所有剩余数据后,这个回调函数会被调用
注意:这个函数不应该被直接调用。它应该被子类实现,并且仅被`Transform`类的内部方法调用。
在一些情景中,你的转换操作需要在流的末尾多发生一点点数据。例如,一个`Zlib`压缩流会存储一些内部状态以便它能优化压缩输出。但是在最后,它需要尽可能好得处理这些留下的东西来使数据完整。
在这种情况中,您可以实现一个`_flush`方法,它会在最后被调用,在所有写入数据被消费、但在触发`end`表示可读端到达末尾之前。和`_transform`一样,只需在写入操作完成时适当地调用`transform.push(chunk)`零或多次。
这个函数有一个下划线前缀,因为它对于类是内部的,并应该直接被用户的程序调用。你应在你的拓展类里覆盖这个方法。
#### Events: 'finish' 和 'end'
`finish`和`end`事件分别来自于父类`Writable`和`Readable`。`finish`事件在`end()`方法被调用以及所有的输入被`_transform`方法处理后触发。`end`事件在所有的在`_flush`方法的回调函数被调用后的数据被输出后触发。
#### Example: SimpleProtocol 解释器 v2
上文中的简单协议解释器可以简单地通过高级别的`Transform`流更好地实现。与上文例子中的`parseHeader`和`SimpleProtocol v1`相似。
在这个例子中,没有从参数中提供输入,然后将它导流至解释器中,这更符合`io.js`的使用习惯。
~~~
var util = require('util');
var Transform = require('stream').Transform;
util.inherits(SimpleProtocol, Transform);
function SimpleProtocol(options) {
if (!(this instanceof SimpleProtocol))
return new SimpleProtocol(options);
Transform.call(this, options);
this._inBody = false;
this._sawFirstCr = false;
this._rawHeader = [];
this.header = null;
}
SimpleProtocol.prototype._transform = function(chunk, encoding, done) {
if (!this._inBody) {
// check if the chunk has a \n\n
var split = -1;
for (var i = 0; i < chunk.length; i++) {
if (chunk[i] === 10) { // '\n'
if (this._sawFirstCr) {
split = i;
break;
} else {
this._sawFirstCr = true;
}
} else {
this._sawFirstCr = false;
}
}
if (split === -1) {
// still waiting for the \n\n
// stash the chunk, and try again.
this._rawHeader.push(chunk);
} else {
this._inBody = true;
var h = chunk.slice(0, split);
this._rawHeader.push(h);
var header = Buffer.concat(this._rawHeader).toString();
try {
this.header = JSON.parse(header);
} catch (er) {
this.emit('error', new Error('invalid simple protocol data'));
return;
}
// and let them know that we are done parsing the header.
this.emit('header', this.header);
// now, because we got some extra data, emit this first.
this.push(chunk.slice(split));
}
} else {
// from there on, just provide the data to our consumer as-is.
this.push(chunk);
}
done();
};
// Usage:
// var parser = new SimpleProtocol();
// source.pipe(parser)
// Now parser is a readable stream that will emit 'header'
// with the parsed header data.
~~~
#### Class: stream.PassThrough
这是一个`Transform`流的实现。将输入的流简单地传递给输出。它的主要目的是用来演示和测试,但它在某些需要构建特殊流的情况下可能有用。
### 简化的构造器API
可以简单的构造流而不使用继承。
这可以通过调用合适的方法作为构造函数和参数来实现:
例子:
#### Readable
~~~
var readable = new stream.Readable({
read: function(n) {
// sets this._read under the hood
}
});
~~~
#### Writable
~~~
var writable = new stream.Writable({
write: function(chunk, encoding, next) {
// sets this._write under the hood
}
});
// or
var writable = new stream.Writable({
writev: function(chunks, next) {
// sets this._writev under the hood
}
});
~~~
#### Duplex
~~~
var duplex = new stream.Duplex({
read: function(n) {
// sets this._read under the hood
},
write: function(chunk, encoding, next) {
// sets this._write under the hood
}
});
// or
var duplex = new stream.Duplex({
read: function(n) {
// sets this._read under the hood
},
writev: function(chunks, next) {
// sets this._writev under the hood
}
});
~~~
#### Transform
~~~
var transform = new stream.Transform({
transform: function(chunk, encoding, next) {
// sets this._transform under the hood
},
flush: function(done) {
// sets this._flush under the hood
}
});
~~~
### 流:内部细节
#### 缓冲
`Writable`流和`Readable`流都会分别在一个内部的叫`_writableState.buffer`或`_readableState.buffer`的对象里缓冲数据。
潜在的被缓冲的数据量取决于被传递给构造函数的`highWaterMark`参数。
在`Readable`流中,当其的实现调用`stream.push(chunk)`时就会发生缓冲。如果流的消费者没有调用`stream.read()`,那么数据就会保留在内部队列中直到它被消费。
在`Writable`流中,当用户重复调用`stream.write(chunk)`时就会发生缓冲,甚至是当`write()`返回`false`时。
流,尤其是`pipe()`方法的初衷,是限制数据的滞留量在一个可接受的水平,这样才使得不同传输速度的来源和目标不会淹没可用的内存。
#### stream.read(0)
在一些情况下,你想不消费任何数据而去触发一次底层可读流机制的刷新。你可以调用`stream.read(0)`,它总是返回`null`。
如果内部的读缓冲量在`highWaterMark`之下,并且流没有正在读取,那么调用`read(0)`将会触发一次低级别的`_read`调用。
几乎永远没有必须这么做。但是,你可能会在`io.js`的`Readable`流类的内部代码的几处看到这个。
#### stream.push('')
推入一个0字节的字符串或`Buffer`(不处于对象模式)有一个有趣的副作用。因为这是一个`stream.push()`的调用,它将会结束读取进程。但是,它不添加任何数据到可读缓冲中,所以没有任何用户可消费的数据。
在极少的情况下,你当下没有数据可以提供,但你的消费者同过调用`stream.read(0)`来得知合适再次检查。在这样的情况下,你可以调用`stream.push('')`。
至今为止,这个功能的唯一使用之处是在`tls.CryptoStream`类中,它将在`io.js`的1.0版本中被废弃。如果你发现你不得不使用`stream.push('')`,请考虑使用另外的方式。因为这几乎表示发生了某些可怕的错误。
### 与旧版本的`Node.js`的兼容性
在`Node.js`的0.10版本之前,可读流接口非常简单,并且功能和功用都不强。
- `data`事件会立刻触发,而不是等待你调用`read()`方法。如果你需要进行一些`I/O`操作来决定是否处理数据,那么你只能将数据存储在某些缓冲区中以防数据流失。
- `pause()`仅供查询,并不保证生效。这意味着你还是要准备接收`data`事件在流已经处于暂停模式中时。
在`io.js` v1.0 和`Node.js` v0.10中,下文所述的`Readable`类添加进来。为了向后兼容性,当一个`data`事件的监听器被添加时或`resume()`方法被调用时,可读流切换至流动模式。其作用是,即便您不使用新的`read()`方法和`readable`事件,您也不必担心丢失数据块。
大多数程序都会保持功能正常,但是,以下有一些边界情况:
- 没有添加任何`data`事件
- 从未调用`resume()`方法
- 流没有被导流至任何可写的目标
例如,考虑以下代码:
~~~
// WARNING! BROKEN!
net.createServer(function(socket) {
// we add an 'end' method, but never consume the data
socket.on('end', function() {
// It will never get here.
socket.end('I got your message (but didnt read it)\n');
});
}).listen(1337);
~~~
在`Node.js` v0.10前,到来的信息数据会被简单地丢弃。但是在`io.js` v1.0 和`Node.js` v0.10后,`socket`会被永远暂停。
解决方案是调用`resume()`方法来开启数据流:
~~~
// Workaround
net.createServer(function(socket) {
socket.on('end', function() {
socket.end('I got your message (but didnt read it)\n');
});
// start the flow of data, discarding it.
socket.resume();
}).listen(1337);
~~~
除了新的`Readable`流切换至流动模式之外,在v0.10之前的流可以被使用`wrap()`方法包裹。
#### 对象模式
通常情况下,流仅操作字符串和`Buffer`。
处于对象模式中的流除了`Buffer`和字符串外,还能读出普通的`JavaScirpt`值。
处于对象模式中的可读流在调用`stream.read(size)`后只会返回单个项目,不论`size`参数是什么。
处于对象模式中的可写流总是忽略`stream.write(data, encoding)`中的`encoding`参数。
对于处于对象模式中的流,特殊值`null`仍然保留它的特殊意义。也就是说,对于对象模式的可读流,`stream.read()`返回一个`null`仍意味着没有更多的数据了,并且`stream.push(null)`会发送一个文件末端信号(`EOF`)。
核心`io.js`中没有流是对象模式的。这个模式仅仅供用户的流库使用。
你应当在子类的构造函数的`options`参数对象中设置对象模式。在流的过程中设置对象模式时不安全的。
对于双工流,可以分别得通过`readableObjectMode`和`writableObjectMode`设置可读端和可写端。这些配置可以被用来通过转换流实现解释器和序列化器。
~~~
var util = require('util');
var StringDecoder = require('string_decoder').StringDecoder;
var Transform = require('stream').Transform;
util.inherits(JSONParseStream, Transform);
// Gets \n-delimited JSON string data, and emits the parsed objects
function JSONParseStream() {
if (!(this instanceof JSONParseStream))
return new JSONParseStream();
Transform.call(this, { readableObjectMode : true });
this._buffer = '';
this._decoder = new StringDecoder('utf8');
}
JSONParseStream.prototype._transform = function(chunk, encoding, cb) {
this._buffer += this._decoder.write(chunk);
// split on newlines
var lines = this._buffer.split(/\r?\n/);
// keep the last partial line buffered
this._buffer = lines.pop();
for (var l = 0; l < lines.length; l++) {
var line = lines[l];
try {
var obj = JSON.parse(line);
} catch (er) {
this.emit('error', er);
return;
}
// push the parsed object out to the readable consumer
this.push(obj);
}
cb();
};
JSONParseStream.prototype._flush = function(cb) {
// Just handle any leftover
var rem = this._buffer.trim();
if (rem) {
try {
var obj = JSON.parse(rem);
} catch (er) {
this.emit('error', er);
return;
}
// push the parsed object out to the readable consumer
this.push(obj);
}
cb();
};
~~~
REPL
最后更新于:2022-04-01 01:59:01
### 稳定度: 2 - 稳定
一个 读取-执行-打印-循环(REPL)可以用于单独的程序,也能很容易的被集成在其他程序中。`REPL`提供了一种交互着运行`JavaScript`然后查看结果的方式。它可以被用来调试,测试或只是尝试一些东西。
在命令行中不带任何参数直接执行`iojs`,你会进入REPL界面。它有一个极简的emacs行编辑器。
~~~
mjr:~$ iojs
Type '.help' for options.
> a = [ 1, 2, 3];
[ 1, 2, 3 ]
> a.forEach(function (v) {
... console.log(v);
... });
1
2
3
~~~
要使用高级的行编辑器的话,带着环境变量`NODE_NO_READLINE=1`启动`io.js`。它将会在允许你使用`rlwrap`的终端设置中,启动一个主要的调试`REPL`(main and debugger REPL)。
例如,你可以把以下内容加入`bashrc`文件:
~~~
alias iojs="env NODE_NO_READLINE=1 rlwrap iojs"
~~~
内置的`REPL`(通过运行`iojs`或`iojs -i`启动)可以被以下环境变量所控制:
- NODE_REPL_HISTORY_FILE - 如果指定,那必须是一个用户可读也可写的文件路径。当给定了一个可用的路径,将启用持久化的历史记录支持:`REPL`历史记录将会跨`iojs``REPL`会话持久化。
- NODE_REPL_HISTORY_SIZE - 默认为`1000`。与`NODE_REPL_HISTORY_FILE`结合,控制需要持久化的历史记录数量。必须为正数。
- NODE_REPL_MODE - 可以是`sloppy`,`strict`或`magic`中的一个。默认为`magic`,会自动在严格模式中执行`"strict mode only"`声明。
#### repl.start(options)
返回并启动一个`REPLServer`实例,继承于`[Readline Interface][]`。接受一个包含以下值得`options`对象:
-
prompt - 所有`I/O`的提示符。默认为`>`。
-
input - 监听的可读流。默认为`process.stdin`。
-
output - 输出数据的可写流。默认为`process.stdout`。
-
terminal - 如果流需要被像TTY对待,并且有`ANSI/VT100`转义代码写入,设置其为`true`。默认为在实例化时检查到的`output`流的`isTTY`属性。
-
eval - 被用来执行每一行的函数。默认为被异步包装过的`eval()`。参阅下文的自定义`eval`的例子。
-
useColors - 一个表明了是否`writer`函数需要输出颜色的布尔值。如果设置了不同的`writer`函数,那么它什么都不会做。默认为`REPL`的终端值。
-
useGlobal - 若设置为`true`,那么`REPL`将使用全局对象,而不是运行每一个脚本在不同上下文中。默认为`false`。
-
ignoreUndefined - 若设置为`true`,那么如果返回值是`undefined`,`REPL`将不会输出它。默认为`false`。
-
writer - 当每一个命令被执行完毕时,都会调用这个函数,它返回了展示的格式(包括颜色)。默认为`util.inspect`。
-
**replMode** - 控制是否`REPL`运行所有的模式在严格模式,默认模式,或混合模式(`"magic"`模式)。接受以下值:
- repl.REPL_MODE_SLOPPY - 在混杂模式下运行命令。
- repl.REPL_MODE_STRICT - 在严格模式下运行命令。这与在每个命令前添加`'use strict'`语句相等。
- repl.REPL_MODE_MAGIC - 试图在默认模式中运行命令,如果失败了,会重新尝试使用严格模式。
你可以使用你自己的`eval`函数,如果它包含以下签名:
~~~
function eval(cmd, context, filename, callback) {
callback(null, result);
}
~~~
在用tab补全时 - `eval`将会带着一个作为输入字符串的`.scope`调用。它被期望返回一个`scope`名字数组,被用来自动补全。
多个`REPL`可以运行相同的`io.js`实例。共享同一个全局对象,但是各自的I/O独立。
下面是在`stdin`,Unix `socket` 和 TCP `socket` 上启动一个`REPL`的例子:
~~~
var net = require("net"),
repl = require("repl");
connections = 0;
repl.start({
prompt: "io.js via stdin> ",
input: process.stdin,
output: process.stdout
});
net.createServer(function (socket) {
connections += 1;
repl.start({
prompt: "io.js via Unix socket> ",
input: socket,
output: socket
}).on('exit', function() {
socket.end();
})
}).listen("/tmp/iojs-repl-sock");
net.createServer(function (socket) {
connections += 1;
repl.start({
prompt: "io.js via TCP socket> ",
input: socket,
output: socket
}).on('exit', function() {
socket.end();
});
}).listen(5001);
~~~
在命令行中运行这个程序会在`stdin`上启动一个`REPL`。另外的`REPL`客户端将会通过Unix `socket`或TCP `socket`连接。`telnet`在连接TCP `socket`时非常有用,`socat`在连接Unix `socket`和TCP `socket`时都非常有用。
通过从基于Unix `socket` 的服务器启动`REPL`,你可以不用重启,而连接到一个长久执行的(long-running)`io.js`进程。
一个通过`net.Server`和`net.Socket`实例运行“全特性”(终端)`REPL`的例子,参阅`https://gist.github.com/2209310`。
一个通过`curl(1)`运行`REPL`的例子,参阅`https://gist.github.com/2053342`。
#### Event: 'exit'
- function () {}
当用户通过任意一种已定义的方式退出`REPL`时触发。具体地说,在`REPL`中键入`.exit`,两次按下`Ctrl+C`来发送`SIGINT`信号,按下`Ctrl+D`来发送结束信号。
例子:
~~~
r.on('exit', function () {
console.log('Got "exit" event from repl!');
process.exit();
});
~~~
#### Event: 'reset'
- function (context) {}
当`REPL`内容被重置时触发。当你键入`.clear`时发生。如果你以`{ useGlobal: true }`启动`REPL`,那么这个事件将永远不会触发。
例子:
~~~
// Extend the initial repl context.
r = repl.start({ options ... });
someExtension.extend(r.context);
// When a new context is created extend it as well.
r.on('reset', function (context) {
console.log('repl has a new context');
someExtension.extend(context);
});
~~~
### REPL 特性
在`REPL`内,按下`Control+D`将会退出。多行表达式可以被输入。Tab补全同时支持全局和本地变量。
核心模块将会被按需载入环境。例如,调用`fs`,将会从`global.fs`获取,作为`require()``fs`模块的替代。
特殊的变量`_`(下划线)包含了上一个表达式的结果。
~~~
> [ "a", "b", "c" ]
[ 'a', 'b', 'c' ]
> _.length
3
> _ += 1
4
~~~
`REPL`可以访问全局作用域里的任何变量。你可以通过将变量赋值给一个关联了所有`REPLServer`的`context`对象来暴露一个对象给`REPL`。例子:
~~~
// repl_test.js
var repl = require("repl"),
msg = "message";
repl.start("> ").context.m = msg;
~~~
`context`对象里的对象会表现得像`REPL`的本地变量:
~~~
mjr:~$ iojs repl_test.js
> m
'message'
~~~
以下是一些特殊的`REPL`命令:
- .break - 当你输入一个多行表达式时,有时你走神了,或有时你不关心如何完成它了。`.break`将会让你重新来过。
- .clear - 重置`context`对象为一个空对象并且清除所有多行表达式。
- .exit - 关闭I/O流,意味着会导致`REPL`退出。
- .help - 展示特殊命令列表。
-
**.save** 将当前的`REPL`会话保存入一个文件。
- `.save ./file/to/save.js`
-
**.load** - 从一个文件中加载`REPL`会话。
- `.load ./file/to/load.js`
这些组合键在`REPL`中有以下影响:
- ctrl + C - 与`.break`关键字相似。终止当前命令。在一个空行上连按两次会强制退出。
- ctrl + D - 与`.exit`关键字相似。
- tab - 展示所有的全局和本地变量。
Readline
最后更新于:2022-04-01 01:58:59
### 稳定度: 2 - 稳定
通过`require('readline')`来使用这个模块。`Readline`允许逐行读取一个流(如`process.stdin`)。
注意,一旦你执行了这个模块,你的`io.js`程序在你关闭此接口之前,将不会退出。以下是如何让你的程序优雅的退出的例子:
~~~
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("What do you think of io.js? ", function(answer) {
// TODO: Log the answer in a database
console.log("Thank you for your valuable feedback:", answer);
rl.close();
});
~~~
#### readline.createInterface(options)
创建一个`readline`接口实例。接受一个`options`对象,接受以下值:
-
input - 需要监听的可读流(必选)。
-
output - 将逐行读取的数据写入的流(可选)。
-
completer - 用于Tab自动补全的可选函数。参阅下文的使用例子。
-
terminal - 如果`input`和`output`流需要被像一个TTY一样对待,并且被经由ANSI/VT100转义代码写入,就传递`true`。默认为在实例化时,检查出的`ouput`流的`isTTY`值。
-
historySize - 保留的历史记录行的最大数量。默认为`30`。
`completer`函数被给予了一个用户输入的当前行,并且支持返回一个含有两个元素的数组:
1.
一个匹配当前输入补全的数组。
1.
一个被用于匹配的子字符串。
最终形式如:`[[substr1, substr2, ...], originalsubstring]`。
例子:
~~~
function completer(line) {
var completions = '.help .error .exit .quit .q'.split(' ')
var hits = completions.filter(function(c) { return c.indexOf(line) == 0 })
// show all completions if none found
return [hits.length ? hits : completions, line]
}
~~~
`completer`同样也可以以同步的方式运行,如果它接受两个参数:
~~~
function completer(linePartial, callback) {
callback(null, [['123'], linePartial]);
}
~~~
`createInterface`通常与`process.stdin`和`process.stdout`搭配,用来接受用户输入:
~~~
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
~~~
一旦你有了一个`readline`接口,你通常要监听一个`line`事件。
如果这个实例中,`terminal`为`true`。那么`output`流将会得到最好的兼容性,如果它定义了`output.columns`属性,并且在`output`的`columns`变化时(当它是一个TTY时,`process.stdout`会自动这么做),触发了一个`resize`事件。
#### Class: Interface
一个代表了有`input`和`output`流的`readline`接口。
#### rl.setPrompt(prompt)
设置提示符,例如当你在命令行运行`iojs`命令时,你看到了`>`,这就是`io.js`的提示符。
#### rl.prompt([preserveCursor])
为用户的输入准备好`readline`,在新的一行放置当前的`setPrompt`选项,给予用户一个新的用于输入的地方。设置`preserveCursor`为`true`,来防止光标位置被重置为`0`。
仍会重置被`createInterface`使用的`input`流,如果它被暂停。
如果当调用`createInterface`时`output`被设置为`null`或`undefined`,提示符将不会被写入。
#### rl.question(query, callback)
带着`query`来预先放置提示符,并且在用户应答时执行回调函数。给用户展示`query`,然后在用户输入了应答后调用`callback`。
仍会重置被`createInterface`使用的`input`流,如果它被暂停。
如果当调用`createInterface`时`output`被设置为`null`或`undefined`,什么都不会被展示。
例子:
~~~
interface.question('What is your favorite food?', function(answer) {
console.log('Oh, so your favorite food is ' + answer);
});
~~~
#### rl.pause()
暂停`readline`的`input`流,允许它在晚些需要时恢复。
注意,带着事件的流不会立刻被暂停。在调用了`pause`后,许多事件可能被触发,包括`line`事件。
#### rl.resume()
恢复`readline`的`input`流。
#### rl.close()
关闭实例接口,放弃对`input`和`output`流的控制。`close`事件也会被触发。
#### rl.write(data[, key])
向`output`流写入数据,除非当调用`createInterface`时`output`被设置为`null`或`undefined`。`key`是一个代表了键序列的对象;在当终端为TTY时可用。
如果`input`流被暂停,它也会被恢复。
例子:
~~~
rl.write('Delete me!');
// Simulate ctrl+u to delete the line written previously
rl.write(null, {ctrl: true, name: 'u'});
~~~
#### Events
#### Event: 'line'
- function (line) {}
当`input`流收到一个`\n`时触发,通常在用户敲下回车时触发。这是一个监听用户输入的好钩子。
例子:
~~~
rl.on('line', function (cmd) {
console.log('You just typed: '+cmd);
});
~~~
#### Event: 'pause'
- function () {}
当`input`流被暂停时触发。
也会在`input`没有被暂停并且收到一个`SIGCONT`事件时触发(参阅`SIGTSTP`事件和`SIGCONT`事件)。
例子:
~~~
rl.on('pause', function() {
console.log('Readline paused.');
});
~~~
#### Event: 'resume'
- function () {}
当`input`流被恢复时触发。
例子:
~~~
rl.on('resume', function() {
console.log('Readline resumed.');
});
~~~
#### Event: 'close'
- function () {}
当`close()`被调用时触发。
也会在`input`流收到它的`end`事件时触发。当这个事件触发时,接口实例需要考虑“被结束”。例如,当`input`流接收到`^D`(也被认作`EOT`)。
这个事件也会在如果当前没有`SIGINT`事件监听器,且`input`流接收到`^C`(也被认作`SIGINT`)时触发。
#### Event: 'SIGINT'
- function () {}
当`input`流接收到`^C`(也被认作`SIGINT`)时触发。如果当前没有`SIGINT`事件的监听器,`pause`事件将会被触发。
例子:
~~~
rl.on('SIGINT', function() {
rl.question('Are you sure you want to exit?', function(answer) {
if (answer.match(/^y(es)?$/i)) rl.pause();
});
});
~~~
#### Event: 'SIGTSTP'
- function () {}
在Windows平台下不能使用。
当`input`流接收到一个`^Z`(也被认作`SIGTSTP`)时触发。如果当前没有`SIGTSTP`事件的监听器,这个程序将会被送至后台运行。
当程序使用`fg`恢复,`pause`和`SIGCONT`事件都会被触发。你可以选择其中的一个来恢复流。
如果流在程序被送至后台前就被暂停,`pause`和`SIGCONT`事件将不会触发。
例子:
~~~
rl.on('SIGTSTP', function() {
// This will override SIGTSTP and prevent the program from going to the
// background.
console.log('Caught SIGTSTP.');
});
~~~
#### Event: 'SIGCONT'
- function () {}
在Windows平台下不能使用。
当`input`流被`^Z`(也被认作`SIGTSTP`)送至后台时触发,然后使用`fg(1)`继续执行。这个事件仅在程序被送至后台前流没有被暂停时触发。
例子:
~~~
rl.on('SIGCONT', function() {
// `prompt` will automatically resume the stream
rl.prompt();
});
~~~
#### Example: Tiny CLI
下面是一个使用以上方法来创建一个迷你的控制台接口的例子:
~~~
var readline = require('readline'),
rl = readline.createInterface(process.stdin, process.stdout);
rl.setPrompt('OHAI> ');
rl.prompt();
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.prompt();
}).on('close', function() {
console.log('Have a great day!');
process.exit(0);
});
~~~
#### readline.cursorTo(stream, x, y)
在给定的TTY流中,将光标移动到指定位置。
#### readline.moveCursor(stream, dx, dy)
在给定的TTY流中,相对于当前位置,将光标移动到指定位置。
#### readline.clearLine(stream, dir)
用指定的方式,在给定的TTY流中,清除当前的行。`dir`可以是以下值之一:
- -1 - 从光标的左边
- 1 - 从光标的右边
- 0 - 整行
#### readline.clearScreenDown(stream)
从当前的光标位置,清除屏幕。
Query Strings
最后更新于:2022-04-01 01:58:57
### 稳定度: 2 - 稳定
这个模块提供了处理 查询字符串 的工具。它提供了以下方法:
#### querystring.stringify(obj[, sep][, eq][, options])
序列化一个对象为一个查询字符串。可以可选地覆盖默认的分隔符(`'&'`)和赋值符号(`'='`)。
`options`对象可以包含`encodeURIComponent`属性(默认为`querystring.escape`),它被用来在需要时,将字符串编码为非utf-8编码。
例子:
~~~
querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' })
// returns
'foo=bar&baz=qux&baz=quux&corge='
querystring.stringify({foo: 'bar', baz: 'qux'}, ';', ':')
// returns
'foo:bar;baz:qux'
// Suppose gbkEncodeURIComponent function already exists,
// it can encode string with `gbk` encoding
querystring.stringify({ w: '中文', foo: 'bar' }, null, null,
{ encodeURIComponent: gbkEncodeURIComponent })
// returns
'w=%D6%D0%CE%C4&foo=bar'
~~~
#### querystring.parse(str[, sep][, eq][, options])
反序列化一个查询字符串为一个对象。可以可选地覆盖默认的分隔符(`'&'`)和赋值符号(`'='`)。
`options`可以包含`maxKeys`属性(默认为`1000`)。它被用来限制被处理的键。将其设置为`0`会移除限制。
`options`可以包含`decodeURIComponent`属性(默认为`querystring.unescape`),它被用来在需要时,解码非uft8编码字符串。
例子:
~~~
querystring.parse('foo=bar&baz=qux&baz=quux&corge')
// returns
{ foo: 'bar', baz: ['qux', 'quux'], corge: '' }
// Suppose gbkDecodeURIComponent function already exists,
// it can decode `gbk` encoding string
querystring.parse('w=%D6%D0%CE%C4&foo=bar', null, null,
{ decodeURIComponent: gbkDecodeURIComponent })
// returns
{ w: '中文', foo: 'bar' }
~~~
#### querystring.escape
`querystring.stringify`使用的转义函数,在需要时可以被覆盖。
#### querystring.unescape
`querystring.parse`使用的反转义函数,在需要时可以被覆盖。
首先它会尝试使用`decodeURIComponent`,但是如果失败了,它就转而使用一个不会在畸形URL上抛出错误的更安全的等价方法。
Punycode
最后更新于:2022-04-01 01:58:54
#### Stability: 2 - Stable
`Punycode.js`在`io.js` v1.0.0+ 和 `Node.js` v0.6.2+ 中被内置。通过`require('punycode')`来获取它(若要在其他版本的`io.js`中使用它,需要先通过npm来安装`punycode`模块)。
#### punycode.decode(string)
转换一个纯ASCII符号 `Punycode`字符串为一个`Unicode`符号的字符串。
~~~
// decode domain name parts
punycode.decode('maana-pta'); // 'mañana'
punycode.decode('--dqo34k'); // '☃-⌘'
~~~
#### punycode.encode(string)
转换一个`Unicode`符号的字符串为一个纯ASCII符号 `Punycode`字符串。
~~~
// encode domain name parts
punycode.encode('mañana'); // 'maana-pta'
punycode.encode('☃-⌘'); // '--dqo34k'
~~~
#### punycode.toUnicode(domain)
转换一个代表了一个域名的`Punycode`字符串为一个`Unicode`字符串。只有代表了域名的部分的`Punycode`字符串会被转换。也就是说,如果你调用了一个已经被转换为`Unicode`的字符串,也是没有问题的。
~~~
// decode domain names
punycode.toUnicode('xn--maana-pta.com'); // 'mañana.com'
punycode.toUnicode('xn----dqo34k.com'); // '☃-⌘.com'
~~~
#### punycode.toASCII(domain)
转换一个代表了一个域名的`Unicode`字符串为一个`Unicode`字符串。只有代表了域名的部分的非ASCII字符串会被转换。也就是说,如果你调用了一个已经被转换为ASCII的字符串,也是没有问题的。
~~~
// encode domain names
punycode.toASCII('mañana.com'); // 'xn--maana-pta.com'
punycode.toASCII('☃-⌘.com'); // 'xn----dqo34k.com'
~~~
#### punycode.ucs2
#### punycode.ucs2.decode(string)
创建一个包含了 字符串中的每个`Unicode`符号的数字编码点 的数组。由于`JavaScript`在内部使用`UCS-2`,这个函数会将一对代理部分(surrogate halves)(UCS-2暴露的单独字符)转换为一个单独的编码点 来匹配UTF-16。
~~~
punycode.ucs2.decode('abc'); // [0x61, 0x62, 0x63]
// surrogate pair for U+1D306 tetragram for centre:
punycode.ucs2.decode('\uD834\uDF06'); // [0x1D306]
~~~
#### punycode.ucs2.encode(codePoints)
基于数字编码点的数组,创建一个字符串。
~~~
punycode.ucs2.encode([0x61, 0x62, 0x63]); // 'abc'
punycode.ucs2.encode([0x1D306]); // '\uD834\uDF06'
~~~
#### punycode.version
一个代表了当前`Punycode.js`版本号的数字。
Process
最后更新于:2022-04-01 01:58:52
`process`对象是一个全局对象,并且何以被在任何地方调用。这是一个`EventEmitter`实例。
### Exit Codes
当没有任何异步操作在等待时,`io.js`通常将会以一个0为退出码退出。以下这些状态码会在其他情况下被用到:
- 1 未捕获的致命异常。这是一个未捕获的异常,并且它没有被`domain`处理,也没有被`uncaughtException`处理。
- 2 未使用(由`Bash`为内建误操作保留)。
- 3 内部的`JavaScript`解析错误。`io.js`内部的`JavaScript`源码引导(bootstrapping)造成的一个解释错误。这极其罕见。并且常常只会发生在`io.js`自身的开发过程中。
- 4 内部的`JavaScript`求值错误。`io.js`内部的`JavaScript`源码引导(bootstrapping)未能在求值时返回一个函数值。这极其罕见。并且常常只会发生在`io.js`自身的开发过程中。
- 5 致命错误。这是V8中严重的不可恢复的错误。典型情况下,一个带有`FATAL ERROR`前缀的信息会被打印在`stderr`。
- 6 内部异常处理函数丧失功能。这是一个未捕获异常,但是内部的致命异常处理函数被设置为丧失功能,并且不能被调用。
- 7 内部异常处理函数运行时失败。这是一个未捕获异常,并且内部致命异常处理函数试图处理它时,自身抛出了一个错误。例如它可能在当`process.on('uncaughtException')`或`domain.on('error')`处理函数抛出错误时发生。
- 8 未使用。`io.js`的之前版本中,退出码`8`通常表示一个未捕获异常。
- 9 无效参数。当一个位置的选项被指定,或者一个必选的值没有被提供。
- 10 内部的`JavaScript`运行时错误。`io.js`内部的`JavaScript`源码引导(bootstrapping)函数被调用时抛出一个错误。这极其罕见。并且常常只会发生在`io.js`自身的开发过程中。
- 12 无效的调试参数。`--debug`和/或`--debug-brk`选项被设置,当时选择了一个无效的端口。
- 大于128 信号退出。如果`io.js`收到了一个如`SIGKILL`或`SIGHUP`的致命信号,那么它将以一个`128`加上 信号码的值 的退出码退出。这是一个标准的Unix实践,因为退出码由一个7位整数定义,并且信号的退出设置了一个高顺序位(high-order bit),然后包含一个信号码的值。
#### Event: 'exit'
进程即将退出时触发。在这个时刻已经没有办法可以阻止事件循环的退出,并且一旦所有的`exit`监听器运行结束时,进程将会退出。因此,在这个监听器中你仅仅能调用同步的操作。这是检查模块状态(如单元测试)的好钩子。回调函数有一个退出码参数。
例子:
~~~
process.on('exit', function(code) {
// do *NOT* do this
setTimeout(function() {
console.log('This will not run');
}, 0);
console.log('About to exit with code:', code);
});
~~~
#### Event: 'beforeExit'
这个事件在`io.js`清空了它的事件循环并且没有任何已安排的任务时触发。通常`io.js`当没有更多被安排的任务时就会退出,但是`beforeExit`中可以执行异步调用,让`io.js`继续运行。
`beforeExit`在程序被显示终止时不会触发,如`process.exit()`或未捕获的异常。除非想去安排更多的任务,否则它不应被用来做为`exit`事件的替代。
#### Event: 'uncaughtException'
当一个异常冒泡回事件循环时就会触发。如果这个时间被添加了监听器,那么默认行为(退出程序且打印堆栈跟踪信息)将不会发生。
例子:
~~~
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`来处理异常是非常粗糙的。
请不要使用它,使用`domain`来替代。如果你已经使用了它,请在不处理这个异常之后重启你的应用。
请不要像`io.js`的`Error Resume Next`这样使用。一个未捕获异常意味着你的应用或拓展有未定义的状态。盲目地恢复意味着任何事都可能发生。
想象你在升级你的系统时电源被拉断了。10次中前9次都没有问题,但是第10次时,你的系统崩溃了。
你已经被警告。
#### Event: 'unhandledRejection'
在一个事件循环中,当一个`promise`被“拒绝”并且没有附属的错误处理函数时触发。当一个带有`promise`异常的程序被封装为被“拒绝”的`promise`时,这样的程序的错误可以被`promise.catch(...)`捕获处理并且“拒绝”会通过`promise`链冒泡。这个事件对于侦测和保持追踪那些“拒绝”没有被处理的`promise`非常有用。这个事件会带着以下参数触发:
- reason `promise`的“拒绝”对象(通常是一个错误实例)
- p 被“拒绝”的`promise`
下面是一个把所有未处理的“拒绝”打印到控制台的例子:
~~~
process.on('unhandledRejection', function(reason, p) {
console.log("Unhandled Rejection at: Promise ", p, " reason: ", reason);
// application specific logging, throwing an error, or other logic here
});
~~~
下面是一个会触发`unhandledRejection`事件的“拒绝”:
~~~
somePromise.then(function(res) {
return reportToUser(JSON.pasre(res)); // note the typo
}); // no `.catch` or `.then`
~~~
#### Event: 'rejectionHandled'
当一个`Promise`被“拒绝”并且一个错误处理函数被附给了它(如`.catch()`)时的下一个事件循环之后触发。这个事件会带着以下参数触发:
- p 一个在之前会被触发在`unhandledRejection`事件中,但现在被处理函数捕获的`promise`
一个`promise`链的顶端没有 “拒绝”可以总是被处理 的概念。由于其异步的本质,一个`promise`的“拒绝”可以在未来的某一个时间点被处理,可以是在事件循环中被触发`unhandledRejection`事件之后。
另外,不像同步代码中是一个永远增长的 未捕获异常 列表,`promise`中它是一个可伸缩的 未捕获`拒绝` 列表。在同步代码中,`uncaughtException`事件告诉你 未捕获异常 列表增长了。但是在`promise`中,`unhandledRejection`事件告诉你 未捕获“拒绝” 列表增长了,`rejectionHandled`事件告诉你 未捕获“拒绝” 列表缩短了。
使用“拒绝”侦测钩子来保持一个被“拒绝”的`promise`列表:
~~~
var unhandledRejections = [];
process.on('unhandledRejection', function(reason, p) {
unhandledRejections.push(p);
});
process.on('rejectionHandled', function(p) {
var index = unhandledRejections.indexOf(p);
unhandledRejections.splice(index, 1);
});
~~~
#### Signal Events
当一个进程收到一个信号时触发。参阅`sigaction(2)`。
监听`SIGINT`信号的例子:
~~~
// 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`信号的快捷方法是在大多数终端中按下 Control-C 。
注意:
- SIGUSR1 是`io.js`用于开启调试的保留信号。可以为其添加一个监听器,但不能阻止调试的开始。
- SIGTERM 和 SIGINT在非Windows平台下有在以 128 + 信号 退出码退出前重置终端模式的默认监听器。如果另有监听器被添加,默认监听器会被移除(即`io.js`将会不再退出)。
- SIGPIPE 默认被忽略,可以被添加监听器。
- SIGHUP 当控制台被关闭时会在Windows中产生,或者其他平台有其他相似情况时(参阅`signal(7)`)。它可以被添加监听器,但是Windows中`io.js`会无条件的在10秒后关闭终端。在其他非Windows平台,它的默认行为是结束`io.js`,但是一旦被添加了监听器,默认行为会被移除。
- SIGTERM 在Windows中不被支持,它可以被监听。
- SIGINT 支持所有的平台。可以由 CTRL+C 产生(尽管它可能是可配置的)。当启用终端的`raw mode`时,它不会产生。
- SIGBREAK 在Windows中,按下 CTRL+BREAK 时它会产生。在非Windows平台下,它可以被监听,但它没有产生的途径。
- SIGWINCH 当终端被改变大小时产生。Windows下,它只会在当光标被移动时写入控制台或可读tty使用`raw mode`时发生。
- SIGKILL 可以被添加监听器。它会无条件得在所有平台下关闭`io.js`。
- SIGSTOP 可以被添加监听器。
注意Windows不支持发送信号,但`io.js`通过`process.kill()`和`child_process.kill()`提供了模拟:- 发送信号`0`被用来检查进程的存在 - 发送SIGINT, SIGTERM 和 SIGKILL 会导致目标进程的无条件退出。
#### process.stdout
一个指向`stdout`的可写流。
例如,`console.log`可能与这个相似:
~~~
console.log = function(msg) {
process.stdout.write(msg + '\n');
};
~~~
在`io.js`中,`process.stderr`和`process.stdout`与其他流不同,因为他们不能被关闭(调用`end()`会报错)。它们永远不触发`finish`事件并且写操作通常是阻塞的。
-
当指向普通文件或TTY文件描述符时,它们是阻塞的。
-
**以下情况下他们指向流**
- 他们在Linux/Unix中阻塞
- 他们在Windows中的其他流里不阻塞
若要检查`io.js`是否在一个TTY上下文中运行,读取`process.stderr`,`process.stdout`或`process.stdin`的`isTTY`属性:
~~~
$ iojs -p "Boolean(process.stdin.isTTY)"
true
$ echo "foo" | iojs -p "Boolean(process.stdin.isTTY)"
false
$ iojs -p "Boolean(process.stdout.isTTY)"
true
$ iojs -p "Boolean(process.stdout.isTTY)" | cat
false
~~~
更多信息请参阅tty文档。
#### process.stderr
一个指向`stderr`的可写流。
在`io.js`中,`process.stderr`和`process.stdout`与其他流不同,因为他们不能被关闭(调用`end()`会报错)。它们永远不触发`finish`事件并且写操作通常是阻塞的。
-
当指向普通文件或TTY文件描述符时,它们是阻塞的。
-
**以下情况下他们指向流**
- 他们在Linux/Unix中阻塞
- 他们在Windows中的其他流里不阻塞
#### process.stdin
一个指向`stdin`的可读流。
一个打开标准输入并且监听两个事件的例子:
~~~
process.stdin.setEncoding('utf8');
process.stdin.on('readable', function() {
var chunk = process.stdin.read();
if (chunk !== null) {
process.stdout.write('data: ' + chunk);
}
});
process.stdin.on('end', function() {
process.stdout.write('end');
});
~~~
作为一个流,`process.stdin`可以被切换至“旧”模式,这样就可以兼容`node.js` v0.10 前所写的脚本。更多信息请参阅 流的兼容性 。
在“旧”模式中`stdin`流默认是被暂停的。所以你必须调用`process.stdin.resume()`来读取。注意调用`process.stdin.resume()`这个操作本身也会将流切换至旧模式。
如果你正将开启一个新的工程。你应该要更常使用“新”模式的流。
#### process.argv
一个包含了命令行参数的数组。第一次元素将会是`'iojs'`,第二个元素将会是`JavaScript`文件名。之后的元素将会是额外的命令行参数。
~~~
// print process.argv
process.argv.forEach(function(val, index, array) {
console.log(index + ': ' + val);
});
~~~
这将会是:
~~~
$ iojs process-2.js one two=three four
0: iojs
1: /Users/mjr/work/iojs/process-2.js
2: one
3: two=three
4: four
~~~
#### process.execPath
这将是开启进程的可执行文件的绝对路径名:
例子:
~~~
/usr/local/bin/iojs
~~~
#### process.execArgv
这是在启动时`io.js`自身参数的集合。这些参数不会出现在`process.argv`中,并且不会包含`io.js`可执行文件,脚本名和其他脚本名之后的参数。这些参数对开启和父进程相同执行环境的子进程非常有用。
例子:
~~~
$ iojs --harmony script.js --version
~~~
`process.execArgv`将会是:
~~~
['--harmony']
~~~
`process.argv`将会是:
~~~
['/usr/local/bin/iojs', 'script.js', '--version']
~~~
#### process.abort()
这将导致`io.js`触发`abort`事件。这个将导致`io.js`退出,并创建一个核心文件。
#### process.chdir(directory)
为进程改变当前工作目录,如果失败,则抛出一个异常。
~~~
console.log('Starting directory: ' + process.cwd());
try {
process.chdir('/tmp');
console.log('New directory: ' + process.cwd());
}
catch (err) {
console.log('chdir: ' + err);
}
~~~
#### process.cwd()
返回进程的当前工作目录。
~~~
console.log('Current directory: ' + process.cwd());
~~~
#### process.env
包含用户环境变量的对象。参阅`environ(7)`。
一个例子:
~~~
{ TERM: 'xterm-256color',
SHELL: '/usr/local/bin/bash',
USER: 'maciej',
PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
PWD: '/Users/maciej',
EDITOR: 'vim',
SHLVL: '1',
HOME: '/Users/maciej',
LOGNAME: 'maciej',
_: '/usr/local/bin/iojs' }
~~~
你可以改写这个对象,但是改变不会反应在你的进程之外。这以为着以下代码不会正常工作:
~~~
$ iojs -e 'process.env.foo = "bar"' && echo $foo
~~~
但是以下代码会:
~~~
process.env.foo = 'bar';
console.log(process.env.foo);
~~~
#### process.exit([code])
使用指定的退出码退出程序,如果忽略退出码。那么将使用“成功”退出码`0`。
以一个“失败”退出码结束:
~~~
process.exit(1);
~~~
在执行`io.js`的shell中可以看到为`1`的退出码。
#### process.exitCode
将是程序退出码的数字,当程序优雅退出 或 被`process.exit()`关闭且没有指定退出码时。
为`process.exit(code)`指定一个退出码会覆盖之前的`process.exitCode`设置。
#### process.getgid()
注意:这个函数只在POSIX平台上有效(如在Windows,Android中无效)。
获取进程的群组标识(参阅`getgid(2)`)。这是一个群组id数组,不是群组名。
~~~
if (process.getgid) {
console.log('Current gid: ' + process.getgid());
}
~~~
#### process.getegid()
注意:这个函数只在POSIX平台上有效(如在Windows,Android中无效)。
获取进程的有效群组标识(参阅`getgid(2)`)。这是一个群组id数组,不是群组名。
~~~
if (process.getegid) {
console.log('Current gid: ' + process.getegid());
}
~~~
#### process.setgid(id)
注意:这个函数只在POSIX平台上有效(如在Windows,Android中无效)。
设置进程的群组标识(参阅`setgid(2)`)。它接受一个数字ID或一个群组名字符串。如果群组名被指定,那么这个方法将在解析群组名为一个ID的过程中阻塞。
~~~
if (process.getgid && process.setgid) {
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.setegid(id)
注意:这个函数只在POSIX平台上有效(如在Windows,Android中无效)。
设置进程的有效群组标识(参阅`setgid(2)`)。它接受一个数字ID或一个群组名字符串。如果群组名被指定,那么这个方法将在解析群组名为一个ID的过程中阻塞。
~~~
if (process.getegid && process.setegid) {
console.log('Current gid: ' + process.getegid());
try {
process.setegid(501);
console.log('New gid: ' + process.getegid());
}
catch (err) {
console.log('Failed to set gid: ' + err);
}
}
~~~
#### process.getuid()
注意:这个函数只在POSIX平台上有效(如在Windows,Android中无效)。
获取进程的用户id(参阅`getuid(2)`)。这是一个数字用户id,不是用户名。
~~~
if (process.getuid) {
console.log('Current uid: ' + process.getuid());
}
~~~
#### process.geteuid()
注意:这个函数只在POSIX平台上有效(如在Windows,Android中无效)。
获取进程的有效用户id(参阅`getuid(2)`)。这是一个数字用户id,不是用户名。
~~~
if (process.geteuid) {
console.log('Current uid: ' + process.geteuid());
}
~~~
#### process.setuid(id)
注意:这个函数只在POSIX平台上有效(如在Windows,Android中无效)。
设置进程的用户ID(参阅`setuid(2)`)。它接受一个数字ID或一个用户名字符串。如果用户名被指定,那么这个方法将在解析用户名为一个ID的过程中阻塞。
~~~
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.seteuid(id)
注意:这个函数只在POSIX平台上有效(如在Windows,Android中无效)。
设置进程的有效用户ID(参阅`seteuid(2)`)。它接受一个数字ID或一个用户名字符串。如果用户名被指定,那么这个方法将在解析用户名为一个ID的过程中阻塞。
~~~
if (process.geteuid && process.seteuid) {
console.log('Current uid: ' + process.geteuid());
try {
process.seteuid(501);
console.log('New uid: ' + process.geteuid());
}
catch (err) {
console.log('Failed to set uid: ' + err);
}
}
~~~
#### process.getgroups()
注意:这个函数只在POSIX平台上有效(如在Windows,Android中无效)。
返回一个补充群组ID的数组。如果包含了有效的组ID,POSIX将不会指定。但`io.js`保证它始终是。
#### process.setgroups(groups)
注意:这个函数只在POSIX平台上有效(如在Windows,Android中无效)。
设置一个补充群组ID。这是一个特殊的操作,意味着你需要拥有`root`或`CAP_SETGID`权限才可以这么做。
列表可以包含群组ID,群组名,或两者。
#### process.initgroups(user, extra_group)
注意:这个函数只在POSIX平台上有效(如在Windows,Android中无效)。
读取`/etc/group`并且初始化群组访问列表,使用用户是组员的所有群组。这是一个特殊的操作,意味着你需要拥有`root`或`CAP_SETGID`权限才可以这么做。
`user`是一个用户名或一个用户ID。`extra_group`是一个群组名或群组ID。
当你注销权限时有些需要关心的:
~~~
console.log(process.getgroups()); // [ 0 ]
process.initgroups('bnoordhuis', 1000); // switch user
console.log(process.getgroups()); // [ 27, 30, 46, 1000, 0 ]
process.setgid(1000); // drop root gid
console.log(process.getgroups()); // [ 27, 30, 46, 1000 ]
~~~
#### process.version
一个暴露`NODE_VERSION`的编译时存储属性。
~~~
console.log('Version: ' + process.version);
~~~
#### process.versions
一个暴露io.js版本和它的依赖的字符串属性。
~~~
console.log(process.versions);
~~~
将可能打印:
~~~
{ http_parser: '2.3.0',
node: '1.1.1',
v8: '4.1.0.14',
uv: '1.3.0',
zlib: '1.2.8',
ares: '1.10.0-DEV',
modules: '43',
openssl: '1.0.1k' }
~~~
#### process.config
一个表示用于编译当前`io.js`执行文件的配置的JavaScript对象。这和运行`./configure`脚本产生的`config.gypi`一样。
一个可能的输出:
~~~
{ target_defaults:
{ cflags: [],
default_configuration: 'Release',
defines: [],
include_dirs: [],
libraries: [] },
variables:
{ host_arch: 'x64',
node_install_npm: 'true',
node_prefix: '',
node_shared_cares: 'false',
node_shared_http_parser: 'false',
node_shared_libuv: 'false',
node_shared_zlib: 'false',
node_use_dtrace: 'false',
node_use_openssl: 'true',
node_shared_openssl: 'false',
strict_aliasing: 'true',
target_arch: 'x64',
v8_use_snapshot: 'true' } }
~~~
#### process.kill(pid[, signal])
给进程传递一个信号。`pid`是进程id,`signal`是描述信号的字符串。信号码类似于`'SIGINT'`或`'SIGHUP'`。如果忽略,那么信号将是`'SIGTERM'`。更多信息参阅`Signal Events`和`kill(2)`。
如果目标不存在将会抛出一个错误,并且在一些情况下,`0`信号可以被用来测试进程的存在。
注意,这个函数仅仅是名字为`process.kill`,它只是一个信号发送者。发送的信号可能与杀死进程无关。
一个发送信号给自身的例子:
~~~
process.on('SIGHUP', function() {
console.log('Got SIGHUP signal.');
});
setTimeout(function() {
console.log('Exiting.');
process.exit(0);
}, 100);
~~~
process.kill(process.pid, 'SIGHUP');
注意:当`SIGUSR1`被`io.js`收到,它会开始调试。参阅`Signal Events`。
process.pid#
进程的PID。
~~~
console.log('This process is pid ' + process.pid);
~~~
process.title#
设置/获取 `'ps'` 中显示的进程名。
当设置该属性时,所能设置的字符串最大长度视具体平台而定,如果超过的话会自动截断。
在 Linux 和 OS X 上,它受限于名称的字节长度加上命令行参数的长度,因为它有覆盖参数内存。
v0.8 版本允许更长的进程标题字符串,也支持覆盖环境内存,但是存在潜在的不安全和混乱。
#### process.arch
返回当前的处理器结构:`'arm'`,`'ia32'`或`'x64'`。
~~~
console.log('This processor architecture is ' + process.arch);
~~~
### process.platform
放回当前的平台:`'darwin'`,`'freebsd'`,`'linux'`,`'sunos'`或`'win32'`。
~~~
console.log('This platform is ' + process.platform);
~~~
#### process.memoryUsage()
返回当前`io.js`进程内存使用情况(用字节描述)的对象。
~~~
var util = require('util');
console.log(util.inspect(process.memoryUsage()));
~~~
可能的输出:
~~~
{ rss: 4935680,
heapTotal: 1826816,
heapUsed: 650472 }
~~~
`heapTotal`和`heapUsed`指向V8的内存使用。
#### process.nextTick(callback[, arg][, ...])
- callback Function
在事件循环的下一次循环中调用回调函数。
这不是`setTimeout(fn, 0)`的简单别名,它更有效率。在之后的`tick`中,它在任何其他的I/O事件(包括`timer`)触发之前运行。
~~~
console.log('start');
process.nextTick(function() {
console.log('nextTick callback');
});
console.log('scheduled');
// Output:
// start
// scheduled
// nextTick callback
~~~
这对于开发你想要给予用户在对象被构建后,任何I/O发生前,去设置事件监听器的机会时,非常有用。
~~~
function MyThing(options) {
this.setupOptions(options);
process.nextTick(function() {
this.startDoingStuff();
}.bind(this));
}
var thing = new MyThing();
thing.getReadyForStuff();
// thing.startDoingStuff() gets called now, not before.
~~~
这对于100%同步或100%异步的API非常重要。考虑一下例子:
~~~
// WARNING! DO NOT USE! BAD UNSAFE HAZARD!
function maybeSync(arg, cb) {
if (arg) {
cb();
return;
}
fs.stat('file', cb);
}
~~~
这个API是危险的,如果你这样做:
~~~
maybeSync(true, function() {
foo();
});
bar();
~~~
`foo()`和`bar()`的调用次序是不确定的。
更好的做法是:
~~~
function definitelyAsync(arg, cb) {
if (arg) {
process.nextTick(cb);
return;
}
fs.stat('file', cb);
}
~~~
注意:`nextTick`队列在每一次事件循环的I/O开始前都要完全执行完毕。所以,递归地设置`nextTick`回调会阻塞I/O的方法,就像一个`while(true);`循环。
#### process.umask([mask])
设置或读取进程的文件模式的创建掩码。子进程从父进程中继承这个掩码。返回旧的掩码如果`mask`参数被指定。否则,会返回当前掩码。
~~~
var oldmask, newmask = 0022;
oldmask = process.umask(newmask);
console.log('Changed umask from: ' + oldmask.toString(8) +
' to ' + newmask.toString(8));
~~~
#### process.uptime()
`io.js`进程已执行的秒数。
#### process.hrtime()
以`[seconds, nanoseconds]`元组数组的形式返回高分辨时间。是相对于过去的任意时间。它与日期无关所以不用考虑时区等因素。它的主要用途是衡量程序性能。
你可以将之前的`process.hrtime()`返回传递给一个新的`process.hrtime()`来获得一个比较。衡量性能时非常有用:
~~~
var time = process.hrtime();
// [ 1800216, 25 ]
setTimeout(function() {
var diff = process.hrtime(time);
// [ 1, 552 ]
console.log('benchmark took %d nanoseconds', diff[0] * 1e9 + diff[1]);
// benchmark took 1000000527 nanoseconds
}, 1000);
~~~
#### process.mainModule
检索`require.main`的备用方式。区别是,如果主模块在运行时改变,`require.main`可能仍指向改变发生前的被引入的原主模块。通常,假设它们一样是安全的。
与`require.main`一样,当如果没有入口脚本时,它将是`undefined`。
Path
最后更新于:2022-04-01 01:58:50
### 稳定度: 2 - 稳定
这个模块提供了处理和转换文件路径的工具。几乎所有的方法都仅提供字符串转换功能。文件系统不会去检查路径是否可用。
通过`require('path')`来使用这个模块。以下是提供的方法:
#### path.normalize(p)
规范化字符串路径,注意`'..'`和`'.'`部分。
当有多个连续斜杠时,它们会被替换为一个斜杠;当路径的最后有一个斜杠,它会被保留。在Windows下使用反斜杠。
例子:
~~~
path.normalize('/foo/bar//baz/asdf/quux/..')
// returns
'/foo/bar/baz/asdf'
~~~
#### path.join([path1][, path2][, ...])
连接所有的参数,并且规范化结果路径。
参数必须是字符串。在0.8版本中,非字符串参数会被忽略,在0.10版本及之后,会抛出一个异常。
例子:
~~~
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..')
// returns
'/foo/bar/baz/asdf'
path.join('foo', {}, 'bar')
// throws exception
TypeError: Arguments to path.join must be strings
~~~
#### 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
~~~
区别是不同的路径不需要一定存在,并且可以是文件。
例子:
~~~
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/iojs, it returns
'/home/myself/iojs/wwwroot/static_files/gif/image.gif'
~~~
#### path.isAbsolute(path)
判断`path`是否是一个绝对路径。一个绝对路径总是被解析为相同的路径,无论当前工作目录是哪里。
Posix例子:
~~~
path.isAbsolute('/foo/bar') // true
path.isAbsolute('/baz/..') // true
path.isAbsolute('qux/') // false
path.isAbsolute('.') // false
~~~
Windows例子:
~~~
path.isAbsolute('//server') // true
path.isAbsolute('C:/foo/..') // true
path.isAbsolute('bar\\baz') // false
path.isAbsolute('.') // false
~~~
#### path.relative(from, to)
解析从`from`到`to`的相对路径。
当我们有两个绝对路径,并且我们要得到它们间一个对于另外一个的相对路径。这实际上是`path.resolve`的相反操作。我们可以看看这是什么意思:
~~~
path.resolve(from, path.relative(from, to)) == path.resolve(to)
~~~
例子:
~~~
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` 命令相似。
例子:
~~~
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.coffee.md')
// returns
'.md'
path.extname('index.')
// returns
'.'
path.extname('index')
// returns
''
~~~
#### path.sep
返回特定平台的文件分隔符。`'\\'`或`'/'`。
一个*nix上的例子:
~~~
'foo/bar/baz'.split(path.sep)
// returns
['foo', 'bar', 'baz']
~~~
一个Windows上的例子:
~~~
'foo\\bar\\baz'.split(path.sep)
// returns
['foo', 'bar', 'baz']
~~~
#### path.delimiter
特定平台的路径分隔符,`';'`或`':'`。
一个*nix上的例子:
~~~
console.log(process.env.PATH)
// '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin'
process.env.PATH.split(path.delimiter)
// returns
['/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/bin']
~~~
一个Windows上的例子:
~~~
console.log(process.env.PATH)
// 'C:\Windows\system32;C:\Windows;C:\Program Files\iojs\'
process.env.PATH.split(path.delimiter)
// returns
['C:\\Windows\\system32', 'C:\\Windows', 'C:\\Program Files\\iojs\\']
~~~
#### path.parse(pathString)
根据一个路径字符串返回一个对象。
一个*nix上的例子:
~~~
path.parse('/home/user/dir/file.txt')
// returns
{
root : "/",
dir : "/home/user/dir",
base : "file.txt",
ext : ".txt",
name : "file"
}
~~~
一个Windows上的例子:
~~~
path.parse('C:\\path\\dir\\index.html')
// returns
{
root : "C:\\",
dir : "C:\\path\\dir",
base : "index.html",
ext : ".html",
name : "index"
}
~~~
#### path.format(pathObject)
根据一个对象,返回一个路径字符串,与`path.parse`相反。
~~~
path.format({
root : "/",
dir : "/home/user/dir",
base : "file.txt",
ext : ".txt",
name : "file"
})
// returns
'/home/user/dir/file.txt'
~~~
#### path.posix
提供对上述的路径方法的访问,但是总是以兼容posix的方式交互(interact)。
#### path.win32
提供对上述的路径方法的访问,但是总是以兼容win32的方式交互(interact)。
OS
最后更新于:2022-04-01 01:58:47
### 稳定度: 2 - 稳定
提供一些基本的操作系统相关的功能。
使用`require('os')`来获得这个模块。
#### os.tmpdir()
返回操作系统默认的临时文件目录。
#### os.homedir()
返回当前用户的家目录。
#### os.endianness()
返回CPU的字节序。`BE`为大端字节序,`LE`为小端字节序。
#### os.hostname()
返回当前操作系统的主机名。
#### os.type()
返回操作系统名。例如,Linux下为`'Linux'`,OS X下为`'Darwin'`,Windows下为`'Windows_NT'`。
#### os.platform()
返回操作系统平台。可能的值有`'darwin'`,`'freebsd'`,`'linux'`,`'sunos'`或`'win32'`。返回`process.platform`值。
#### os.arch()
返回操作系统CPU架构。可能的值有`'x64'`,`'arm'`和`'ia32'`。返回`process.arch`值。
#### os.release()
返回操作系统的发行版本。
#### os.uptime()
返回操作系统的运行时间(秒)。
#### os.loadavg()
返回一个包含1,5,15分钟平均负载的数组。
平均负载是一个系统活动测量,由操作系统计算并且由一个分数表示。根据经验,理想的负载均衡数应该比系统的逻辑CPU数小。
平均负载完全是一个`UNIX-y`概念;在Windows中没有完全对等的概念。所以在Windows下,这个函数总是返回`[0, 0, 0]`。
#### os.totalmem()
以字节的形式返回系统的总内存。
#### os.freemem()
以字节的形式返回系统的可用内存。
#### os.cpus()
返回一个包含安装的各个CPU/核心信息的对象数组:型号,速度(单位MHz),和时间(一个包含CPU/核心花费的毫秒数的对象:`user`,`nice`,`sys`,`idle`和`irq`)。
`os.cpus`例子:
~~~
[ { model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz',
speed: 2926,
times:
{ user: 252020,
nice: 0,
sys: 30340,
idle: 1070356870,
irq: 0 } },
{ model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz',
speed: 2926,
times:
{ user: 306960,
nice: 0,
sys: 26980,
idle: 1071569080,
irq: 0 } },
{ model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz',
speed: 2926,
times:
{ user: 248450,
nice: 0,
sys: 21750,
idle: 1070919370,
irq: 0 } },
{ model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz',
speed: 2926,
times:
{ user: 256880,
nice: 0,
sys: 19430,
idle: 1070905480,
irq: 20 } },
{ model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz',
speed: 2926,
times:
{ user: 511580,
nice: 20,
sys: 40900,
idle: 1070842510,
irq: 0 } },
{ model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz',
speed: 2926,
times:
{ user: 291660,
nice: 0,
sys: 34360,
idle: 1070888000,
irq: 10 } },
{ model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz',
speed: 2926,
times:
{ user: 308260,
nice: 0,
sys: 55410,
idle: 1071129970,
irq: 880 } },
{ model: 'Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz',
speed: 2926,
times:
{ user: 266450,
nice: 1480,
sys: 34920,
idle: 1072572010,
irq: 30 } } ]
~~~
注意因为`nice`值是UNIX中心的,所以在Windows中所有进程的`nice`值都将是`0`。
#### os.networkInterfaces()
获取一个网络接口列表:
~~~
{ lo:
[ { address: '127.0.0.1',
netmask: '255.0.0.0',
family: 'IPv4',
mac: '00:00:00:00:00:00',
internal: true },
{ address: '::1',
netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
family: 'IPv6',
mac: '00:00:00:00:00:00',
internal: true } ],
eth0:
[ { address: '192.168.1.108',
netmask: '255.255.255.0',
family: 'IPv4',
mac: '01:02:03:0a:0b:0c',
internal: false },
{ address: 'fe80::a00:27ff:fe4e:66a1',
netmask: 'ffff:ffff:ffff:ffff::',
family: 'IPv6',
mac: '01:02:03:0a:0b:0c',
internal: false } ] }
~~~
注意,由于底层系统实现的原因,它将只会返回被赋予一个地址的网络接口。
#### os.EOL
一个定义了对于操作系统,合适的行结束记号的常量。
Net
最后更新于:2022-04-01 01:58:45
### 稳定度: 2 - 稳定
`net`模块为你提供了异步的网络调用的包装。它同时包含了创建服务器和客户端的函数。你可以通过`require('net')`来引入这个模块。
#### net.createServer([options][, connectionListener])
创建一个新的TCP服务器。`connectionListener`参数会被自动绑定为`connection`事件的监听器。
`options`是一个包含下列默认值的对象:
~~~
{
allowHalfOpen: false,
pauseOnConnect: false
}
~~~
如果`allowHalfOpen`是`true`,那么当另一端的`socket`发送一个`FIN`报文时`socket`并不会自动发送`FIN`报文。`socket`变得不可读,但是可写。你需要明确地调用`end()`方法。详见`end`事件。
如果`pauseOnConnect`是true,那么`socket`在每一次被连接时会暂停,并且不会读取数据。这允许在进程间被传递的连接不读取任何数据。如果要让一个被暂停的`socket`开始读取数据,调用`resume()`方法。
以下是一个应答服务器的例子,监听8124端口:
~~~
var net = require('net');
var server = net.createServer(function(c) { //'connection' listener
console.log('client connected');
c.on('end', function() {
console.log('client disconnected');
});
c.write('hello\r\n');
c.pipe(c);
});
server.listen(8124, function() { //'listening' listener
console.log('server bound');
});
~~~
使用`telnet`测试:
~~~
telnet localhost 8124
~~~
想要监听`socket``/tmp/echo.sock`,只需改变倒数第三行:
~~~
server.listen('/tmp/echo.sock', function() { //'listening' listener
~~~
使用`nc`连接一个UNIX domain socket服务器:
~~~
nc -U /tmp/echo.sock
~~~
#### net.connect(options[, connectionListener])
#### net.createConnection(options[, connectionListener])
工厂函数,返回一个新的`net.Socket`实例,并且自动使用提供的`options`进行连接。
`options`会被同时传递给`net.Socket`构造函数和`socket.connect`方法。
参数`connectListener`将会被立即添加为`connect`事件的监听器。
下面是一个上文应答服务器的客户端的例子:
~~~
var net = require('net');
var client = net.connect({port: 8124},
function() { //'connect' listener
console.log('connected to server!');
client.write('world!\r\n');
});
client.on('data', function(data) {
console.log(data.toString());
client.end();
});
client.on('end', function() {
console.log('disconnected from server');
});
~~~
要连接`socket``/tmp/echo.sock`只需要改变第二行为:
~~~
var client = net.connect({path: '/tmp/echo.sock'});
~~~
#### net.connect(port[, host][, connectListener])
#### net.createConnection(port[, host][, connectListener])
工厂函数,返回一个新的`net.Socket`实例,并且自动使用指定的端口(port)和主机(host)进行连接。
如果`host`被省略,默认为`localhost`。
参数`connectListener`将会被立即添加为`connect`事件的监听器。
#### net.connect(path[, connectListener])
#### net.createConnection(path[, connectListener])
工厂函数,返回一个新的unix`net.Socket`实例,并且自动使用提供的路径(path)进行连接。
参数`connectListener`将会被立即添加为`connect`事件的监听器。
#### Class: net.Server
这个类用于创建一个TCP或本地服务器。
#### server.listen(port[, hostname][, backlog][, callback])
开始从指定端口和主机名接收连接。如果省略主机名,那么如果IPv6可用,服务器会接受从任何IPv6地址(::)来的链接,否则为任何IPv4地址(0.0.0.0)。如果端口为0那么将会为其设置一个随机端口。
积压量`backlog`是连接等待队列的最大长度。实际长度由你的操作系统的`sysctl`设置决定(如linux中的`tcp_max_syn_backlog`和`somaxconn`)。这个参数的默认值是511(不是512)。
这个函数式异步的。当服务器绑定了指定端口后,`listening`事件将会被触发。最后一个参数`callback`将会被添加为`listening`事件的监听器。
有些用户可能遇到的情况是收到`EADDRINUSE`错误。这意味着另一个服务器已经使用了该端口。一个解决的办法是等待一段时间后重试。
~~~
server.on('error', function (e) {
if (e.code == 'EADDRINUSE') {
console.log('Address in use, retrying...');
setTimeout(function () {
server.close();
server.listen(PORT, HOST);
}, 1000);
}
});
~~~
(注意,`io.js`中所有的`socket`都已经设置了`SO_REUSEADDR`)
#### server.listen(path[, callback])
- path String
- callback Function
启动一个本地`socket`服务器,监听指定路径(`path`)上的连接。
这个函数式异步的。当服务器监听了指定路径后,`listening`事件将会被触发。最后一个参数`callback`将会被添加为`listening`事件的监听器。
在UNIX中,`local domain`经常被称作`UNIX domain`。`path`是一个文件系统路径名。它在被创建时会受相同文件名约定(same naming conventions)的限制并且进行权限检查(permissions checks)。它在文件系统中可见,并且在被删除前持续存在。
在Windows中,`local doamin`使用一个命名管道(named pipe)实现。`path`必须指向`\\?\pipe\`或`\\.\pipe\.`中的一个条目,但是后者可能会做一些命名管道的处理,如处理`..`序列。除去表现,命名管道空间是平坦的(flat)。管道不会持续存在,它们将在最后一个它们的引用关闭后被删除。不要忘记,由于`JavaScript`的字符串转义,你必须在指定`path`时使用双反斜杠:
~~~
net.createServer().listen(
path.join('\\\\?\\pipe', process.cwd(), 'myctl'))
~~~
#### server.listen(handle[, callback])
- handle Object
- callback Function
`handle`对象可以被设置为一个服务器或一个`socket`(或者任意以下划线开头的成员`_handle`),或者一个`{fd: <n>}`对象。
这将使得服务器使用指定句柄接受连接,但它假设文件描述符或句柄已经被绑定至指定的端口或域名`socket`。
在Windows下不支持监听一个文件描述符。
这个函数式异步的。当服务器已被绑定后,`listening`事件将会被触发。最后一个参数`callback`将会被添加为`listening`事件的监听器。
#### server.listen(options[, callback])
-
**options Object**
- port Number 可选
- host String 可选
- backlog Number 可选
- path String 可选
- exclusive Boolean 可选
-
callback Function 可选
`port`,`host`和`backlog`属性,以及可选的`callback`函数,与`server.listen(port, [host], [backlog], [callback])`中表现一致。`path`可以被指定为一个UNIX `socket`。
如果`exclusive`是`false`(默认),那么工作集群(cluster workers)将会使用相同的底层句柄,处理的连接的职责将会被它们共享。如果`exclusive`是`true`,那么句柄是不被共享的,企图共享将得到一个报错的结果。下面是一个监听独有端口的例子:
~~~
server.listen({
host: 'localhost',
port: 80,
exclusive: true
});
~~~
#### server.close([callback])
使服务器停止接收新的连接并且保持已存在的连接。这个函数式异步的,当所有的连接都结束时服务器会最终关闭,并处罚一个`close`事件。可选的,你可以传递一个回调函数来监听`close`事件。如果传递了,那么它的唯一的第一个参数将表示任何可能潜在发生的错误。
#### server.address()
返回服务器绑定的地址,协议族名和端口通过操作系统报告。对查找操作系统分配的地址哪个端口被分配非常有用。返回一个有三个属性的对象。如`{ port: 12346, family: 'IPv4', address: '127.0.0.1' }`。
例子:
~~~
var server = net.createServer(function (socket) {
socket.end("goodbye\n");
});
// grab a random port.
server.listen(function() {
address = server.address();
console.log("opened server on %j", address);
});
~~~
在`listening`事件触发前,不要调用`server.address()`方法。
#### server.unref()
调用一个`server`对象的`unref`方法将允许如果它是事件系统中唯一活跃的服务器,程序将会退出。如果服务器已经被调用过这个方法,那么再次调用这个方法将不会有任何效果。
返回`server`对象。
#### server.ref()
与`unref`相反,在一个已经被调用`unref`方法的`server`中调用`ref`方法,那么如果它是唯一活跃的服务器时,程序将不会退出(默认)。如果服务器已经被调用过这个方法,那么再次调用这个方法将不会有任何效果。
返回`server`对象。
#### server.maxConnections
设置了这个属性后,服务器的连接数达到时将会开始拒绝连接。
一旦`socket`被使用`child_process.fork()`传递给了子进程,这个属性就不被推荐去设置。
#### server.connections
这个函数已经被弃用。请使用`server.getConnections()`替代。
服务器的当前连接数。
当使用`child_process.fork()`传递一个`socket`给子进程时,这个属性将变成`null`。想要得到正确的结果请使用`server.getConnections`。
#### server.getConnections(callback)
异步地去获取服务器的当前连接数,在`socket`被传递给子进程时仍然可用。
回调函数的两个参数是`err`和`count`。
### `net.Server`是一个具有以下事件的`EventEmitter`:
#### Event: 'listening'
当调用`server.listen`后,服务器已被绑定时触发。
#### Event: 'connection'
- Socket object 连接对象
当新的连接产生时触发。`socket`是一个`net.Socket`实例。
#### Event: 'close'
当服务器关闭时触发。注意如果服务器中仍有连接存在,那么这个事件会直到所有的连接都关闭后才触发。
#### Event: 'error'
- Error Object
当发生错误时触发。`close`事件将会在它之后立即触发。参阅`server.listen`。
#### Class: net.Socket
这个对象是一个TCP或本地`socket`的抽象。`net.Socket`实例实现了双工流(duplex Stream)接口。它可以被使用者创建,并且被作为客户端(配合`connect()`)使用。或者也可以被`io.js`创建,并且通过服务器的`connection`事件传递给使用者。
#### new net.Socket([options])
创建一个新的`socket`对象。
`options`是一个有以下默认值的对象:
~~~
{ fd: null
allowHalfOpen: false,
readable: false,
writable: false
}
~~~
`fd`允许你使用一个指定的已存在的`socket`文件描述符。设置`readable` 和/或 `writable`为`true`将允许从这个`socket`中读 和/或 写(注意,仅在传递了`passed`时可用)。关于`allowHalfOpen`,参阅`createServer()`和`end`事件。
#### socket.connect(options[, connectListener])
从给定的`socket`打开一个连接。
对于TCP`socket`,`options`参数需是一个包含以下属性的对象:
-
port: 客户端需要连接的端口(必选)。
-
host: 客户端需要连接的主机(默认:'localhost')
-
localAddress: 将要绑定的本地接口,为了网络连接。
-
localPort: 将要绑定的本地端口,为了网络连接。
-
family : IP协议族版本,默认为`4`。
-
lookup : 自定义查找函数。默认为`dns.lookup`。
对于本地domain `socket`,`options`参数需是一个包含以下属性的对象:
- path: 客户端需要连接的路径(必选)。
通常这个方法是不需要的,因为通过`net.createConnection`打开`socket`。只有在你自定义了`socket`时才使用它。
这个函数式异步的,当`connect`事件触发时,这个`socket`就被建立了。如果在连接的过程有问题,那么`connect`事件将不会触发,`error`将会带着这个异常触发。
`connectListener`参数会被自动添加为`connect`事件的监听器。
#### socket.connect(port[, host][, connectListener])
#### socket.connect(path[, connectListener])
参阅`socket.connect(options[, connectListener])`。
#### socket.bufferSize
`net.Socket`的属性,用于`socket.write()`。它可以帮助用户获取更快的运行速度。计算机不能一直保持大量数据被写入`socket`的状态,网络连接可以很慢。`io.js`在内部会排队等候数据被写入`socekt`并确保传输连接上的数据完好。 (内部实现为:轮询`socekt`的文件描述符等待它为可写)。
内部缓存的可能结果是内存使用会增长。这个属性展示了缓存中还有多少待写入的字符(字符的数目约等于要被写入的字节数,但是缓冲区可能包含字符串,而字符串是惰性编码的,所以确切的字节数是未知的)。
遇到数值很大或增长很快的`bufferSize`时,应当尝试使用`pause()`和`resume()`来控制。
#### socket.setEncoding([encoding])
设置`socket`的编码作为一个可读流。详情参阅`stream.setEncoding()`。
#### socket.write(data[, encoding][, callback])
在套接字上发送数据。第二个参数指定了字符串的编码,默认为UTF8。
如果所有数据成功被刷新至了内核缓冲区,则返回`true`。如果所有或部分数据仍然在用户内存中排队,则返回`false`。`drain`事件将会被触发当`buffer`再次为空时。
当数据最终被写入时,`callback`回调函数将会被执行,但可能不会马上执行。
#### socket.end([data][, encoding])
半关闭一个`socket`。比如,它发送一个`FIN`报文。可能服务器仍然在发送一些数据。
如果`data`参数被指定,那么等同于先调用`socket.write(data, encoding)`,再调用`socket.end()`。
#### socket.destroy()
确保这个`socket`上没有I/O活动发生。只在发生错误情况才需要(如处理错误)。
#### socket.pause()
暂停数据读取。`data`事件将不会再触发。对于控制上传非常有用。
#### socket.resume()
用于在调用`pause()`后,恢复数据读取。
#### socket.setTimeout(timeout[, callback])
如果`socket`在`timeout`毫秒中没有活动后,设置其为超时。默认情况下,`net.Socket`没有超时。
当超时发生,`socket`会收到一个`timeout`事件,但是连接将不会被断开。用户必须手动地调用`end()`或`destroy()`方法。
如果`timeout`是`0`,那么现有的超时将会被禁用。
可选的`callback`参数就会被自动添加为`timeout`事件的监听器。
返回一个`socket`。
#### socket.setNoDelay([noDelay])
警用纳格算法(Nagle algorithm)。默认情况下TCP连接使用纳格算法,它们的数据在被发送前会被缓存。设置`noDelay`为`true`将会在每次`socket.write()`时立刻发送数据。`noDelay`默认为`true`。
返回一个`socket`。
#### socket.setKeepAlive([enable][, initialDelay])
启用/警用长连接功能,并且在第一个在闲置`socket`的长连接`probe`被发送前,可选得设置初始延时。`enable`默认为`false`。
设定`initialDelay`(毫秒),来设定在收到的最后一个数据包和第一个长连接`probe`之间的延时。将`initialDelay`设成`0`会让值保持不变(默认值或之前所设的值)。默认为`0`。
返回一个`socket`。
#### socket.address()
返回绑定的地址,协议族名和端口通过操作系统报告。对查找操作系统分配的地址哪个端口被分配非常有用。返回一个有三个属性的对象。如`{ port: 12346, family: 'IPv4', address: '127.0.0.1' }`。
#### socket.unref()
调用一个`socket`对象的`unref`方法将允许如果它是事件系统中唯一活跃的`socket`,程序将会退出。如果`socket`已经被调用过这个方法,那么再次调用这个方法将不会有任何效果。
返回`socket`对象。
#### socket.ref()
与`unref`相反,在一个已经被调用`unref`方法的`socket`中调用`ref`方法,那么如果它是唯一活跃的`socket`时,程序将不会退出(默认)。如果`socket`已经被调用过这个方法,那么再次调用这个方法将不会有任何效果。
返回`socket`对象。
#### socket.remoteAddress
远程IP地址字符串。例如,`'74.125.127.100'`或`'2001:4860:a005::68'`。
#### socket.remoteFamily
远程IP协议族字符串。例如,`'IPv4'`或`'IPv6'`。
#### socket.remotePort
远程端口数值。例如,`80`或`21`。
#### socket.localAddress
远程客户端正连接的本地IP地址字符串。例如,如果你正在监听`'0.0.0.0'`并且客户端连接在`'192.168.1.1'`,其值将为`'192.168.1.1'`。
#### socket.localPort
本地端口数值。例如,`80`或`21`。
#### socket.bytesRead
接受的字节数。
#### socket.bytesWritten
发送的字节数。
### net.Socket `net.Socket`实例是一个包含以下事件的`EventEmitter`:
#### Event: 'lookup'
在解析主机名后,连接主机前触发。对UNIX `socket`不适用。
- err {Error | Null} 错误对象,参阅 `dns.lookup()`
- address {String} IP地址
- family {String | Null} 地址类型。参阅'dns.lookup()`
#### Event: 'connect'
在`socket`连接成功建立后触发。参阅`connect()`。
#### Event: 'data'
- Buffer object
在接受到数据后触发。参数将会是一个`Buffer`或一个字符串。数据的编码由`socket.setEncoding()`设置(更多详细信息请查看可读流章节)。
注意,当`socket`触发`data`事件时,如果没有监听器存在。那么数据将会丢失。
#### Event: 'end'
当另一端的`socket`发送一个`FIN`报文时触发。
默认情况(`allowHalfOpen == false`)下,一旦一个`socket`的文件描述符被从它的等待写队列(pending write queue)中写出,`socket`会销毁它。但是,当设定`allowHalfOpen == true`后,`socket`不会在它这边自动调用`end()`,允许用户写入任意数量的数据,需要注意的是用户需要在自己这边调用`end()`。
#### Event: 'timeout'
当`socket`因不活动而超时时触发。这只是来表示`socket`被限制。用户必须手动关闭连接。
参阅`socket.setTimeout()`。
#### Event: 'drain'
当写缓冲为空时触发。可以被用来控制上传流量。
参阅`socket.write()`的返回值。
#### Event: 'error'
- Error object
当发生错误时触发。`close`事件会紧跟着这个事件触发。
#### Event: 'close'
- had_error 如果`socket`有一个传输错误时为`true`
当`socket`完全关闭时触发。参数`had_error`是一个表示`socket`是否是因为传输错误而关闭的布尔值。
#### net.isIP(input)
测试`input`是否是一个IP地址。如果是不合法字符串时,会返回`0`。如果是IPv4地址则返回`4`,是IPv6地址则返回`6`。
#### net.isIPv4(input)
如果`input`是一个IPv4地址则返回`true`,否则返回`false`。
#### net.isIPv6(input)
如果`input`是一个IPv6地址则返回`true`,否则返回`false`。
Modules
最后更新于:2022-04-01 01:58:43
### 稳定度: 3 - 锁定
`io.js`又一个简单的模块加载系统。在`io.js`中,文件和模块是一一对应的。以下例子中,`foo.js`加载的同目录下的`circle.js`。
`foo.js`的内容:
~~~
var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is '
+ circle.area(4));
~~~
`circle.js`的内容:
~~~
var PI = Math.PI;
exports.area = function (r) {
return PI * r * r;
};
exports.circumference = function (r) {
return 2 * PI * r;
};
~~~
`circle.js`模块暴露了`area()`函数和`circumference()`函数。想要为你的模块添加函数或对象,你可以将它们添加至特殊的`exports`对象的属性上。
模块的本地变量是私有的,好似模块被包裹在一个函数中。在这个例子中变量`PI`是`circle.js`私有的。
如果想要你的模块暴露一个函数(例如一个构造函数),或者想要一次赋值就暴露一个完整的对象,而不是一次绑定一个属性,那就将之赋值给`module.exports`而不是`exports`。
以下,`bar.js`使用了暴露了一个构造函数的`square`模块:
~~~
var square = require('./square.js');
var mySquare = square(2);
console.log('The area of my square is ' + mySquare.area());
~~~
`square`模块内部:
~~~
// assigning to exports will not modify module, must use module.exports
module.exports = function(width) {
return {
area: function() {
return width * width;
}
};
}
~~~
模块系统在`require("module")`中被实现。
### 循环依赖
当存在循环的`require()`调用。一个模块可能在返回时,被没有被执行完毕。
考虑一下情况:
`a.js`:
~~~
console.log('a starting');
exports.done = false;
var b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');
~~~
`b.js`:
~~~
console.log('b starting');
exports.done = false;
var a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');
~~~
`main.js`:
~~~
console.log('main starting');
var a = require('./a.js');
var b = require('./b.js');
console.log('in main, a.done=%j, b.done=%j', a.done, b.done);
~~~
当`main.js`加载`a.js`,而后`a.js`会去加载`b.js`。与此同时,`b.js`尝试去加载`a.js`。为了避免一个无限循环,`a.js`会返回一个未完成的副本给`b.js`模块。`b.js`会接着完成加载,然后它所暴露的值再被提供给`a.js`模块。
这样`main.js`就完成了它们的加载。因此程序的输出是:
~~~
$ iojs main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done=true, b.done=true
~~~
如果在你的程序里有循环依赖,请确保它们按你的计划工作。
#### 核心模块
`io.js`中有一些模块是被编译成二进制的。这些模块会在本文档的其他地方详细讨论。
核心模块被定义在`io.js`源码的`lib/`目录下。
当被`require()`时,核心模块总是被优先加载的。例如`require('http')`总是会返回内建的HTTP模块,甚至是有一个同名文件时。
#### 文件模块
如果准确的文件名没有被发现,那么`io.js`将会依次添加`.js`,`.json`或`.node`后缀名,然后试图去加载。
`.js`文件被解释为`JavaScript`文本文件,`.json`被解释为`JSON`文本文件,`.node`文件被解释为编译好的插件模块,然后被`dlopen`加载。
前缀是`'/'`则是文件的绝对路径。例如`require('/home/marco/foo.js')`将会加载`/home/marco/foo.js`。
前缀是`'./'`则是调用`require()`的文件的相对路径。也就是说,`circle.js`必须与`foo.js`在同一目录下,这样`require('./circle')`才能找到它。
如果没有`'/'`,`'./'`或`'../'`前缀,模块要么是一个核心模块,或是需要从`node_modules`目录中被加载。
如果指定的路径不存在,`require()`将会抛出一个`code`属性是`'MODULE_NOT_FOUND'`的错误。
#### 从node_modules目录中加载
如果传递给`require()`的模块标识符不是一个本地模块,也没有以`'/'`,`'../'`或`'./'`开始。那么`io.js`将会从当前目录的父目录开始,添加`/node_modules`,试图从这个路径来加载模块。
如果还是没有找到模块,那么它会再移至此目录的父目录,如此往复,直至到达文件系统的根目录。
例如,如果一个位于`'/home/ry/projects/foo.js'`的文件调用了`require('bar.js')`,那么`io.js`将会按照以下的路径顺序来查找:
~~~
/home/ry/projects/node_modules/bar.js
/home/ry/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
~~~
这要求程序本地化(localize)自己的依赖,防止它们崩溃。
你也可以在模块名中加入一个路径后缀,来引用这个模块中特定的一个文件或子模块。例如,`require('example-module/path/to/file')`将会从`example-module`的位置解析相对路径`path/to/file`。路径后缀遵循相同的模块解析语义。
#### 作为模块的目录
在一个单独目录下组织程序和库,然后提供一个单独的入口,是非常便捷的。有三种方法,可以将目录作为`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`。
这就是`io.js`所能够了解`package.json`文件的程度。
如果目录中没有`package.json`文件,那么`io.js`将会视图去加载当前目录中的`index.js`或`index.node`。例如,如果在上面的例子中没有`package.json`,那么`require('./some-library')`将会试图加载:
~~~
./some-library/index.js
./some-library/index.node
~~~
#### 缓存
模块在第一次被加载后,会被缓存。这意味着,如果都解析到了相同的文件,每一次调用`require('foo')`都将会返回同一个对象。
多次调用`require('foo')`可能不会造成模块代码被执行多次。这是一个重要的特性。有了它,“部分完成”的对象也可以被返回,这样,传递依赖也能被加载,即使它们可能会造成循环依赖。
如果你想要一个模块被多次执行,那么就暴露一个函数,然后执行这个函数。
##### 模块缓存警告
模块的缓存依赖于它们被解析后的文件名。所以调用模块的位置不同,可以会解析出不同的文件名(比如需要从node_modules目录中加载)。所以不能保证`require('foo')`总是会返回相同的对象,因为它们可能被解析为了不同的文件。
#### module对象
- {Object}
每一个模块中,变量`module`是一个代表了当前模块的引用。为了方便,`module.exports`也可以通过模块作用域中的`exports`取得。`module`对象实际上不是全局的,而是每个模块本地的。
#### module.exports
- Object
`module.exports`对象是由模块系统创建的。有时这是难以接受的;许多人希望它们的模块是一些类的实例。如果需要这样,那么就将想要暴露的对象赋值给`module.exports`。注意,将想要暴露的对象传递给`exports`,将仅仅只会重新绑定(rebind)本地变量`exports`,所以不要这么做。
例如假设我们正在写一个叫做`a.js`的模块:
~~~
var EventEmitter = require('events').EventEmitter;
module.exports = new EventEmitter();
// Do some work, and after some time emit
// the 'ready' event from the module itself.
setTimeout(function() {
module.exports.emit('ready');
}, 1000);
~~~
那么在另一个文件中我们可以:
~~~
var a = require('./a');
a.on('ready', function() {
console.log('module a is ready');
});
~~~
主要,对`module.exports`的赋值必须立刻完成。它不能在任何的回调函数中完成。以下例子将不能正常工作:
`x.js`:
~~~
setTimeout(function() {
module.exports = { a: "hello" };
}, 0);
~~~
`y.js`:
~~~
var x = require('./x');
console.log(x.a);
~~~
#### exports快捷方式
`exports`变量是一个`module.exports`的引用。如果你将一个新的值赋予它,那么它将不再指向先前的那个值。
为了说明这个行为,将`require()`的实现假设为这样:
~~~
function require(...) {
// ...
function (module, exports) {
// Your module code here
exports = some_func; // re-assigns exports, exports is no longer
// a shortcut, and nothing is exported.
module.exports = some_func; // makes your module export 0
} (module, module.exports);
return module;
}
~~~
一个指导方针是,如果你弄不清楚`exports`和`module.exports`之间的关系,请只使用`module.exports`。
#### module.require(id)
- id String
- Return: 被解析的模块的`module.exports`
`module.require`方法提供了一种像`require()`一样,从源模块中加载模块的方法。
注意,为了这么做,你必须取得`module`对象的引用。因为`require()`返回`module.exports`,并且`module`对象是一个典型的只在特定的模块作用域中有效的变量,如果要使用它,必须被明确地导出。
#### module.id
- String
模块的识别符。通常是被完全解析的文件名。
#### module.filename
- String
模块完全解析后的文件名。
#### module.loaded
- Boolean
模块是否加载完成,或者是正在加载的过程中。
#### module.parent
- Module Object
引用这个模块的模块。
#### module.children
- Array
这个模块所引入的模块。
#### 总体来说
为了获得`require()`被调用时将要被加载的准确文件名,使用`require.resolve()`函数。
综上所述,以下是一个`require.resolve`所做的事的高级算法伪代码:
~~~
require(X) from module at path Y
1. If X is a core module,
a. return the core module
b. STOP
2. If X begins with './' or '/' or '../'
a. LOAD_AS_FILE(Y + X)
b. LOAD_AS_DIRECTORY(Y + X)
3. LOAD_NODE_MODULES(X, dirname(Y))
4. THROW "not found"
LOAD_AS_FILE(X)
1. If X is a file, load X as JavaScript text. STOP
2. If X.js is a file, load X.js as JavaScript text. STOP
3. If X.json is a file, parse X.json to a JavaScript Object. STOP
4. If X.node is a file, load X.node as binary addon. STOP
LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
a. Parse X/package.json, and look for "main" field.
b. let M = X + (json main field)
c. LOAD_AS_FILE(M)
2. If X/index.js is a file, load X/index.js as JavaScript text. STOP
3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
4. If X/index.node is a file, load X/index.node as binary addon. STOP
LOAD_NODE_MODULES(X, START)
1. let DIRS=NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
a. LOAD_AS_FILE(DIR/X)
b. LOAD_AS_DIRECTORY(DIR/X)
NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I >= 0,
a. if PARTS[I] = "node_modules" CONTINUE
c. DIR = path join(PARTS[0 .. I] + "node_modules")
b. DIRS = DIRS + DIR
c. let I = I - 1
5. return DIRS
~~~
#### 从全局文件夹加载
如果`NODE_PATH`环境变量被设置为了一个以冒号分割的绝对路径列表,那么在找不到模块时,`io.js`将会从这些路径中寻找模块(注意:在Windows中,`NODE_PATH`是以分号间隔的)。
`NODE_PATH`最初被创建,是用来支持在当前的模块解析算法被冻结(frozen)前,从不同的路径加载模块的。
`NODE_PATH`仍然被支持,但是,如今`io.js`生态圈已经有了放置依赖模块的公约,它已经不那么必要的。有时,当人们没有意识到`NODE_PATH`有被设置时,依赖于`NODE_PATH`的部署可能会产生出人意料的表现。有时,一个模块的依赖改变了,造成了通过`NODE_PATH`,加载了不同版本的模块。
另外,`io.js`将会查找以下路径:
- 1: $HOME/.node_modules
- 2: $HOME/.node_libraries
- 3: $PREFIX/lib/node
`$HOME`是用户的家目录,`$PREFIX`是`io.js`中配置的`node_prefix`。
由于一些历史原因,高度推荐你将依赖放入`node_modules`目录。它会被加载的更快,且可靠性更好。
#### 访问主模块
当一个文件直接由`io.js`执行,`require.main`将被设置为这个模块。这意味着你可以判断一个文件是否是直接被运行的。
~~~
require.main === module
~~~
对于一个文件`foo.js`,如果通过`iojs foo.js`运行,以上将会返回`true`。如果通过`require('./foo')`,将会返回`false`。
因为`module`提供了一个`filename`属性(通常等于`__filename`),所以当前应用的入口点可以通过检查`require.main.filename`来获取。
#### 附录:包管理小贴士
`io.js`的`require()`函数的语义被设计得足够通用,来支持各种目录结构。包管理程序诸如`dpkg`,`rpm`和`npm`将可以通过不修改`io.js`模块,来构建本地包。
以下我们给出一个建议的可行的目录结构:
假设`/usr/lib/node/<some-package>/<some-version>`中有指定版本包的内容。
包可以依赖于其他包。为了安装`foo`包,你可能需要安装特定版本的`bar`包。`bar`包可能有它自己的依赖,在一些情况下,它们的依赖可以会冲突或者产生循环。
由于`io.js`会查找任何它加载的包得真实路径(也就是说,解析`symlinks`),解析以下结构的方案非常简单:
- /usr/lib/node/foo/1.2.3/ - `foo`包的内容,`1.2.3`版本。
- /usr/lib/node/bar/4.3.2/ - `foo`包所依赖的`bar`包的内容。
- /usr/lib/node/foo/1.2.3/node_modules/bar - 指向`/usr/lib/node/bar/4.3.2/`的符号链接。
- /usr/lib/node/bar/4.3.2/node_modules/* - 指向`bar`包所依赖的包的符号链接。
因此,即使有循环依赖,或者依赖冲突,每个模块都能够获取它们使用的特定版本的依赖。
当`foo`包中的代码执行`require('bar')`,将会获得符号链接`/usr/lib/node/foo/1.2.3/node_modules/bar`指向的版本。接着,`bar`包种的代码执行`require('quux')`,它将会获得符号链接`/usr/lib/node/bar/4.3.2/node_modules/quux`指向的版本。
此外,为了优化模块查找的过程,我们将模块放在`/usr/lib/node_modules/<name>/<version>`而不是直接放在`/usr/lib/node`中。然后在找不到依赖时,`io.js`就不会一直去查找`/usr/node_modules`或`/node_modules`目录了。
为了让模块在`io.js`的REPL中可用,可能需要将`/usr/lib/node_modules`目录加入到`$NODE_PATH`环境变量。因为使用`node_modules`目录的模块查找都是使用相对路径,且基于调用`require()`的文件的真实路径,因此包本身可以在任何位置。