NodeJS学习笔记(二).js

一个人想着一个人 提交于 2020-03-30 03:23:39

1 Stream(数据流)
    当内存中无法一次装下需要处理的数据时,或者一边读取一边处理更加高效时,我们就需要用到
数据流。NodeJS中通过各种 Stream 来提供对数据流的操作。

(1)为数据来源创建一个只读数据流:
var rs = fs.createReadStream(src);  // 从src读取文件, 返回一个新的可读流对象
rs.on('data', function (chunk) {         //  触发'data'事件
 rs.pause();           //暂停触发'data'事件
 doSomething(chunk, function () {
  rs.resume();      //恢复触发'data'事件
 });
});
rs.on('end', function () {
 cleanUp();
});

(2)为数据目标创建一个只写数据流:
var rs = fs.createReadStream(src);
var ws = fs.createWriteStream(dst);
rs.on('data', function (chunk) {
 if (ws.write(chunk) === false) {
 rs.pause();
 }
});
rs.on('end', function () {
 ws.end();
});
ws.on('drain', function () {    //发生在write()方法被调用并返回false之后。此事件被触发说明内核缓冲区已空,再次写入是安全的。
 rs.resume();
});

以上代码实现了数据从只读数据流到只写数据流的搬运,并包括了防爆仓控制。因为这种使用场
景很多,例如上边的大文件拷贝程序,NodeJS直接提供了 .pipe 方法来做这件事情,其内部实
现方式与上边的代码类似。

2 File System(文件系统)

NodeJS通过 fs 内置模块提供对文件的操作。fs 模块提供的API基本上可以分为以下三类:
• 文件属性读写。
其中常用的有 fs.stat、fs.chmod、fs.chown 等等。
• 文件内容读写。
其中常用的有 fs.readFile、fs.readdir、fs.writeFile、fs.mkdir 等等。
• 底层文件操作。
其中常用的有 fs.open、fs.read、fs.write、fs.close 等等。

NodeJS最精华的异步IO模型在 fs 模块里有着充分的体现,例如上边提到的这些API都通过回调
函数传递结果。以 fs.readFile 为例:
fs.readFile(pathname, function (err, data) {
 if (err) {
 // Deal with error.
 } else {
 // Deal with data.
 }
});
如上边代码所示,基本上所有 fs 模块API的回调参数都有两个。第一个参数在有错误发生时等
于异常对象,第二个参数始终用于返回API方法执行结果。
此外,fs 模块的所有异步API都有对应的同步版本,用于无法使用异步操作时,或者同步操作
更方便时的情况。同步API除了方法名的末尾多了一个 Sync 之外,异常对象与执行结果的传递
方式也有相应变化。同样以 fs.readFileSync 为例:
try {
 var data = fs.readFileSync(pathname);
 // Deal with data.
 } catch (err) {
 // Deal with error.
}

3 Path(路径)
操作文件时难免不与文件路径打交道。NodeJS提供了 path 内置模块来简化路径相关操作,并提
升代码可读性。
(1) path.normalize:将传入的路径转换为标准路径,具体讲的话,除了解析路径中的 . 与 .. 外,还能去掉多余的斜
杠。如果有程序需要使用路径作为某些数据的索引,但又允许用户随意输入路径时,就需要使用
该方法保证路径的唯一性。以下是一个例子:
var cache = {};
function store(key, value) {
 cache[path.normalize(key)] = value;
}
store('foo/bar', 1);
store('foo//baz//../bar', 2);
console.log(cache); // => { "foo/bar": 2 }
注意: 标准化之后的路径里的斜杠在Windows系统下是 \,而在*nix系统下是 /。如果想保证任何
系统下都使用 / 作为路径分隔符的话,需要用 .replace(/\\/g, '/') 再替换一下标准
路径。

(2) path.join:将传入的多个路径拼接为标准路径。该方法可避免手工拼接路径字符串的繁琐,并且能在不同系
统下正确使用相应的路径分隔符。以下是一个例子:
path.join('foo/', 'baz/', '../bar'); // => "foo/bar"

(3) path.extname:该方法返回路径中的文件扩展名,即路径最低一级的目录中'.'字符后的任何字符串。如果路径最低一级的目录中没有'.' 或者只有'.',那么该方法返回一个空字符串。
当我们需要根据不同文件扩展名做不同操作时,该方法就显得很好用。以下是一个例子:
path.extname('foo/bar.js'); // => ".js"

4 遍历目录
遍历目录是操作文件时的一个常见需求。比如写一个程序,需要找到并处理指定目录下的所有JS
文件时,就需要遍历整个目录。
(1)遍历算法:
目录是一个树状结构,在遍历时一般使用深度优先+先序遍历算法。深度优先,意味着到达一个
节点后,首先接着遍历子节点而不是邻居节点。先序遍历,意味着首次到达了某节点就算遍历完
成,而不是最后一次返回某节点才算数。因此使用这种遍历方式时,下边这棵树的遍历顺序是 A
> B > D > E > C > F。
    A
   / \
  B   C
 / \   \
D   E   F

(2) 同步遍历:
function travel(dir, callback) {
 fs.readdirSync(dir).forEach(function (file) {
 var pathname = path.join(dir, file);
 if (fs.statSync(pathname).isDirectory()) {
  travel(pathname, callback);
 } else {
  callback(pathname);
     }
 });
}

(3) 异步遍历:
function travel(dir, callback, finish) {
fs.readdir(dir, function (err, files) {
(function next(i) {
if (i < files.length) {
var pathname = path.join(dir, files[i]);
fs.stat(pathname, function (err, stats) {
if (stats.isDirectory()) {
travel(pathname, callback, function () {
next(i + 1);
});
} else {
callback(pathname, function () {
next(i + 1);
});
}
});
} else {
 finish && finish();
}
}(0));
});
}

5 文本编码:

(1) 常用的文本编码有 UTF8 和 GBK 两种,并且 UTF8 文件还可能带有BOM。在读取不同
编码的文本文件时,需要将文件内容转换为JS使用的 UTF8 编码字符串后才能正常处理。
 可以根据文本文件头几个字节等于啥来判断文件是否包含BOM,以及使用哪种
Unicode编码。但是,BOM字符虽然起到了标记文件编码的作用,其本身却不属于文件内容的一
部分,如果读取文本文件时不去掉BOM,在某些使用场景下就会有问题。例如我们把几个JS文
件合并成一个文件后,如果文件中间含有BOM字符,就会导致浏览器JS语法错误。因此,使用
NodeJS读取文本文件时,一般需要去掉BOM。以下代码实现了识别和去除UTF8 BOM的
功能:
function readText(pathname) {
 var bin = fs.readFileSync(pathname);
 if (bin[0] === 0xEF && bin[1] === 0xBB && bin[2] === 0xBF) {
   bin = bin.slice(3);
 }
 return bin.toString('utf-8');
}

(2) GBK转UTF8
NodeJS支持在读取文本文件时,或者在 Buffer 转换为字符串时指定文本编码,但遗憾的是,
GBK编码不在NodeJS自身支持范围内。因此,一般我们借助 iconv-lite 这个三方包来转换编
码。使用NPM下载该包后,我们可以按下边方式编写一个读取GBK文本文件的函数。

var iconv = require('iconv-lite');
function readGBKText(pathname) {
 var bin = fs.readFileSync(pathname);
 return iconv.decode(bin, 'gbk');
}

(3) 单字节编码:即使一个文本文件中有中文等字符,如果我们需要处理的字符仅在ASCII0~128范围
内,比如除了注释和字符串以外的JS代码,我们就可以统一使用单字节编码来读取文件,不用关
心文件的实际编码是GBK还是UTF8。
1. GBK编码源文件内容:
var foo = '中文';
2. 对应字节:
76 61 72 20 66 6F 6F 20 3D 20 27 D6 D0 CE C4 27 3B
3. 使用单字节编码读取后得到的内容:
var foo = '{乱码}{乱码}{乱码}{乱码}';
4. 替换内容:
var bar = '{乱码}{乱码}{乱码}{乱码}';
5. 使用单字节编码保存后对应字节:
76 61 72 20 62 61 72 20 3D 20 27 D6 D0 CE C4 27 3B
6. 使用GBK编码读取后得到内容:
var bar = '中文';
这里的诀窍在于,不管大于0xEF的单个字节在单字节编码下被解析成什么乱码字符,使用同样
的单字节编码保存这些乱码字符时,背后对应的字节保持不变。

NodeJS中自带了一种 binary 编码可以用来实现这个方法,因此在下例中,我们使用这种编码
来演示上例对应的代码该怎么写。
function replace(pathname) {
 var str = fs.readFileSync(pathname, 'binary');
 str = str.replace('foo', 'bar');
 fs.writeFileSync(pathname, str, 'binary');
}

总结:
• 学好文件操作,编写各种程序都不怕。
• 如果不是很在意性能,fs 模块的同步API能让生活更加美好。
• 需要对文件读写做到字节级别的精细控制时,请使用 fs 模块的文件底层操作API。
• 不要使用拼接字符串的方式来处理路径,使用 path 模块。
• 掌握好目录遍历和文件编码处理技巧,很实用。


      进程管理

1 任何一个进程都有启动进程时使用的命令行参数,有标准输入标准输出,有运行权限,有运行环
境和运行状态。在NodeJS中,可以通过 process 对象感知和控制NodeJS自身进程的方方面面。
另外需要注意的是,process 不是内置模块,而是一个全局对象,因此在任何地方都可以直接
使用。

在NodeJS中可以通过 process.argv 获取命令行参数。但是比较意外的是,node 执行程序路
径和主模块文件路径固定占据了 argv[0] 和 argv[1] 两个位置,而第一个命令行参数从
argv[2] 开始。为了让 argv 使用起来更加自然,可以按照以下方式处理。
function main(argv) {
// ...
}
main(process.argv.slice(2));

2 使用 child_process 模块可以创建和控制子进程。该模块提供的API中最核心的是 .spawn,
其余API都是针对特定使用场景对它的进一步封装,算是一种语法糖。

3 cluster 模块是对 child_process 模块的进一步封装,专用于解决单进程NodeJS Web服务器
无法充分利用多核CPU的问题。使用该模块可以简化多进程服务器程序的开发,让每个核上运行
一个工作进程,并统一通过主进程监听端口和分发请求。

4 NodeJS程序的标准输入流(stdin)、一个标准输出流(stdout)、一个标准错误流(stderr)分别
对应 process.stdin、process.stdout 和 process.stderr,第一个是只读数据流,后
边两个是只写数据流,对它们的操作按照对数据流的操作方式即可。

5
> var t = new Date();   #=> Tue Mar 25 2014 08:41:27 GMT+0000 (UTC)

> var k = new Date();  #=>  Tue Mar 25 2014 08:41:58 GMT+0000 (UTC)

> k - t  #=> 30816  (毫秒)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!