简介
- nodejs中重要的API:处理进程(stdio)的stdin以及stdout相关的API还有文件系统FS的相关api
- 之前介绍过 node通过使用回调和事件机制来实现并发,现在这些api 会首次接触到基于非阻塞事件的io编程中的流控制
- 搭建一个简单的命令行文件浏览器,功能是允许用户读取和创建文件
需求
-定义需求
程序需要在命令行运行,意味着程序得通过node命令执行,或者直接执行,然后通过终端提供交互给用户进行输入和输出
启动后显示当前目录下列表
选择文件时,显示文件的内容
选择目录时,显示目录下信息
运行结束后退出
-
根据需求我们需要做到以下几点
- 创建模块
- 决定采用同步的fs 还是异步的fs
- 理解什么是流 stream
- 实现输入输出
- 重构
- 使用fs进行文件交互
- 完成
编写首个 node程序
创建模块
- 先创建文件夹 file-xeplorer
- 在内部创建 package.json
/这里版本号遵循semver 的版本控制标准/
{
"name": "file-explorer",
"version": "0.0.1",
"description": "一个命令行文件资源管理器",
"dependencies": {}
}
- 通过命令行输入
npm install
来验证package.json是否有效 - 接着创建index.js
同步还是异步
- 我们从生命依赖关系开始,由于stdio Api 是一个全局的process对象的一部分,所以我们这里为一个依赖就是fs
/*
* 模块依赖
* */
const fs = require('fs');
//同步版本
console.log(fs.readdirSync('.'));
[图片上传失败...(image-3124cc-1533372113516)]
- index.js
/*
* 模块依赖
* */
const fs = require('fs');
fs.readdir(__dirname, (err, files) =>{
console.log(files);
});
什么是流 stream
console.log('hello world');
console.log('-----------------');//换行
process.stdout.write('hello world');
process.stdout.write('------------');//不换行
process全局对象包含了三个流对象
-
- stdin : 标准输入
-
- stdout :标准输出
-
- stderr : 标准错误
[图片上传失败...(image-dca6c7-1533372113516)]
- stderr : 标准错误
第一个stdin 是一个可读流,而stdout和stderr都是可写流
-
stdin流默认的状态是暂停的,通常,执行一个程序,程序会做一些处理,然后退出, 在这里程序需要纸质处在运行状态来接收用户输入的数据
-
当恢复那个流的时候,node会观察对应的文件描述(unix状态下为0),随后宝石时间循环的运行,同时保持程序不退出,等待事件的触发,除非有io等待,否则nodejs总是会自动退出
输入和输出
- 这里我们写出第一部分,列出当前目标路下的问文件 然后等待用户输入
/*
* 模块依赖
* */
const fs = require('fs');
fs.readdir(process.cwd(), function (err, files) {
console.log('');
if (!files.length) {
return console.log(' \033[31m 没有文件显示 !\033[39m\n');
}
console.log(' 选择您想要查看的文件或目录\n');
function file(i) {
let filename = files[i];
fs.stat(__dirname + '/' + filename, function (err, stat) {
if (stat.isDirectory()) {
console.log(' ' + i + ' \033[36m' + filename + '/\033[39m');
} else {
console.log(' ' + i + ' \033[36m' + filename + '\033[39m');
}
i++;
if (i == files.length) {
console.log('');
process.stdout.write(' \033[33m输入你的选择: \033[39m');
process.stdin.resume();
process.stdin.serEncoding('utf8');
} else {
file(i)
}
});
}
file(0);
});
- 下面我们来分析上面的代码
- 为了输出更加友好我们首先输出一个空行:
console.log('')
- 如果files数组为空,告知用户当前目录没有文件 文本周围的
\033[31m
和\033[39m
为了让文本呈现为红色,例子中得\n
也是为了输出友好console.log(' \033[31m 没有文件显示 !\033[39m\n')
- 在输出查看语句后
- 定义了一个函数,数组中每个元素都会执行这个额函数,这里也出现贯穿始终的异步流程控制模式,出阿航执行,
function file(i){....}
- 然后,现货区文件名,在查看文件名对应的路径情况,fs.stat会个提出文件或者目录的元数据
let filename = files[i];fs.stat(__dirname + '/' + filename, function (err, stat)..
- 回调函数中,同时还给出了错误对象和一个stat对象,背离中使用到的stat对象上的方法是
isDirectory
- 如果路径所代表的是目录,我们就用有别于文件的颜色表示出来
- 下面就是流控制中得核心部分了
-计数器不断递增,同时检查是否还有为处理的文件 - 如果所有文件都处理完了 此时提示用户进行选择,注意
- 这里用的是
process.stdout.weite
而不是cl,这样就无需换行可以直接在提示语后面输入 - 这里
process.stdin.resume()
就是等待用户输入 - 后面是设置流编码为utf8
- 如果还有未处理的文件则用递归调用函数进行处理
- 直到列出所有文件,用户输入完毕,紧接着进行下一步串行处理
- 重要模式: 串行处理
重构
- 要做重构,我们从创建快捷变量开始
- [图片上传失败...(image-6e8c61-1533372113516)]
- 由于我们书写的代码是异步,会有问题,随着韩数量的增长,过多的函数嵌套会让程序的可读性变差
- 为此我们可以为每一个异步操作预先定一个一个函数
- 抽取函数
- [图片上传失败...(image-85c6-1533372113516)]
- [图片上传失败...(image-582698-1533372113516)]
- 读取用户输入后,接下来要做的就是根据用户输入做出相应处理,用户需要选择要读取的文件,所以代码层,我们设置了stdin的编码后,开始监听器data时间:
function read() {
console.log('');
stdout.write(' \033[33m输入你的选择: \033[39m');
stdin.resume();
stdin.setEncoding('utf8');
stdin.on('data', option);
}
function option(data) {
if (!files[Number(data)]) {
stdout.write(' \031[33m输入你的选择: \033[39m')
} else {
stdin.pause();
}
}
- 这里我们检查用户的输入是否匹配files数组的下表,files数组是fs.readdir回调函数中的一部分,上述代码中,我们将utf-8编码字符串转换为了Number类型来方便检查
- 现在我们已经能定位文件了那就开始读取他
function option(data) {
const filename = files[Number(data)];
if (!filename) {
stdout.write(' \033[33m输入你的选择: \033[39m')
} else {
stdin.pause();
fs.readFile(__dirname + '/' + filename, 'utf8', function (err, data) {
console.log('');
console.log('\033[90m'+data.replace(/(.*)/g, ' $1')+'\033[39m');
});
}
}
- 这样我们已经可以读取一个文件了
- 但是读取文件夹的时候就会出错,在这种情况下,我们就将其目录下的文件列表显示出来,
- 为了避免再次执行fs.stat 我们在file函数中,将stat对象保存下来
至此我们就完成了首个查看文件和文件夹内容的程序,虽然很简陋
/*
* 模块依赖
* */
const fs = require('fs'), stdin = process.stdin, stdout = process.stdout;
fs.readdir(process.cwd(), function (err, files) {
console.log('');
if (!files.length) {
return console.log(' \033[31m 没有文件显示 !\033[39m\n');
}
console.log(' 选择您想要查看的文件或目录\n');
let stats = [];
function file(i) {
let filename = files[i];
fs.stat(__dirname + '/' + filename, function (err, stat) {
stats[i] = stat;
if (stat.isDirectory()) {
console.log(' ' + i + ' \033[36m' + filename + '/\033[39m');
} else {
console.log(' ' + i + ' \033[36m' + filename + '\033[39m');
}
if (++i === files.length) {
read();
} else {
file(i)
}
});
}
function read() {
console.log('');
stdout.write(' \033[33m输入你的选择: \033[39m');
stdin.resume();
// stdin.setEncoding('utf8');
stdin.on('data', option);
}
function option(data) {
const filename = files[Number(data)];
if (!filename) {
stdout.write(' \033[33m输入你的选择: \033[39m')
} else {
stdin.pause();
if (stats[Number(data)].isDirectory()) {
fs.readdir(__dirname + '/' + filename, function (err, files) {
console.log('');
console.log(' (' + files.length + ' files)');
files.forEach(function (file) {
console.log(' - ' + file);
});
console.log('');
});
} else {
fs.readFile(__dirname + '/' + filename, 'utf8', function (err, data) {
console.log('');
console.log('\033[90m' + data.replace(/(.*)/g, ' $1') + '\033[39m');
});
}
}
}
file(0);
});