三天node入門(day1)

1.前言

目錄:

  1. 安裝(就不說了,網(wǎng)上去找)
  2. 模塊
  3. 代碼的組織和部署
  4. 文件操作

本文的命令行為

$ node node_test.js test.py test.py1

process.argv[2]test.py。

有疑問或是文章中有錯(cuò)誤的地方,請(qǐng)?jiān)谠u(píng)論區(qū)指出來,如果私信的話別人就看不到了,加油一起進(jìn)步~~(゜▽゜*)?

2.模塊

每一個(gè)文件就是一個(gè)模塊,文件的路徑就是模塊名。在每個(gè)模塊中,都有requireexports、 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ū)傩?,其他模塊使用require方法可以調(diào)用當(dāng)前模塊的導(dǎo)出對(duì)象。

exports.sayName=function(name){
    console.log('Hi' +name)
}

c.module

一個(gè)對(duì)象,可以訪問當(dāng)前模塊的相關(guān)信息,點(diǎn)擊查看他的相關(guān)屬性。最多的用途是用來替換當(dāng)前模塊的導(dǎo)出模塊,他的默認(rèn)導(dǎo)出值為空對(duì)象,這時(shí)我們來將他改成一個(gè)函數(shù)。

module.exports = function () {
    console.log('test');
};

d.主模塊

node只有一個(gè)入口文件稱為主模塊,需要調(diào)用命令行來啟用的。

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è)目錄下,寫過npm包的人會(huì)知道,這個(gè)大模塊需要一個(gè)入口文件。如果入口文件名為index的話,在引入時(shí)直接寫之前的路徑就好了,如:

const app=require('application')
// 等價(jià)于
const app=require('application/index')

c.命令行

舉個(gè)栗子,希望有一個(gè)命令行程序,傳入相關(guān)參數(shù),并將他打印出來

$ node myapp/src/util/node-echo.js Hello world
Hello world

這種使用方法不太像是一個(gè)命令行程序,下面才是我們期望的方式

$ node-echo Hello world

Linux

在Linux下,我們可以把js文件當(dāng)作shell腳本來執(zhí)行,為了達(dá)到上述效果,步驟:

  1. 在shell腳本中,通過#!注釋指定當(dāng)前腳本的解釋器,首先在node-echo.js文件頂部增加這條注釋,證明該腳本需要node來解析。
#! /myapp/src/util/env node
  1. 賦予node-echo.js文件執(zhí)行權(quán)限
$ chmod +x /myapp/src/util/node-echo.js
  1. 在PATH環(huán)境變量下指定某個(gè)目錄,比如要在/myapp/src/util/下創(chuàng)建一個(gè)軟鏈文件,文件名與我們希望使用的終端命令同名,命令如下:
$ sudo ln -s /myapp/src/util/node-echo.js /myapp/src/util/node-echo

這樣處理過后,可以在任意目錄下使用node-echo命令咯~

Windows

windows下與Linux完全不同,需要.cmd文件來解決問題。假如node-echo.js存放在C:\myapp\src\util目錄,并且該目錄已經(jīng)添加到PATH環(huán)境變量里,接下來需要在該目錄下新建一個(gè)名為node-echo.cmd文件,如下:

@node "C:\myapp\src\util\node-echo.js" %*

這樣處理過后,可以在任意目錄下使用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/                        # 存放測試用例
    package.json                    # 元數(shù)據(jù)文件
    README.md                       # 說明文件

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

以上程序通過fs.readFileSync從源路徑讀取文件內(nèi)容,并使用fs.writeFileSync將文件寫入到目標(biāo)路徑。process是一個(gè)全局變量,通過process.argv獲得命令行參數(shù)。值得注意的是argv[0]始終為node執(zhí)行程序的絕對(duì)路徑,argv[1]為主模塊的絕對(duì)路徑,所以傳入的參數(shù)需要從argv[2]這個(gè)位置取。

大文件拷貝

上面的文件拷貝小文件沒有什么大問題,但是讀取大文件內(nèi)存會(huì)爆倉,要讀取大文件只能讀一點(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ù)流連接起來。

b.Buffer

js中沒有二進(jìn)制數(shù)據(jù)類型,node提供了一個(gè)與String對(duì)等的全劇構(gòu)造函數(shù)Buffer來對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行操作。除了可以讀取文件得到Buffer的實(shí)例,還能夠直接構(gòu)造,如:

var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);

Buffer與String類型差不多,可以用length屬性讀取字節(jié)長度,也可以通過[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ū)別的。字符串是只讀的,他的意思不是說這個(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,通過copy方法將buffer中的數(shù)據(jù)復(fù)制過去。

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)存中無法一次裝下需要處理的數(shù)據(jù),或是需要一邊讀一邊處理時(shí),我們就需要用到數(shù)據(jù)流。Node中通過各種Stream來提供對(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í)行得過來,可以按照如下方法來解決這個(gè)問題。

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è)問題是,寫入速度跟不上讀取速度的話,內(nèi)部緩存會(huì)爆倉。我們根據(jù).write方法的返回值來判斷傳入的數(shù)據(jù)是寫入目標(biāo)文件還是臨時(shí)放在了緩存中,通過drain事件,drain的意思是排干,意思是用來判斷什么時(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(){ // 防爆倉控制
    rs.resume()
})

d.其他文件操作

(a)文件屬性的讀寫

獲取文件屬性

fs.stat

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)限

fs.chmod

關(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)

fs.chown

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)容

fs.readFile

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

fs.readdir('../',function(err,files){ //傳入目錄
    if(err){
        throw err
    }
        console.log(files)
})

寫入文件

如果文件存在,則被覆蓋

fs.writeFile

fs.writeFile('test.txt','test message~~~',function(err){
    if(err){
        throw err
    }
        console.log('saved file')
})

創(chuàng)建目錄

如果目錄存在,則拋出異常

fs.mkdir

fs.mkdir('newdir',0777,err=>{
    if(err) throw err
        console.log('created!')
})

(c)底層文件操作

打開/關(guān)閉文件

fs.open
fs.close

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來讀取文件數(shù)據(jù)并寫入buffer指向的緩沖區(qū)對(duì)象。相對(duì)于readFile提供了更底層的接口.

一般情況下不建議使用這種方式來讀取文件,因?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)置模塊來簡化路徑相關(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
最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • https://nodejs.org/api/documentation.html 工具模塊 Assert 測試 ...
    KeKeMars閱讀 6,603評(píng)論 0 6
  • Node.js是目前非?;馃岬募夹g(shù),但是它的誕生經(jīng)歷卻很奇特。 眾所周知,在Netscape設(shè)計(jì)出JavaScri...
    w_zhuan閱讀 3,731評(píng)論 2 41
  • 個(gè)人入門學(xué)習(xí)用筆記、不過多作為參考依據(jù)。如有錯(cuò)誤歡迎斧正 目錄 簡書好像不支持錨點(diǎn)、復(fù)制搜索(反正也是寫給我自己看...
    kirito_song閱讀 2,651評(píng)論 1 37
  • Node.js是目前非常火熱的技術(shù),但是它的誕生經(jīng)歷卻很奇特。 眾所周知,在Netscape設(shè)計(jì)出JavaScri...
    Myselfyan閱讀 4,203評(píng)論 2 58
  • 看別人寫的怎么學(xué)英語的文章,什么聽說讀寫,聽播客,看外國大片,和老外聊天,我想說能不能結(jié)合點(diǎn)實(shí)際的,大家都要工作沒...
    羅蓁蓁閱讀 405評(píng)論 2 4

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