1.前言
目錄:
- 安裝(就不說(shuō)了谤祖,網(wǎng)上去找)
- 模塊
- 代碼的組織和部署
- 文件操作
本文的命令行為
$ node node_test.js test.py test.py1
process.argv[2]
為 test.py
可婶。
有疑問(wèn)或是文章中有錯(cuò)誤的地方岔乔,請(qǐng)?jiān)谠u(píng)論區(qū)指出來(lái),如果私信的話別人就看不到了,加油一起進(jìn)步~~(゜▽゜*)?
2.模塊
每一個(gè)文件就是一個(gè)模塊尉咕,文件的路徑就是模塊名。在每個(gè)模塊中璃岳,都有require
年缎、exports
、 module
這三個(gè)變量可以使用铃慷。
一個(gè)模塊的代碼只在初始化時(shí)執(zhí)行一次单芜,之后被緩存以供重復(fù)調(diào)用。
a.require
一個(gè)方法犁柜,用于在一個(gè)模塊中洲鸠,加載另一個(gè)模塊的,返回一個(gè)導(dǎo)出對(duì)象馋缅。建議傳入相對(duì)路徑而不是絕對(duì)路徑扒腕。
const foo1 = require('./foo') // .js文件擴(kuò)展名可以忽略
const data = require('./data.json') //也可以加載json文件
b.exports
一個(gè)對(duì)象,是當(dāng)前模塊的導(dǎo)出對(duì)象萤悴,用于導(dǎo)出方法或?qū)傩择渌K使用require
方法可以調(diào)用當(dāng)前模塊的導(dǎo)出對(duì)象。
exports.sayName=function(name){
console.log('Hi' +name)
}
c.module
一個(gè)對(duì)象覆履,可以訪問(wèn)當(dāng)前模塊的相關(guān)信息蹋盆,點(diǎn)擊查看他的相關(guān)屬性费薄。最多的用途是用來(lái)替換當(dāng)前模塊的導(dǎo)出模塊,他的默認(rèn)導(dǎo)出值為空對(duì)象栖雾,這時(shí)我們來(lái)將他改成一個(gè)函數(shù)楞抡。
module.exports = function () {
console.log('test');
};
d.主模塊
node只有一個(gè)入口文件稱為主模塊,需要調(diào)用命令行來(lái)啟用的析藕。
node main.js
在一個(gè)文件中多次調(diào)用相同模塊時(shí)召廷,被引入的模塊的內(nèi)部變量只初始化一次,不會(huì)開辟新的內(nèi)存噪径。
3.代碼的組織和部署
a.模塊的路徑解析規(guī)則
從上一章我們知道了require
支持相對(duì)路徑和絕對(duì)路徑柱恤。但這種引入方式在后期維護(hù)上如果修改了某個(gè)文件的存放位置,在引用它的文件中也要隨之修改相關(guān)路徑找爱,牽一發(fā)而動(dòng)全身梗顺。require
還有第三種的路徑寫法。
內(nèi)置模塊
內(nèi)置模塊沒有路徑解析车摄,直接返回模塊的導(dǎo)出對(duì)象
const fs=require('fs')
node_modules
大部分第三方模塊都安裝于此目錄下寺谤,在引入時(shí)直接忽略node_modules文件夾之前的路徑,直接引入就好吮播。
比如該路徑為:
/mytac/node_modules/app
引入時(shí)
const app=require('app')
NODE_PATH環(huán)境變量
定義NODE_PATH環(huán)境變量变屁,如
NODE_PATH=/a/b/c
當(dāng)引用require('file')
時(shí),node會(huì)嘗試以下路徑
/a/b/c/file
b.包
由多個(gè)子模塊組成的模塊稱為包意狠,把所有子模塊放在同個(gè)目錄下粟关,寫過(guò)npm包的人會(huì)知道,這個(gè)大模塊需要一個(gè)入口文件环戈。如果入口文件名為index的話闷板,在引入時(shí)直接寫之前的路徑就好了,如:
const app=require('application')
// 等價(jià)于
const app=require('application/index')
c.命令行
舉個(gè)栗子院塞,希望有一個(gè)命令行程序遮晚,傳入相關(guān)參數(shù),并將他打印出來(lái)
$ node myapp/src/util/node-echo.js Hello world
Hello world
這種使用方法不太像是一個(gè)命令行程序拦止,下面才是我們期望的方式
$ node-echo Hello world
Linux
在Linux下县遣,我們可以把js文件當(dāng)作shell腳本來(lái)執(zhí)行,為了達(dá)到上述效果汹族,步驟:
- 在shell腳本中萧求,通過(guò)
#!
注釋指定當(dāng)前腳本的解釋器,首先在node-echo.js文件頂部增加這條注釋顶瞒,證明該腳本需要node來(lái)解析夸政。
#! /myapp/src/util/env node
- 賦予node-echo.js文件執(zhí)行權(quán)限
$ chmod +x /myapp/src/util/node-echo.js
- 在PATH環(huán)境變量下指定某個(gè)目錄,比如要在
/myapp/src/util/
下創(chuàng)建一個(gè)軟鏈文件搁拙,文件名與我們希望使用的終端命令同名秒梳,命令如下:
$ sudo ln -s /myapp/src/util/node-echo.js /myapp/src/util/node-echo
這樣處理過(guò)后,可以在任意目錄下使用node-echo
命令咯~
Windows
windows下與Linux完全不同箕速,需要.cmd文件來(lái)解決問(wèn)題酪碘。假如node-echo.js存放在C:\myapp\src\util
目錄,并且該目錄已經(jīng)添加到PATH環(huán)境變量里盐茎,接下來(lái)需要在該目錄下新建一個(gè)名為node-echo.cmd
文件兴垦,如下:
@node "C:\myapp\src\util\node-echo.js" %*
這樣處理過(guò)后,可以在任意目錄下使用node-echo
命令咯~
工程目錄標(biāo)準(zhǔn)樣例
- /home/user/workspace/node-echo/ # 工程目錄
- bin/ # 存放命令行相關(guān)代碼
node-echo
+ doc/ # 存放文檔
- lib/ # 存放API相關(guān)代碼
echo.js
- node_modules/ # 存放三方包
+ argv/
+ tests/ # 存放測(cè)試用例
package.json # 元數(shù)據(jù)文件
README.md # 說(shuō)明文件
d.NPM
下載第三方包
# 下載安裝并將依賴寫入package.json文件中
$ npm install package --save
# 安裝指定版本
$ npm install package1.0.1
發(fā)布自己的包
先要在npm注冊(cè)個(gè)賬號(hào)字柠,然后按照npm init的提示填寫相關(guān)信息探越,最后使用npm publish
發(fā)布就好。
3.文件操作
a.fs模塊
node只提供了基本的文件操作api窑业,但沒有文件拷貝這種高級(jí)操作钦幔,這里我們先練個(gè)手
小文件拷貝
var fs = require('fs');
function copy(src, dst) {
fs.writeFileSync(dst, fs.readFileSync(src));
}
function main(argv) {
copy(argv[0], argv[1]);
}
main(process.argv.slice(2));
進(jìn)入該目錄下,如果想要復(fù)制該目錄下的test.py文件到該文件夾下test2.py常柄,鍵入命令:
$ node node-echo.js test.py test2.py
以上程序通過(guò)fs.readFileSync
從源路徑讀取文件內(nèi)容鲤氢,并使用fs.writeFileSync
將文件寫入到目標(biāo)路徑。process是一個(gè)全局變量西潘,通過(guò)process.argv獲得命令行參數(shù)卷玉。值得注意的是argv[0]始終為node執(zhí)行程序的絕對(duì)路徑,argv[1]為主模塊的絕對(duì)路徑喷市,所以傳入的參數(shù)需要從argv[2]這個(gè)位置取相种。
大文件拷貝
上面的文件拷貝小文件沒有什么大問(wèn)題,但是讀取大文件內(nèi)存會(huì)爆倉(cāng)品姓,要讀取大文件只能讀一點(diǎn)寫一點(diǎn)寝并,直至完成,對(duì)于上面的程序需要進(jìn)行如下改造缭黔。
var fs=require('fs')
function copy(src,dist){
fs.createReadStream(src).pipe(fs.createWriteStream(dist))
}
function main(argv){
copy(argv[0],argv[1])
}
main(process.argv.slice(2))
使用fs.createReadStream
創(chuàng)建一個(gè)源文件只讀數(shù)據(jù)流食茎,使用fs.createWriteStream
創(chuàng)建一個(gè)只寫數(shù)據(jù)流,并用pipe方法將兩個(gè)數(shù)據(jù)流連接起來(lái)馏谨。
b.Buffer
js中沒有二進(jìn)制數(shù)據(jù)類型别渔,node提供了一個(gè)與String對(duì)等的全劇構(gòu)造函數(shù)Buffer
來(lái)對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行操作。除了可以讀取文件得到Buffer的實(shí)例惧互,還能夠直接構(gòu)造哎媚,如:
var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);
Buffer與String類型差不多,可以用length屬性讀取字節(jié)長(zhǎng)度喊儡,也可以通過(guò)[index]
方式讀取文件位置拨与。也可與String相互轉(zhuǎn)化,如:
var str=bin.toString('utf-8') // 指定編碼
var bin=new Buffer('hello','utf-8') //<Buffer 68 65 6c 6c 6f>
當(dāng)然艾猜,他們兩者也是有區(qū)別的买喧。字符串是只讀的捻悯,他的意思不是說(shuō)這個(gè)字符串不會(huì)被修改,而是單獨(dú)修改某個(gè)字節(jié)位置淤毛,這個(gè)位置并不會(huì)改變今缚,而Buffer則不同,修改Buffer更像是修改數(shù)組低淡,可以直接修改某個(gè)位置的值姓言。
使用slice
方法也不是返回一個(gè)新Buffer,而是返回了某個(gè)位置的指針,修改該指針的值會(huì)作用于原buffer蔗蹋。比如:
var bin =new Buffer([0x68,0x65,0x6c])
var bin2=bin.slice(1)
bin2[0]=0x68
console.log(bin) // 68 68 6c
所以何荚,如果想要拷貝一份buffer,需要?jiǎng)?chuàng)建一個(gè)新buffer猪杭,通過(guò)copy
方法將buffer中的數(shù)據(jù)復(fù)制過(guò)去餐塘。
var bin =new Buffer([0x68,0x65,0x6c])
var dup=new Buffer(bin.length)
bin.copy(dup)
bin[0]=0x65
console.log(bin) //65 65 6c
console.log(dup) // 68 65 6c
c.Stream
當(dāng)內(nèi)存中無(wú)法一次裝下需要處理的數(shù)據(jù),或是需要一邊讀一邊處理時(shí)皂吮,我們就需要用到數(shù)據(jù)流唠倦。Node中通過(guò)各種Stream
來(lái)提供對(duì)數(shù)據(jù)流的操作。比如涮较,對(duì)數(shù)據(jù)創(chuàng)建一個(gè)只讀流:
var fs=require('fs')
var rs=fs.createReadStream(process.argv[2])
rs.on('data',function(chunk){
// do something
console.log(chunk)
})
rs.on('end',function(){
console.log('end')
})
Stream基于事件機(jī)制工作稠鼻,所有Stream的實(shí)例都繼承于NodeJS提供的EventEmitter。
上面的data事件會(huì)不斷被觸發(fā)狂票,然而data事件中的函數(shù)并不會(huì)每次都執(zhí)行得過(guò)來(lái)候齿,可以按照如下方法來(lái)解決這個(gè)問(wèn)題。
var fs=require('fs')
var rs=fs.createReadStream(process.argv[2])
function print(data,func){
console.log(data)
func()
}
rs.on('data',function(chunk){
rs.pause()
print(chunk,function(){
rs.resume()
})
})
rs.on('end',function(){
console.log('end')
})
創(chuàng)建一個(gè)只寫數(shù)據(jù)流
var fs=require('fs')
var rs=fs.createReadStream(process.argv[2])
var ws=fs.createWriteStream(process.argv[3])
rs.on('data',function(chunk){
ws.write(chunk)
})
rs.on('end',function(){
ws.end()
console.log('end')
})
但上面的程序有個(gè)問(wèn)題是闺属,寫入速度跟不上讀取速度的話慌盯,內(nèi)部緩存會(huì)爆倉(cāng)。我們根據(jù).write
方法的返回值來(lái)判斷傳入的數(shù)據(jù)是寫入目標(biāo)文件還是臨時(shí)放在了緩存中掂器,通過(guò)drain事件亚皂,drain的意思是排干,意思是用來(lái)判斷什么時(shí)候只寫數(shù)據(jù)流已經(jīng)將緩存中的數(shù)據(jù)寫入目標(biāo)国瓮,可以傳入下一個(gè)待寫數(shù)據(jù)了灭必。
var fs=require('fs')
const argvs=process.argv
var rs=fs.createReadStream(argvs[2])
var ws=fs.createWriteStream(argvs[3])
rs.on('data',function(chunk){
if(ws.write(chunk)===false){
rs.pause()
}
})
rs.on('end',function(){
ws.end()
})
ws.on('drain',function(){ // 防爆倉(cāng)控制
rs.resume()
})
d.其他文件操作
(a)文件屬性的讀寫
獲取文件屬性
var fs=require('fs')
const argvs=process.argv
fs.stat(argvs[2],function(err,stats){
if(err){
throw err
}else{
console.log(stats)
}
})
修改讀寫權(quán)限
關(guān)于設(shè)置權(quán)限707、777
var fs=require('fs')
const argvs=process.argv
function getState(path,str){
fs.stat(path,function(err,stat){
if(err){
throw err
}else{
console.log(str+stat.mode)
}
})
}
getState(argvs[2],'原權(quán)限')
fs.chmod(argvs[2],0777,function(err){
if(err){
throw err
console.log('讀寫失敗')
}else{
getState(argvs[2],'現(xiàn)權(quán)限')
}
})
更改文件所有權(quán)
var fs=require('fs')
const path=process.argv[2]
function getState(path){
fs.stat(path,function(err,stat){
if(err){
throw err
}else{
const {gid,uid}=stat
console.log(`gid:${gid},uid:${uid}`)
}
})
}
getState(path)
fs.chown(path,1,0,function(err){ // 1,0分別對(duì)應(yīng)uid乃摹、gid
if(err){
throw err
}else{
console.log('changed')
getState(path)
}
})
(b)文件內(nèi)容讀寫
讀取文件內(nèi)容
var fs=require('fs')
const path=process.argv[2]
fs.readFile(path,'utf-8',function(err,data){ // 不指定編碼的情況下禁漓,以buffer形式輸出
if(err){
throw err
}else{
console.log(data)
}
})
讀取文件目錄
fs.readdir('../',function(err,files){ //傳入目錄
if(err){
throw err
}
console.log(files)
})
寫入文件
如果文件存在,則被覆蓋
fs.writeFile('test.txt','test message~~~',function(err){
if(err){
throw err
}
console.log('saved file')
})
創(chuàng)建目錄
如果目錄存在孵睬,則拋出異常
fs.mkdir('newdir',0777,err=>{
if(err) throw err
console.log('created!')
})
(c)底層文件操作
打開/關(guān)閉文件
var fs=require('fs')
const path=process.argv[2]
fs.open(path,'w',(err,fd)=>{
if(err) throw err
fs.futimes(fd,1388648322,1388648322,err=>{
if(err) throw err
console.log('futimes done')
fs.close(fd,()=>{
console.log('done')
})
})
})
讀取文件數(shù)據(jù)
fs.read
根據(jù)指定的文件描述符fd來(lái)讀取文件數(shù)據(jù)并寫入buffer指向的緩沖區(qū)對(duì)象播歼。相對(duì)于readFile提供了更底層的接口.
一般情況下不建議使用這種方式來(lái)讀取文件,因?yàn)樗竽闶謩?dòng)管理緩沖區(qū)和文件指針掰读,尤其是在 你不知道文件大小的時(shí)候秘狞,這將會(huì)是一件很麻煩的事情叭莫。
var fs = require('fs')
const path = process.argv[2]
fs.open(path, 'r', (err, fd) => {
if (err) throw err
let buf = new Buffer(8)
fs.read(fd, buf, 1, 15, null, (err, bytesRead, buffer) => { // 0為偏移量 100為讀取的字結(jié)束 null為開始讀取的位置,null只從讀取位置讀取
if (err) throw err
console.log('bytesRead', bytesRead)
console.log(buffer)
})
})
根據(jù)文件描述符寫入文件
fs.write
該方法提供更底層的操作烁试,實(shí)際應(yīng)用中建議使用多 fs.writeFile()
var fs = require('fs')
const path = process.argv[2]
fs.open(path, 'w', (err, fd) => {
if (err) throw err
const data='# hello python!'
const buf=new Buffer(data,'utf-8')
fs.write(fd,buf,0,data.length,0,(err,bytesWritten,buffer)=>{
if(err) throw err
console.log(bytesWritten)
console.log(buffer)
fs.close(fd,err=>{
if(err) throw err
console.log('file closed')
})
})
}
以上介紹的方法都是以異步的方式調(diào)用的食寡,也分別都有對(duì)應(yīng)的同步方法,拿readFileSync
舉例:
var fs = require('fs')
const path = process.argv[2]
try{
const data=fs.readFileSync(path)
console.log(data)
}catch(err){
console.log(err)
}
e.Path
node中提供了幾個(gè)內(nèi)置模塊來(lái)簡(jiǎn)化路徑相關(guān)操作廓潜,并提升代碼可讀性。
路徑標(biāo)準(zhǔn)化
path.normalize(path)
將傳入的路徑轉(zhuǎn)換為標(biāo)準(zhǔn)的路徑善榛,可以去掉多余的斜杠辩蛋。但在不同操作系統(tǒng)下,解析后的斜杠不一樣移盆。
var path=require('path')
const url = process.argv[0]
console.log(path.normalize(url))
連接路徑分隔符
將path片段連接在一起并將路徑規(guī)范化悼院。
path.join
var path=require('path')
console.log(path.join('/foo','//bar','abc','../abc')) // \foo\bar\abc
獲取path的擴(kuò)展名
返回文件的擴(kuò)展名,從最后一個(gè).
截取咒循,沒有.
則返回空字符串据途。
path.extname
const url = process.argv[2]
var path=require('path')
console.log(path.extname(url)) // .py