大家好,我是若川。歡迎加我微信?ruochuan12,加群交流學習。今天分享一篇nodejs基礎的文章。點擊下方卡片關注我,或者查看源碼等系列文章。
在日常使用 Node 進行開發的時候,會使用到一些文件系統、路徑操作等基礎 API,這里整理一下,方便大家理解和直接使用。
這里只介紹最常用的那些,不是所有哈,想要看更全的,直接看官方文檔[1]就 OK。
盡量不廢話,多上代碼。
Process 模塊
先介紹 process 模塊,它提供了當前 Node 進程相關的全局環境信息。在后面的 API 中被用到。
//?內置模塊,直接使用
const?process?=?require('process');
process.cwd()
這是一個函數,返回當前 Node 進程執行的目錄,舉例一個常見的場景:
一個 Node 模塊 A
通過 NPM 發布,項目 B
中使用了模塊 A
。在 A
中需要操作 B
項目下的文件時,就可以用 process.cwd()
來獲取 B
項目的路徑。
const?cwd?=?process.cwd();?//?輸出:/Users/xiaolian/Code/node-api-test
process.argv
在終端通過 Node 執行命令的時候,通過 process.argv
可以獲取傳入的命令行參數,返回值是一個數組:
0: Node 路徑(一般用不到,直接忽略)
1: 被執行的 JS 文件路徑(一般用不到,直接忽略)
2~n: 真實傳入命令的參數**
所以,我們只要從 process.argv[2]
開始獲取就好了。一般都是這樣用:
const?args?=?process.argv.slice(2);
直接獲取我們想要的參數。
process.env
返回一個對象,存儲當前環境相關的所有信息,一般很少直接用到。
一般我們會在 process.env
上掛載一些變量標識當前的環境。比如最常見的用 process.env.NODE_ENV
區分 development
和 production
。在 vue-cli
的源碼中也經常會看到 process.env.VUE_CLI_DEBUG
標識當前是不是一 DEBUG
模式。
這里提一個 webpack 的插件 DefinePlugin[2],在日常的構建流程中,我們經常會通過這個插件來注入不同的全局變量,從而執行不同的構建流程,并且代碼中的 process.env.xxx
會被替換成具體的值,在 Terser 壓縮階段會將 deadCode 移除,優化代碼體積。
process.platform
這個用的不多,返回當前系統信息,枚舉值如下:
console.log(process.platform);//?'aix'
//?'darwin'??-?macOS
//?'freebsd'
//?'linux'?-?linux
//?'openbsd'
//?'sunos'
//?'win32'?-?windows
Path 模塊
//?內置模塊,直接使用
const?path?=?require('path');
Node 中幾乎路徑相關的操作都會使用這個模塊。
這里就說 5 個最常用的:
path.join(...paths)
path.join
作用是將傳入的多個路徑拼成一個完整的路徑。
const?dPath?=?path.join('template',?'aaa',?'bbb',?'ccc',?'d.js');
//?輸出:?template/aaa/bbb/ccc/d.js
來看一個非常常見的場景,我們需要獲取當前項目的 package.json 文件,就可以這樣獲取它的路徑:
const?pkgPath?=?path.join(process.cwd(),?'./package.json');
//?輸出:?/Users/xiaolian/Code/node-api-test/package.json
path.join
可以傳入任意個路徑,比如:
['package.json',?'README.md'].forEach(fileName?=>?{const?templateFilePath?=?path.join(process.cwd(),?'template',?fileName);console.log(templateFilePath);
});
//?輸出:?/Users/xiaolian/Code/node-api-test/template/package.json
//?輸出:?/Users/xiaolian/Code/node-api-test/template/README.md
path.resolve(...paths)
path.resovle
和 path.join
的區別在于它的作用是將傳入的多個路徑和當前執行路徑拼接成一個完整的絕對路徑。
假設我現在 index.js
在 scripts
目錄下,然后我在根目錄下執行 node scripts/index.js
,它的代碼如下:
const?dPath?=?path.resolve('aaa',?'bbb',?'ccc',?'d.js');
//?輸出:??/Users/xiaolian/Code/node-api-test/aaa/bbb/ccc/d.js
一般情況下,當 path.resolve
的第一個參數為 ./
時,可以直接理解和 path.join(processs.cwd(), '')
表現一致。
path.basename(path[, ext])
path.basename
返回指定 path
最后一個路徑名,其中第二個參數 ext
可選,表示文件擴展名。比如:
console.log(path.basename('scripts/index.js'));??//?index.js
console.log(path.basename('scripts/index.js',?'.js'));??//?匹配到?.js,返回?index
console.log(path.basename('scripts/index.js',?'.json'));??//?沒匹配到,返回?index.js
path.dirname(path)
和 path.basename
對應,返回指定 path
最后一個路徑名之前的路徑。比如:
console.log(path.basename('scripts/index.js'));??//?scripts
console.log(path.basename('scripts/hook/index.js'));??//?scripts/hook
path.extname(path)
和 path.basename
對應,返回指定 path
最后一個路徑名的文件擴展名(含小數點 .
)。比如:
console.log(path.basename('scripts/index.js'));??//?.js
console.log(path.basename('README.md'));??//?.md
對比
最后再來對比一下各個路徑相關的 API 的區別。
項目 A
的目錄結構如下:
├──?scripts
│???└──?index.js
├──?src
│???└──?index.js
├──?package.json
├──?README.md
scripts/index.js
的代碼如下:
const?path?=?require('path');console.log(path.join('package.json'));
console.log(path.resolve('package.json'));
console.log(path.join('src',?'index.js'));
console.log(path.resolve('src',?'index.js'));
console.log(path.join(process.cwd(),?'package.json'));
console.log(path.resolve('./',?'package.json'));
console.log(__filename);
console.log(__dirname);
然后,我們在項目 A
的根目錄下執行 node scripts/index.js
,結果如下:
->?node?scripts/index.js
package.json
/Users/xiaolian/Code/A/package.json
src/index.js
/Users/xiaolian/Code/A/src/index.js
/Users/xiaolian/Code/A/package.json
/Users/xiaolian/Code/A/package.json
/Users/xiaolian/Code/A/scripts/index.js
/Users/xiaolian/Code/A/scripts
品,仔細品,它們有什么區別。
個人而言,一般還是習慣用 path.join(process.cwd(), 'xxx')
。
File System 模塊
//?內置模塊,直接使用
const?fs?=?require('fs');
文件系統相關操作的模塊,除了 fs
之外,我們還經常用到 fs-extra
,后面會介紹。
這個模塊在平時的 Node 開發中會被大量使用,這里簡單列幾個,其它的還是看文檔哈:https://nodejs.org/dist/latest-v14.x/docs/api/fs.html[3]
fs
模塊的 API 默認都是異步回調的形式,如果你想使用同步的方法,有兩種解決方法:
使用 Node 提供的同步 API:
xxxSync
,也就是在 API 的后面加一個Sync
后綴,它就是一個同步方法了(具體還是需要查文檔哈,是否有提供同步 API)包裝成一個 Promise 使用
fs.stat(path[, options], callback)
fs.stat()
返回一個文件或者目錄的信息。
const?fs?=?require('fs');fs.stat('a.js',?function(err,?stats)?{console.log(stats);
});
其中包含的參數有很多,介紹幾個比較常用的:
export?interface?StatsBase<T>?{isFile():?boolean;?????????????????//?判斷是否是一個文件isDirectory():?boolean;????????????//?判斷是否一個目錄size:?T;???????????????????????????//?大小(字節數)atime:?Date;???????????????????????//?訪問時間mtime:?Date;???????????????????????//?上次文件內容修改時間ctime:?Date;???????????????????????//?上次文件狀態改變時間birthtime:?Date;???????????????????//?創建時間
}
一般我們會使用 fs.stat
來取文件的大小,做一些判斷邏輯,比如發布的時候可以檢測文件大小是否符合規范。在 CLI 中,經常需要獲取一個路徑下的所有文件,這時候也需要使用 fs.stat
來判斷是目錄還是文件,如果是目錄則繼續遞歸。當然,現在也有更方便的 API 可以完成這個工作。
同步方法
const?fs?=?require('fs');try?{const?stats?=?fs.statSync('a.js');
}?catch(e)?{}
fs.readdir(path[, options], callback)
fs.readdir(path)
獲取 path
目錄下的文件和目錄,返回值為一個包含 file
和 directory
的數組。
假設當前目錄為:
.
├──?a
│???├──?a.js
│???└──?b
│???????└──?b.js
├──?index.js
└──?package.json
執行以下代碼:
const?fs?=?require('fs');fs.readdir(process.cwd(),?function?(error,?files)?{if?(!error)?{console.log(files);}
});
返回值為:
[?'a','index.js','package.json'?]
可以看到這里只返回了根目錄下的文件和目錄,并沒有去深度遍歷。所以如果需要獲取所有文件名,就需要自己實現遞歸。
同步方法
const?fs?=?require('fs');try?{const?dirs?=?fs.readdirSync(process.cwd());
}?catch(e)?{}
fs.readFile(path[, options], callback)
文件讀取的 API,通過 fs.readFile
可以獲取指定 path
的文件內容。
入參如下:
第一個參數: 文件路徑
第二個參數: 配置對象,包括
encoding
和flag
,也可以直接傳如encoding
字符串第三個參數: 回調函數
使用方法如下:
const?fs?=?require('fs');
const?path?=?require('path');fs.readFile(path.join(process.cwd(),?'package.json'),?'utf-8',?function?(error,content
)?{if?(!error)?{console.log(content);}
});
如果沒傳 encoding
,則其默認值為 null
,此時返回的文件內容為 Buffer
格式。
同步方法
const?fs?=?require('fs');try?{fs.readFileSync(path.join(process.cwd(),?'package.json'),?'utf-8');
}?catch(e)?{}
fs.writeFile(file, data[, options], callback)
對應著讀文件 readFile
,fs
也提供了寫文件的 API writeFile
,接收四個參數:
第一個參數: 待寫入的文件路徑
第二個參數: 待寫入的文件內容
第三個參數: 配置對象,包括
encoding
和flag
,也可以直接傳如encoding
字符串第三個參數: 回調函數
使用方法如下:
const?fs?=?require('fs');
const?path?=?require('path');fs.writeFile(path.join(process.cwd(),?'result.js'),'console.log("Hello?World")',function?(error,?content)?{console.log(error);}
);
同步方法
const?fs?=?require('fs');
const?path?=?require('path');try?{fs.writeFileSync(path.join(process.cwd(),?'result.js'),'console.log("Hello?World")','utf-8');
}?catch?(e)?{}
本文主要是總結了一下在開發 Node 時常用的一些 API,后續的文章會帶來 Node 常用的一些三方包。
參考資料
[1]
官方文檔: https://nodejs.org/dist/latest-v14.x/docs/api/
[2]DefinePlugin: https://webpack.js.org/plugins/define-plugin
[3]https://nodejs.org/dist/latest-v14.x/docs/api/fs.html: https://nodejs.org/dist/latest-v14.x/docs/api/fs.html
最近組建了一個江西人的前端交流群,如果你也是江西人可以加我微信 ruochuan12 拉你進群。
·················?若川出品?·················
今日話題
去年清明假期開通了第二個微信號?ruochuan12,昨天突破2000好友了。清明假期打算完稿vuex4源碼文章,可惜,我是沒有完成,錯誤的預估了工作量,和自己的惰性...我可能可以封為資深拖稿專家了。
一個愿景是幫助5年內前端人走向前列的公眾號
可加我個人微信 ruochuan12,長期交流學習
推薦閱讀
我在阿里招前端,我該怎么幫你?(現在還能加我進模擬面試群)
如何拿下阿里巴巴 P6 的前端 Offer