簡(jiǎn)介
- nodejs中重要的API:處理進(jìn)程(stdio)的stdin以及stdout相關(guān)的API還有文件系統(tǒng)FS的相關(guān)api
- 之前介紹過(guò) node通過(guò)使用回調(diào)和事件機(jī)制來(lái)實(shí)現(xiàn)并發(fā),現(xiàn)在這些api 會(huì)首次接觸到基于非阻塞事件的io編程中的流控制
- 搭建一個(gè)簡(jiǎn)單的命令行文件瀏覽器,功能是允許用戶讀取和創(chuàng)建文件
需求
-定義需求
程序需要在命令行運(yùn)行,意味著程序得通過(guò)node命令執(zhí)行,或者直接執(zhí)行,然后通過(guò)終端提供交互給用戶進(jìn)行輸入和輸出
啟動(dòng)后顯示當(dāng)前目錄下列表
選擇文件時(shí),顯示文件的內(nèi)容
選擇目錄時(shí),顯示目錄下信息
運(yùn)行結(jié)束后退出
-
根據(jù)需求我們需要做到以下幾點(diǎn)
- 創(chuàng)建模塊
- 決定采用同步的fs 還是異步的fs
- 理解什么是流 stream
- 實(shí)現(xiàn)輸入輸出
- 重構(gòu)
- 使用fs進(jìn)行文件交互
- 完成
編寫首個(gè) node程序
創(chuàng)建模塊
- 先創(chuàng)建文件夾 file-xeplorer
- 在內(nèi)部創(chuàng)建 package.json
/這里版本號(hào)遵循semver 的版本控制標(biāo)準(zhǔn)/
{
"name": "file-explorer",
"version": "0.0.1",
"description": "一個(gè)命令行文件資源管理器",
"dependencies": {}
}
- 通過(guò)命令行輸入
npm install來(lái)驗(yàn)證package.json是否有效 - 接著創(chuàng)建index.js
同步還是異步
- 我們從生命依賴關(guān)系開始,由于stdio Api 是一個(gè)全局的process對(duì)象的一部分,所以我們這里為一個(gè)依賴就是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全局對(duì)象包含了三個(gè)流對(duì)象
- stdin : 標(biāo)準(zhǔn)輸入
- stdout :標(biāo)準(zhǔn)輸出
- stderr : 標(biāo)準(zhǔn)錯(cuò)誤
[圖片上傳失敗...(image-dca6c7-1533372113516)]
- stderr : 標(biāo)準(zhǔn)錯(cuò)誤
第一個(gè)stdin 是一個(gè)可讀流,而stdout和stderr都是可寫流
stdin流默認(rèn)的狀態(tài)是暫停的,通常,執(zhí)行一個(gè)程序,程序會(huì)做一些處理,然后退出, 在這里程序需要紙質(zhì)處在運(yùn)行狀態(tài)來(lái)接收用戶輸入的數(shù)據(jù)
當(dāng)恢復(fù)那個(gè)流的時(shí)候,node會(huì)觀察對(duì)應(yīng)的文件描述(unix狀態(tài)下為0),隨后寶石時(shí)間循環(huán)的運(yùn)行,同時(shí)保持程序不退出,等待事件的觸發(fā),除非有io等待,否則nodejs總是會(huì)自動(dòng)退出
輸入和輸出
- 這里我們寫出第一部分,列出當(dāng)前目標(biāo)路下的問(wèn)文件 然后等待用戶輸入
/*
* 模塊依賴
* */
const fs = require('fs');
fs.readdir(process.cwd(), function (err, files) {
console.log('');
if (!files.length) {
return console.log(' \033[31m 沒(méi)有文件顯示 !\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);
});
- 下面我們來(lái)分析上面的代碼
- 為了輸出更加友好我們首先輸出一個(gè)空行:
console.log('') - 如果files數(shù)組為空,告知用戶當(dāng)前目錄沒(méi)有文件 文本周圍的
\033[31m和\033[39m為了讓文本呈現(xiàn)為紅色,例子中得\n也是為了輸出友好console.log(' \033[31m 沒(méi)有文件顯示 !\033[39m\n') - 在輸出查看語(yǔ)句后
- 定義了一個(gè)函數(shù),數(shù)組中每個(gè)元素都會(huì)執(zhí)行這個(gè)額函數(shù),這里也出現(xiàn)貫穿始終的異步流程控制模式,出阿航執(zhí)行,
function file(i){....}- 然后,現(xiàn)貨區(qū)文件名,在查看文件名對(duì)應(yīng)的路徑情況,fs.stat會(huì)個(gè)提出文件或者目錄的元數(shù)據(jù)
let filename = files[i];fs.stat(__dirname + '/' + filename, function (err, stat).. - 回調(diào)函數(shù)中,同時(shí)還給出了錯(cuò)誤對(duì)象和一個(gè)stat對(duì)象,背離中使用到的stat對(duì)象上的方法是
isDirectory - 如果路徑所代表的是目錄,我們就用有別于文件的顏色表示出來(lái)
- 下面就是流控制中得核心部分了
-計(jì)數(shù)器不斷遞增,同時(shí)檢查是否還有為處理的文件 - 如果所有文件都處理完了 此時(shí)提示用戶進(jìn)行選擇,注意
- 這里用的是
process.stdout.weite而不是cl,這樣就無(wú)需換行可以直接在提示語(yǔ)后面輸入 - 這里
process.stdin.resume()就是等待用戶輸入 - 后面是設(shè)置流編碼為utf8
- 如果還有未處理的文件則用遞歸調(diào)用函數(shù)進(jìn)行處理
- 直到列出所有文件,用戶輸入完畢,緊接著進(jìn)行下一步串行處理
- 重要模式: 串行處理
重構(gòu)
- 要做重構(gòu),我們從創(chuàng)建快捷變量開始
- [圖片上傳失敗...(image-6e8c61-1533372113516)]
- 由于我們書寫的代碼是異步,會(huì)有問(wèn)題,隨著韓數(shù)量的增長(zhǎng),過(guò)多的函數(shù)嵌套會(huì)讓程序的可讀性變差
- 為此我們可以為每一個(gè)異步操作預(yù)先定一個(gè)一個(gè)函數(shù)
- 抽取函數(shù)
- [圖片上傳失敗...(image-85c6-1533372113516)]
- [圖片上傳失敗...(image-582698-1533372113516)]
- 讀取用戶輸入后,接下來(lái)要做的就是根據(jù)用戶輸入做出相應(yīng)處理,用戶需要選擇要讀取的文件,所以代碼層,我們?cè)O(shè)置了stdin的編碼后,開始監(jiān)聽器data時(shí)間:
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數(shù)組的下表,files數(shù)組是fs.readdir回調(diào)函數(shù)中的一部分,上述代碼中,我們將utf-8編碼字符串轉(zhuǎn)換為了Number類型來(lái)方便檢查
- 現(xiàn)在我們已經(jīng)能定位文件了那就開始讀取他
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');
});
}
}
- 這樣我們已經(jīng)可以讀取一個(gè)文件了
- 但是讀取文件夾的時(shí)候就會(huì)出錯(cuò),在這種情況下,我們就將其目錄下的文件列表顯示出來(lái),
- 為了避免再次執(zhí)行fs.stat 我們?cè)趂ile函數(shù)中,將stat對(duì)象保存下來(lái)
至此我們就完成了首個(gè)查看文件和文件夾內(nèi)容的程序,雖然很簡(jiǎn)陋
/*
* 模塊依賴
* */
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 沒(méi)有文件顯示 !\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);
});