1.模块系统
为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统。模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。
创建模块
在 Node.js 中,创建一个模块非常简单,如下我们创建一个 'main.js' 文件,代码如下:
var hello = require('./hello'); hello.world();
以上实例中,代码 require('./hello') 引入了当前目录下的hello.js文件(./ 为当前目录,node.js默认后缀为js)。
Node.js 提供了exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。
接下来我们就来创建hello.js文件,代码如下:
exports.world = function() { console.log('Hello World'); }
在以上示例中,hello.js 通过 exports 对象把 world 作为模块的访问接口,在 main.js 中通过 require('./hello') 加载这个模块,然后就可以直接访 问 hello.js 中 exports 对象的成员函数了。
有时候我们只是想把一个对象封装到模块中,格式如下:
module.exports = function() { // ... }
例如:
//hello.js function Hello() { var name; this.setName = function(thyName) { name = thyName; }; this.sayHello = function() { console.log('Hello ' + name); }; }; module.exports = Hello;
这样就可以直接获得这个对象了:
//main.js var Hello = require('./hello'); hello = new Hello(); hello.setName('BYVoid'); hello.sayHello();
模块接口的唯一变化是使用 module.exports = Hello 代替了exports.world = function(){}。 在外部引用该模块时,其接口对象就是要输出的 Hello 对象本身,而不是原先的 exports。
服务端的模块放在哪里
也许你已经注意到,我们已经在代码中使用了模块了。像这样:
var http = require("http"); ... http.createServer(...);
Node.js中自带了一个叫做"http"的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。
这把我们的本地变量变成了一个拥有所有 http 模块所提供的公共方法的对象。
Node.js 的 require方法中的文件查找策略如下:
由于Node.js中存在4类模块(原生模块和3种文件模块),尽管require方法极其简单,但是内部的加载却是十分复杂的,其加载优先级也各自不同。如下图所示:
从文件模块缓存中加载
尽管原生模块与文件模块的优先级不同,但是都不会优先于从文件模块的缓存中加载已经存在的模块。
从原生模块加载
原生模块的优先级仅次于文件模块缓存的优先级。require方法在解析文件名之后,优先检查模块是否在原生模块列表中。以http模块为例,尽管在目录下存在一个http/http.js/http.node/http.json文件,require("http")都不会从这些文件中加载,而是从原生模块中加载。
原生模块也有一个缓存区,同样也是优先从缓存区加载。如果缓存区没有被加载过,则调用原生模块的加载方式进行加载和执行。
从文件加载
当文件模块缓存中不存在,而且不是原生模块的时候,Node.js会解析require方法传入的参数,并从文件系统中加载实际的文件,加载过程中的包装和编译细节在前一节中已经介绍过,这里我们将详细描述查找文件模块的过程,其中,也有一些细节值得知晓。
require方法接受以下几种参数的传递:
- http、fs、path等,原生模块。
- ./mod或../mod,相对路径的文件模块。
- /pathtomodule/mod,绝对路径的文件模块。
- mod,非原生模块的文件模块。
2.Buffer(缓冲区)
JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。但在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。
在 Node.js 中,Buffer 类是随 Node 内核一起发布的核心库。Buffer 库为 Node.js 带来了一种存储原始数据的方法,可以让 Node.js 处理二进制数据,每当需要在 Node.js 中处理I/O操作中移动的数据时,就有可能使用 Buffer 库。原始数据存储在 Buffer 类的实例中。一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存。
Buffer
类是一个全局变量类型,用来直接处理二进制数据的。 它能够使用多种方式构建。
创建Buffer的方法:
(1)、Buffer.alloc(size[, fill[, encoding]])
用于创建一定长度的Buffer,必须参数size,Buffer的长度;可选参数fill,Buffer的填充值;encoding:编码格式,如UTF-8;
let buf = Buffer.alloc(10,2,'utf-8'); console.log(buf); //<Buffer 02 02 02 02 02 02 02 02 02 02> console.log(buf.length); //10
(2)、Buffer.from(array);
通过一个八位字节的 array
创建一个新的 Buffer
。
let buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]); console.log(buf); //<Buffer 62 75 66 66 65 72> console.log(buf.length); //6 console.log(buf.toString()); //buffer
(3)、Buffer.from(string[, encoding]);
新建一个包含所给的 JavaScript 字符串 string
的 Buffer
。 encoding
参数指定 string
的字符编码。
let buf = Buffer.from('zhangsan'); console.log(buf); //<Buffer 7a 68 61 6e 67 73 61 6e> console.log(buf.length); //8 console.log(buf.toString()); //zhangsan
写入缓存区:
(1)、 buf.write(string[, offset[, length]][, encoding]) 根据 encoding
的字符编码写入 string
到 buf
中的 offset
位置。 length
参数是写入的字节数。 如果 buf
没有足够的空间保存整个字符串,则只会写入 string
的一部分。 只部分解码的字符不会被写入。
let buf = Buffer.alloc(10); let len = buf.write('zae',3,2,'utf-8'); // 写入的内容,写入的位置,写入的长度,编码格式 console.log(buf); //<Buffer 00 00 00 7a 61 00 00 00 00 00> 由于写入的长度为2,则e没有被写入 console.log(len); //2
读取缓存区数据:
(1)、buf.toString([encoding[, start[, end]]])
根据 encoding
指定的字符编码解码 buf
成一个字符串。 start
和 end
可传入用于只解码 buf
的一部分。
let buf = Buffer.alloc(10); for(let i = 0;i<5;i++){ // 97 是 'a' 的十进制 ASCII 值 buf[i] = i+97; } console.log(buf.toString()); //abcde console.log(buf.toString('utf-8',2,4)); //cd
将Buffer转成JSON对象:
(1)、buf.toJSON();
返回 buf
的 JSON 格式。 当字符串化一个 Buffer
实例时,JSON.stringify()
会隐式地调用该函数。
let buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]); console.log(buf.toJSON()); //{ type: 'Buffer', data: [ 1, 2, 3, 4, 5 ] }
缓冲区合并:
(1)、Buffer.concat(list[, totalLength]);
list
<Array> 要合并的Buffer
或Uint8Array
实例的数组totalLength
<integer> 合并时list
中Buffer
实例的总长度- 返回: <Buffer>
let buf1 = Buffer.alloc(10); let buf2 = Buffer.alloc(10); let buf3 = Buffer.alloc(10); let buf4 = Buffer.concat([buf1,buf2,buf3]); console.log(buf4.length); //30
缓冲区比较:
(2)、Buffer.compare(buf1, buf2);
比较 buf1
和 buf2
,通常用于 Buffer
实例数组的排序。 相当于调用 buf1.compare(buf2)
。
let buffer1 = new Buffer('ABC'); let buffer2 = new Buffer('ABCD'); let result = Buffer.compare(buffer1,buffer2); console.log(result); //-1 if(result < 0) { console.log(buffer1 + " 在 " + buffer2 + "之前"); }else if(result === 0){ console.log(buffer1 + " 与 " + buffer2 + "相同"); }else { console.log(buffer1 + " 在 " + buffer2 + "之后"); }
3.Stream(流):
Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。例如,对http 服务器发起请求的request 对象就是一个 Stream,还有stdout(标准输出)。
Node.js,Stream 有四种流类型:
Readable - 可读操作。
Writable - 可写操作。
Duplex - 可读可写操作.
Transform - 操作被写入数据,然后读出结果。
所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:
data - 当有数据可读时触发。
end - 没有更多的数据可读时触发。
error - 在接收和写入过程中发生错误时触发。
finish - 所有数据已被写入到底层系统时触发。
从流中读取数据:
创建一个 input.txt 文件
var fs = require("fs"); var data = ''; // 创建可读流 var readerStream = fs.createReadStream('input.txt'); // 设置编码为 utf8。 readerStream.setEncoding('UTF8'); // 处理流事件 --> data, end, and error readerStream.on('data', function(chunk) { data += chunk; }); readerStream.on('end',function(){ console.log(data); }); readerStream.on('error', function(err){ console.log(err.stack); }); console.log("程序执行完毕");
写入流:
var fs = require("fs"); var data = '写入文件'; // 创建一个可以写入的流,写入到文件 output.txt 中 var writerStream = fs.createWriteStream('output.txt'); // 使用 utf8 编码写入数据 writerStream.write(data,'UTF8'); // 标记文件末尾 writerStream.end(); // 处理流事件 --> data, end, and error writerStream.on('finish', function() { console.log("写入完成。"); }); writerStream.on('error', function(err){ console.log(err.stack); }); console.log("程序执行完毕");执行后会自动创建一个 output.txt 文件
管道流:
管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。如上面的图片所示,我们把文件比作装水的桶,而水就是文件里的内容,我们用一根管子(pipe)连接两个桶使得水从一个桶流入另一个桶,这样就慢慢的实现了大文件的复制过程。
以下实例我们通过读取一个文件内容并将内容写入到另外一个文件中。
var fs = require("fs"); // 创建一个可读流 var readerStream = fs.createReadStream('input.txt'); // 创建一个可写流 var writerStream = fs.createWriteStream('output.txt'); // 管道读写操作 // 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中 readerStream.pipe(writerStream); console.log("程序执行完毕");
链式流:
链式是通过连接输出流到另外一个流并创建多个对个流操作链的机制。链式流一般用于管道操作。
接下来我们就是用管道和链式来压缩和解压文件。
var fs = require("fs"); var zlib = require('zlib'); // 压缩 input.txt 文件为 input.txt.gz fs.createReadStream('input.txt') .pipe(zlib.createGzip()) .pipe(fs.createWriteStream('input.txt.gz')); console.log("文件压缩完成。");
执行后会出现一个压缩文件
4.调试:
调试方法:
- console.log
- 断点调试:node debugger 或node inspector 或vscode
- 测试驱动开发