讀<了不起的Node.js>-06.命令行工具(CLI)以及FS API首個(gè)Node應(yīng)用

簡(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)]
  • 第一個(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);
});


    


    



?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • //公共引用 varfs =require('fs'), path =require('path'); 1、讀取文...
    才気莮孒閱讀 890評(píng)論 0 1
  • 個(gè)人入門學(xué)習(xí)用筆記、不過(guò)多作為參考依據(jù)。如有錯(cuò)誤歡迎斧正 目錄 簡(jiǎn)書好像不支持錨點(diǎn)、復(fù)制搜索(反正也是寫給我自己看...
    kirito_song閱讀 2,654評(píng)論 1 37
  • 一、Python簡(jiǎn)介和環(huán)境搭建以及pip的安裝 4課時(shí)實(shí)驗(yàn)課主要內(nèi)容 【Python簡(jiǎn)介】: Python 是一個(gè)...
    _小老虎_閱讀 6,350評(píng)論 0 10
  • Node.js是目前非常火熱的技術(shù),但是它的誕生經(jīng)歷卻很奇特。 眾所周知,在Netscape設(shè)計(jì)出JavaScri...
    w_zhuan閱讀 3,736評(píng)論 2 41
  • 人生若只如初見 你該重新打扮 別再以我喜歡的樣子 迷惑我的眼 人生若只如初見 我們也無(wú)需交談 就辜負(fù)那心動(dòng)的瞬間 ...
    8a81955dd0ff閱讀 212評(píng)論 0 3

友情鏈接更多精彩內(nèi)容