前端筆記 — node.js

一. 簡介

1.Node.js是js運行在服務(wù)器的一個平臺
2.Node中鼓拧,每一個js文件中的代碼都是獨立運行在一個閉包中的
3.Node.js的三個特點
  a. 單線程
  b. 非阻塞I/O
  c. 事件驅(qū)動
4.Node沒有web容器,也沒有根目錄

二. exports與require

1.介紹

node在執(zhí)行模塊中的代碼時,會將代碼包含在一個函數(shù)中,這個函數(shù)有5個參數(shù):

  1. exports:用來將模塊內(nèi)部的局部變量或局部函數(shù)暴露給外部
  2. require:用來引用外部的模塊
  3. module:代表當(dāng)前模塊本身,exports就是module的屬性,我們既可以用exports導(dǎo)出,也可以用module.exports導(dǎo)出
  4. __filename:當(dāng)前模塊的完整路徑
  5. __dirname:當(dāng)前模塊所在文件夾的完整路徑
    所以exports與require并不是全局變量吨岭,而是函數(shù)參數(shù)
function (exports, require, module, __filename, __dirname) {
    // js文件中的代碼
}
2.使用方法

可以使用exports導(dǎo)出變量或函數(shù)

// common.js
exports.name = '張三'
exports.say = () => {
    console.log('hello')
}

使用require引入模塊

let common = require('./common')
console.log(common.name) // 張三
common.say() // hello

也可以使用module.exports直接導(dǎo)出對象

// common.js
module.exports = {
    name: '張三',
    say: () => { console.log('hello') }
}

require引入的實際上是module.exports導(dǎo)出的內(nèi)容,所以不能直接使用exports導(dǎo)出對象

// common.js
exports = {
    name: '張三',
    say: () => { console.log('hello') }
}

// other.js
let common = require('./common') // 這里的common是一個空的對象{}峦树,并沒有name和say屬性

經(jīng)常有這樣的寫法:exports = module.exports = xxx辣辫,這種寫法的作用就是module.exports指向新對象后,讓exports重新指向這個新的對象

三. Buffer

Buffer是一個二進制容器魁巩,數(shù)據(jù)結(jié)構(gòu)與數(shù)組類似急灭,用于Node中的數(shù)據(jù)存放
Buffer是Node自帶的,不需要引入谷遂,可以直接使用
Buffer常用方法:

  • Buffer.from(str):將一個字符串轉(zhuǎn)換成buffer
  • Buffer.alloc(size):創(chuàng)建一個指定大小的buffer
  • Buffer.alloc(size[, fill[, encoding]]):size表示新建buffer的期望長度葬馋;fill用來填充buffer的值,默認(rèn)為0肾扰;encoding為編碼格式畴嘶,默認(rèn)utf-8

四. 文件系統(tǒng)(File System)

Node通過fs模塊來和文件系統(tǒng)進行交互,該模塊提供了一些標(biāo)準(zhǔn)的文件訪問API
使用fs之前集晚,需要先引入

const fs = require('fs')
1.open窗悯、openSync
open(path, flags[, mode], callback):使用異步的方式打開文件
let fd = fs.open('1.txt', 'w', (err, fd) => {
    // fd為打開的文件
}) // 異步打開文件用于寫入

openSync(path, flags[, mode]):使用同步的方式打開文件

let fd = fs.openSync('1.txt', 'w') // 打開文件用于寫入
2.write、writeSync偷拔、writeFile蟀瞧、writeFileSync

這四個方法用于向文件寫入內(nèi)容,其中前面兩個方法只能傳上面open方法獲取的fd条摸,后面的兩個方法可以直接傳路徑

// write、writeSync
let fd = fs.openSync('hello.txt', 'w')
let content = 'hello world!'
fs.write(fd, content, err => {
    if (!err) console.log('寫入成功')
})
fs.writeSync(fd, content) // 返回寫入的字節(jié)數(shù)

// writeFile铸屉、writeFileSync
fs.writeFile('hello.txt', content, err => {
    if (!err) console.log('寫入成功')
})
fs.writeFileSync('hello.txt', content) // 無返回值
3.使用文件流寫入
let ws = fs.createWriteStream('test.txt')
ws.once('open', () => {
    console.log('通道打開')
})
ws.once('close', () => {
    console.log('通道關(guān)閉')
})
ws.write('你好啊')
ws.write('你好不好啊')
ws.write('我很好啊')
4.readFile钉蒲、readFileSync
fs.readFile('1.txt', (err, data) => {
    if (!err) console.log(data) // data為讀取到的文件內(nèi)容
    else throw err
})

let data = fs.readFileSync('1.txt')  // 同步讀取文件內(nèi)容
5.使用文件流讀寫文件
let read = fs.createReadStream('1.txt')
let write = fs.createWriteStream('2.txt')
read.on('data', (data) => { // 監(jiān)聽讀取事件,每讀取到一段數(shù)據(jù)都會回調(diào)次方法
    write.write(data) // 將讀入的文件內(nèi)容寫入2.txt中
})
// 也可以使用下面的方法直接建立讀寫通道
read.pipe(write)

五. mongoDB

mongoDB是非關(guān)系型數(shù)據(jù)庫彻坛,沒有表顷啼、行的概念,只有集合(Collection昌屉,類似于關(guān)系型數(shù)據(jù)庫中的表)與文檔(Document钙蒙,類似于關(guān)系型數(shù)據(jù)庫中的行),用json格式存儲數(shù)據(jù)间驮。

1.mongoDB常用命令

show dbs:顯示當(dāng)前所有的數(shù)據(jù)庫
use 數(shù)據(jù)庫名:進入到指定數(shù)據(jù)庫躬厌,沒有則創(chuàng)建
db:顯示當(dāng)前數(shù)據(jù)庫
show tables:顯示當(dāng)前數(shù)據(jù)庫中的所有集合
db.dropDatabase():刪除當(dāng)前數(shù)據(jù)庫
db.<collecton>.drop():刪除集合

2.常用的CRUD語句

db.<collection>.insert(doc):添加數(shù)據(jù)

db.student.insert({name: '張三', age: 20}) // 插入一條數(shù)據(jù)
db.student.insert([
    {name: '張三', age: 20},
    {name: '李四', age: 25}
]) // 插入多條數(shù)據(jù)

db.<collection>.find():查詢集合中的所有數(shù)據(jù)

db.student.find() // 查詢student集合中的所有數(shù)據(jù)
db.student.find({name: '張三'}) // 查詢所有name為張三的數(shù)據(jù)
db.student.findOne({name: '張三'}) // 查詢一條name為張三的數(shù)據(jù)
db.student.find({name: '張三'}, {age: 1, _id: 0}) // 查詢所有name為張三的數(shù)據(jù)竞帽,只顯示age字段扛施,不顯示_id字段
db.student.find().count() 或 db.student.find().length() // 返回數(shù)據(jù)總條數(shù)鸿捧,可傳入查詢條件

配合操作符進行查詢:$lt(小于),$lte(小于等于)疙渣,$gt(大于)匙奴,$gte(大于等于)

db.student.find({age: {$lt: 20}}) // 查詢年齡小于20的數(shù)據(jù)
db.student.find({age: {$gt: 10, $lt: 20}}) // 查詢年齡大于10且小于20的數(shù)據(jù)
db.student.find({$or: [{age: {$lt: 10}}, {age: {$gt: 20}}]}) // 查詢年齡小于10或大于20的數(shù)據(jù)

skip(num),跳過前面num條數(shù)據(jù)妄荔,limit(size)泼菌,查詢size條數(shù)據(jù),可用于分頁

// 查詢第三頁的數(shù)據(jù)啦租,每頁10條
db.student.find().skip(20).limit(10) // 跳過前面20條哗伯,查詢10條

sort():用于數(shù)據(jù)排序

db.student.find().sort({name: 1}) // 根據(jù)name升序排列,1表示升序刷钢,-1表示降序
db.student.find().sort({name: 1, age: -1}) // 先根據(jù)name升序排序笋颤,若name相同,則根據(jù)age降序排列

db.<collection>.update():更新數(shù)據(jù)

db.student.update({name: '張三'}, {$set: {age: 30}}) // 更新第一條張三的age為30
db.student.update({name: '張三'}, {$set: {age: 30}}, {multi: true}) // 更新所有張三的age為30
// updateMany() === update({}, {}, {multi: true})
// updateOne() === update({}, {})
db.student.update({name: '張三'}, {$unset: {age: 1}}) // 刪除張三的age内地,age的值可以隨便傳
db.student.updateMany({name: '張三'}, {$inc: {age: 10}}) // 將張三的年齡加10
db.student.replaceOne({name: '張三'}, {name: '馬六', age: 20}) // 將name為張三的數(shù)據(jù)替換為{name: '馬六', age: 20}

更深層次的操作

db.student.update({name: '張三'}, {
    $set: {
        hobby: {
            common: ['上網(wǎng)', '看書'],
            special: ['胸口碎大石', '徒手劈榴蓮']
        }
    }
})

// 查詢普通愛好有上網(wǎng)的文檔
db.student.find({'hobby.common': '上網(wǎng)'})
// 給張三添加一個新的普通愛好“跑步”
db.student.update({name: '張三'}, {$push: {'hobby.common': '跑步'}}) // 也可以將$push替換為$addToSet伴澄,區(qū)別就是,$addToSet會判斷是否已經(jīng)存在阱缓,如果存在非凌,則不添加
// 刪除張三“徒手劈榴蓮”的特殊愛好
db.student.update({name:  '張三'}, {$pull: {'hobby.special': '徒手劈榴蓮'}})
// 或
db.student.update({name: '張三'}, {$pop: {'hobby.common': 1}}) // 只能傳1或者-1,1表示刪除最后一個荆针,-1表示刪除第一個

db.<collection>.remove():刪除數(shù)據(jù)

db.student.remove({}) // 刪除全部數(shù)據(jù)敞嗡,參數(shù)不能為空
db.student.remove({name: '張三'}) // 刪除所有name為張三的數(shù)據(jù)
db.student.remove({name: '張三'}, true) // 刪除一條name為張三的數(shù)據(jù)

六. mongoose

Mongoose是一個可以通過Node來操作MongoDB的模塊,本質(zhì)上是一個對象文檔模型(ODM)庫

1.基本使用

引入mongoose模塊

let mongoose = require('mongoose')

通過connect(url(s), [options], [callback])方法創(chuàng)建數(shù)據(jù)庫連接

mongoose.connect('mongodb://數(shù)據(jù)庫ip:端口號/數(shù)據(jù)庫名稱')
// 指定用戶名連接
mongoose.connect('mongodb://用戶名:密碼@數(shù)據(jù)庫ip:端口號/數(shù)據(jù)庫名稱') // 默認(rèn)端口27017
// 如果要連接多個數(shù)據(jù)庫航背,只需要設(shè)置多個url以逗號隔開喉悴,同時設(shè)置mongos為true
mongoose.connect('urlA, urlB, ...', {mongos: true})

監(jiān)聽各種狀態(tài)

let db = mongoose.connection
db.on('error', () => { console.log('連接失敗') })
db.on('open', () => { console.log('連接成功') })
db.on('close', () => { console.log('連接斷開') })

使用disconnect()斷開連接

mongoose.disconnect()
2.Schema

用于定義MongoDB中集合里文檔的結(jié)構(gòu),可以理解為mongoose對表結(jié)構(gòu)的定義玖媚,每個schema會映射到MongoDB中的一個Collection箕肃,它不具備操作數(shù)據(jù)庫的能力

定義schema時,指定字段名和類型即可今魔,支持的類型包括:String勺像、Number、Data错森、Buffer吟宦、Boolean、Mixed涩维、ObjectId及Array殃姓。通過mongoose.Schema來調(diào)用schema,然后使用new方法創(chuàng)建schema

let Schema = mongoose.Schema
let personSchema = new Schema({
    name: String,
    age: Number,
    sex: {
        type: 'string',
        default: '男'
    }
})
// 創(chuàng)建schema對象時,聲明字段類型有兩種方法辰狡,一種是首字母大寫的字段類型锋叨,另一種是引號包含的小寫字段類型

mongoose會將集合名稱設(shè)置為模型名稱的小寫版。如果名稱的最后一個字符是字母宛篇,則會變成復(fù)數(shù)娃磺,如果最后一個字符是數(shù)字,則不變叫倍。例如模型名稱為“person”偷卧,則映射的集合名稱為“people”,也可以指定集合名稱

let personSchema = new Schema({
    // ...
}, {collection: 'user_info'}) // 之后的增刪改查操作都是針對user_info這個集合

如果創(chuàng)建后還需要添加其他的字段吆倦,可以使用add()方法

personSchema.add({chat: String})
3.Model

Model是由Schema編譯而成的假想構(gòu)造器听诸,具有抽象屬性和行為。Model的每一個實例就是一個Document蚕泽,簡單說model就是由schema生成的模型晌梨,可以對數(shù)據(jù)庫操作

使用model()方法,將schema編譯為model须妻,第一個參數(shù)為模型名稱

let personModel = mongoose.model('person', personSchema)

也可以在編譯model時指定集合名稱

let personModel = mongoose.model('user', personSchema, 'user_info')
4.插入數(shù)據(jù)

使用create()插入數(shù)據(jù)

personModel.create({
    name: '張三',
    age: 25,
    chat: '15345632345'
}, err => {
    if (!err) console.log('插入成功')
    else throw err
})

插入多條數(shù)據(jù)

personModel.create([
    {name: '李四', age: 27, chat: 'lisi'},
    {name: '靜靜', age: 30, sex: '女', chat: 'jj'}
])

使用save()方法添加數(shù)據(jù)

let person = new personModel({
    name: '王五',
    age: 32
})
person.save((err, result) => { console.log(result) }) // result為插入的數(shù)據(jù)
5.查找仔蝌、修改及刪除

查詢

personModel.find({name: '張三'}, (err, docs) => {
    console.log(docs) // docs為查詢結(jié)果
})
// 查詢所有
personModel.find({}, (err, docs) => { 
    console.log(docs)
})
// 分頁查詢,不顯示_id荒吏,只顯示name敛惊、age
personModel.find({}, '-_id name age', {skip: 2, limit: 2}, (err, docs) => {})
或 personModel.find({}, {_id: 0, name: 1, age: 1}, {skip: 2, limit: 2}, (err, docs) => {})
// 統(tǒng)計數(shù)據(jù)總條數(shù)
personModel.count({}, (err, total) => {
    console.log(total) // total為總條數(shù)
})

修改

// 將所有名為張三的人,年齡改為28
personModel.update({name: '張三'}, {$set: {age: 28}}, {multi: true}, err => {})
// 也可使用updateOne()更新一條或updateMany()更新所有

刪除

// 刪除名為王五的數(shù)據(jù)
personModel.remove({name: '王五'}, err => {})

七. HTTP

1.基本使用

引入模塊绰更,使用createServer()方法創(chuàng)建服務(wù)瞧挤,該方法接受一個回調(diào)函數(shù)作為參數(shù),回調(diào)函數(shù)有兩個參數(shù)儡湾,第一個為請求對象特恬,第二個為響應(yīng)對象。然后使用創(chuàng)建的服務(wù)進行監(jiān)聽

let http = require('http')
http.createServer((req, res) => {
    // 處理代碼
    res.end() // 最后必須調(diào)用該方法結(jié)束請求
}).listen(port, ip) // port為端口號徐钠,ip地址

使用響應(yīng)對象的writeHead()方法鸵鸥,寫入狀態(tài)碼及響應(yīng)頭信息,write()或end()方法向客戶端寫入內(nèi)容

res.writeHead(200, {'Content-Type': 'text/html;charset=UTF-8'})
res.write('Hello World!')
res.end('你好')

Node.js沒有web容器丹皱,不能直接通過URL訪問資源,需要配置路由宋税,通過請求對象的url屬性可以獲取請求的資源路徑

if (req.url === '/page1.html') {
    fs.readFile('./html/page1.html', (err, data) => {
        if (!err) {
            res.writeHead(200, {'Content-Type': 'text/html;charset=UTF-8'})
            res.end(data)
        }
    })
} else if (req.url === '/page2.html') {
    // ...
} else {
    res.writeHead(404, {'Content-Type': 'text/plain;charset=UTF-8'})
    res.end('訪問的頁面不存在')
}
2.處理get請求

使用url模塊的parse()方法摊崭,可以將請求url轉(zhuǎn)換成對象,對象的query屬性則為get請求的參數(shù)

// http://127.0.0.1/?name=tom&age=28&sex=1
let url = require('url')
http.createServer((req, res) => {
    let urlObj = url.parse(req.url)
    console.log(urlObj.query) // name=tom&age=28&sex=1
    /*
        如果給parse()方法傳入第二個參數(shù)true杰赛,則query會被轉(zhuǎn)換成對象
        let urlObj = url.parse(req.url, true)
        console.log(urlObj.query) // {name: 'tom', age: 28, sex: 1}
    */
    res.end()
}).listen(...)
3.處理post請求

Node接受post請求傳遞的數(shù)據(jù)時呢簸,是分段接受的,并不是一次性全部接受,需要監(jiān)聽data事件

let qs = require('queryString')
http.createServer((req, res) => {
    let allData = ''
   // 將每次接受到的數(shù)據(jù)拼接起來
   req.on('data', data => { 
       allData += data
   })
   // 監(jiān)聽數(shù)據(jù)傳輸完畢
   req.once('end', () => {
       let d = qs.parse(allData) // 使用queryString模塊的parse()方法將數(shù)據(jù)轉(zhuǎn)成對象
       console.log(d)
   })
   res.end('ok')
})
4.formidable

formidable是一個第三方庫根时,可以用來處理post請求瘦赫,并保存上傳的文件

// 安裝后引入
let formidable = require('formidable')
let uuidv1 = require('uuid/v1') // 一個第三方庫,可以生成UUID
let path = require('path')
http.createServer((req, res) => {
    // 創(chuàng)建
    let form = new formidable.IncomingForm()
    // 設(shè)置上傳文件保存的路徑
    form.uploadDir = './uploads'
    // 處理post請求上傳的數(shù)據(jù)蛤迎,第一個參數(shù)為請求對象确虱,第二個參數(shù)是一個回調(diào)函數(shù)
    // err為錯誤信息,fields為請求的參數(shù)替裆,files為上傳的文件
    form.parse(req, (err, fields, files) => {
        // 這里面可以做一些處理校辩,比如給上傳的文件重命名
        let name = uuidv1() // 生成新的名稱
        let extName = path.extName(files.photo.name) // 獲取后綴名,假設(shè)上傳的文件字段名為photo
        let oldPath = `${__dirname}/files.photo.path`
        let newPath = `${__dirname}/uploads/${name}${extName}`
        fs.rename(oldPath, newPath, err => { // 文件重命名
            if (!err) {
                res.writeHeader(200, ....)
                res.end()
            } else throw err
        })
    })
})
5.靜態(tài)資源匹配

根據(jù)請求資源路徑辆童,自動去查找對應(yīng)的資源宜咒,并返回給客戶端,不用每個路徑都配置路由

http.createServer((req, res) => {
    let pathUrl = url.parse(req.url) // 將請求url轉(zhuǎn)成對象
    let pathName = pathUrl.pathName // 獲取請求資源路徑
    if (!pthName.includes('.')) { // 如果請求路徑不包含.把鉴,則請求的是文件夾故黑,返回index.html
        pathName += '/index.html'
    }
    /*
        path.normalize()方法能將路徑格式化成正確的格式
        從static文件夾中尋找資源
    */
    let fileUrl = './' + path.normalize('static/' + pathName)
    let extName = path.extName(pathName) // 獲取后綴名
    // 根據(jù)fileUrl去查找對應(yīng)的資源
    fs.readFile(fileUrl, (err, data) => {
        if (err) { // 訪問的資源不存在
            res.write(404, {'Content-Type': 'text/html;charset=UTF-8'})
            res.end('<h1>404,頁面不存在</h1>')
        }
        let contentType = getContentType(extName) // 自定義方法庭砍,根據(jù)后綴名取對應(yīng)的ContentType
        res.writeHeader(200, {'Content-Type': contentType})
        res.end(data) // 將讀取到的資源返回給客戶端
    })
}).listen(80, '127.0.0.1')
6.模板引擎(EJS)

EJS是一套簡單的模板語言场晶,利用普通的JavaScript代碼生成HTML頁面。
EJS文件以.ejs為后綴逗威,可以在HTML中插入js語句及變量或表達式峰搪。js語句使用<%%>插入,變量或表達式使用<%=%>插入
例:有如下模板及json數(shù)據(jù)

將數(shù)據(jù)綁定到模板中并返回給客戶端

let ejs = require('ejs')
http.createServer((req, res) => {
    fs.readFile('./topNes.ejs', async (err, data) => {
        if (!err) {
            let newsList = await getNewsList()
            let tmp = data.toString()
            let newsEjs = ejs.render(tmp, newsList)
            res.writeHeader(200, {'Content-Type': 'text/html;charset=UTF-8'})
            res.end(newsEjs)
        }
    })
}).listen(80, '127.0.0.1')

let getNewsList = () => {
    return new Promise((resolve, reject)=> {
        fs.readFile('./topNews.json', (err, data) => {
            if (!err) resolve(JSON.parse(data))
            else reject(err)
        })
    })
}
7.request()發(fā)送請求

可以使用request(options[, callback])方法發(fā)送請求
options常用配置如下

  • protocol:協(xié)議類型凯旭。默認(rèn)值:'http:'
  • hostname:請求服務(wù)器的域名或ip概耻。默認(rèn)值:'localhost'
  • port:請求的端口。默認(rèn)值:'80'
  • path:請求的路徑罐呼,get請求時包含查詢字符串鞠柄。例如 '/index.html?page=2'。默認(rèn)值:'/'
  • method:請求方法嫉柴。默認(rèn)值:'get'
  • headers:請求頭對象

使用該方法時厌杜,必須始終調(diào)用req.end()來表示請求結(jié)束。如果請求期間發(fā)生了錯誤计螺,可以監(jiān)聽 'error' 事件夯尽,如果沒有監(jiān)聽,則拋出錯誤

發(fā)送get請求

let http = require('http')
let req = http.request({
    hostname: '39.98.51.29',
    port: '6604',
    path: 'v1/api/device?deviceNo=200', // 請求參數(shù)拼在路徑后面
    headers: {
        ACCESS_TOKEN: 'token' // token放在請求頭中
        // get請求時可以不設(shè)置Content-Type
    }
}, res => { // 響應(yīng)的回調(diào)函數(shù)
    /*
        保存接受到的數(shù)據(jù)登馒。也可以將數(shù)據(jù)保存到數(shù)組中匙握,最終得到的是二進制數(shù)組,
        可以通過toString()方法轉(zhuǎn)為字符串陈轿,也可以直接使用JSON.parse()方法轉(zhuǎn)為對象
    */
    let str = '' 
    res.on('data', data => { // 數(shù)據(jù)是分段傳輸?shù)娜Ψ模枰O(jiān)聽data事件
        str += data
    })
    res.on('end', () => { // 表示數(shù)據(jù)傳輸完畢
        console.log(str)
    })
})
req.end() // 必須調(diào)用

發(fā)送post請求

let http = require('http')
let qs = require('querystring')

let data = qs.stringify({ // 請求參數(shù)為 form-data 時秦忿,使用qs.stringify()將對象轉(zhuǎn)為 form-data 格式
    username: 'ykx',
    password: '123456'
})
let req = http.request({
    hostname: '39.98.51.29',
    port: '6604',
    path: '/login',
    method: 'post', // 非get請求時,必須設(shè)置method
    headers: {
        // 如果請求參數(shù)為 form-data蛾娶,則 Content-Type 為 application/x-www-form-urlencoded
        // 如果請求參數(shù)為 json灯谣,則 Content-Type 為 application/json;charset=UTF-8
        'Content-Type': 'application/x-www-form-urlencoded'
    }
}, res => {
    let arr= []
    res.on('data', data => {
        arr.push(data)
    })
    res.on('end', () => {
        console.log(arr.toString()) // 或者直接JSON.parse(arr)轉(zhuǎn)成對象
    })
})
req.write(data) // 將請求參數(shù)寫入請求體
req.end()

如果請求頭中包含Accept-Encoding: gzip, deflate,表示請求的結(jié)果需要gzip壓縮蛔琅,需要使用zlib解壓胎许,否則亂碼

let zlib = require('zlib')

let req = http.request({
    ...
}, res => {
    let allData = ''
    let gunzip = zlib.createGunzip()
    res.pipe(gunzip)
    gunzip.on('data', data => {
          allData += data
     }).on('end', () => {
        // 這里的allData就是解壓后的數(shù)據(jù)
     })
})
req.end()

八. Express

Express是基于Node.js平臺,快速揍愁、開放呐萨、極簡的web開發(fā)框架。不對Node.js已有的特性進行二次抽象莽囤,只是在它之上擴展了Web應(yīng)用所需的基本功能

Express自身功能極簡谬擦,完全由路由和中間件構(gòu)成,從本質(zhì)上說朽缎,一個express應(yīng)用就是在調(diào)用各種中間件

1.基本使用

npm安裝后在文件中引入惨远,然后創(chuàng)建express()實例,監(jiān)聽端口

const express = require('express')
const app = express()

// 配置路由
app.get('/', (req, res) => {
    // 監(jiān)聽根路由的訪問 http://127.0.0.1:3000
    res.send('Hello world!')
})
app.get('/news', (req, res) => { ... }) // http://127.0.0.1:3000/news

// 配置二級路由话肖,多級路由也是如此配置
app.get('/news/topTen', (req, res) => { ... }) // http://127.0.0.1:3000/news/topTen

// 可以傳遞路徑參數(shù)
app.get('/about/:name', (req, res) => {
    console.log(req.params.name) // 通過請求對象的params屬性可以獲取參數(shù)
    res.send('關(guān)于')
})

app.listen(3000) // 相當(dāng)于 http.createServer(app).listen(3000)
/*
    app.listen底層實現(xiàn)如下

     app.listen = function () {
         var server = http.createServer(this)
         return server.listen.apply(server, arguments)
     }
*/
2.搭建靜態(tài)資源庫

利用express托管靜態(tài)文件北秽,可以提供諸如圖像、CSS文件和JavaScript文件之類的靜態(tài)文件

app.use(express.static('./static')) // 配置static為靜態(tài)文件庫最筒,可以直接通過路徑訪問贺氓,可以同時配置多個靜態(tài)文件庫
// 用上面的方法配置靜態(tài)文件庫后,下面這個回調(diào)不會被執(zhí)行床蜘,當(dāng)訪問根路徑時辙培,實際上是訪問的static文件夾中的index.html
app.get('/', (req, res) => {
    res.send('Hello world!')
})

也可以給靜態(tài)文件配置指定的訪問路徑

app.use('/test', express.static('./static')) // 訪問靜態(tài)文件時,路徑前面必須加上test -> 127.0.0.1/test/img/a.jpg
// 由于訪問靜態(tài)文件必須加上test邢锯,所以訪問根路徑時下面的回調(diào)會被執(zhí)行
app.get('/', (req, res) => {
    /* 
        發(fā)送HTTP響應(yīng)扬蕊,其body參數(shù)可以是一個Buffer對象,一個String丹擎,Object或者Array尾抑。
        send()方法只能使用一次,多次使用會報錯蒂培,如果需要返回多個數(shù)據(jù)再愈,可以使用write()和end()
    */
    res.send('Hellow world')
})
3.模板引擎

使用app.engine(ext, callback)方法創(chuàng)建自己的模板引擎。ext是指文件擴展名护戳,callback是模板引擎函數(shù)践磅,它接受以下參數(shù):

  • filePath:模板文件的位置
  • options:選項對象,即渲染模板時傳入的參數(shù)
  • callback:回調(diào)函數(shù)
// 模板文件index.ntl內(nèi)容如下
<h1>#title#</h1>
<p>#message#</p>

// 映射模板引擎
let fs = require('fs')
app.engine('ntl', (filePath, options, callback) => {
    fs.readFile(filePath, (err, data) => {
        if (err) return callback(err)
        let rendered = data.toString().replace('#title#', options.title).replace('#message#', options.message)
        return callback(null, rendered)
    })
})
app.set('views', './views') // 設(shè)置模板文件目錄灸异,會自動在這個目錄中尋找對應(yīng)名稱的模板文件
app.set('view engine', 'ntl') // 注冊模板引擎府适,第二個參數(shù)為模板文件夾的后綴名

app.get('/', (req, res) => {
    res.render('index', { title: 'Hey', message: 'Hello world!' })
})

// 渲染結(jié)果
<h1>Hey</h1>
<p>Hello world!</p>

使用ejs模板時,不需要映射肺樟,內(nèi)部已實現(xiàn)

app.set('views', './views')
app.set('view engine', 'ejs')
app.get('/', (req, res) => {
    res.render('topNews', newsList) // newsList是要綁定的數(shù)據(jù)
})
4.應(yīng)用生成器

通過應(yīng)用生成工具express-generator 可以快速創(chuàng)建一個應(yīng)用骨架

  1. 使用 npm install express-generator -g 全局安裝
  2. 通過 express --view=ejs myapp 快速創(chuàng)建應(yīng)用(ejs為使用的模板檐春,myapp為應(yīng)用名稱)
  3. 啟動應(yīng)用,DEBUG=myapp:* npm start(MacOS或Linux)么伯,set DEBUG=myapp:* & npm start(Windows)

生成的應(yīng)用目錄如下

app.js中代碼解釋

// 引用包文件
let createError = require('http-errors')
...
let logger = require('morgan')

// 引用路由
let indexRouter = require('./routers/index')
let usersRouter = require('./routers/users')

// 生成express實例
let app = express()

// 設(shè)置模板引擎為ejs
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'ejs')

// 使用中間件
app.use(logger('dev'))
/*
    下面兩個中間件用來處理post請求的參數(shù)
    第一個處理json格式的疟暖,第二個處理form-data格式的
    如果不經(jīng)過處理,通過req.body取不到請求參數(shù)
*/
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
...
app.use(express.static(path.join(__dirname, 'public')))

// 路由配置
app.use('/', indexRouter)
app.use('/users', usersRouter)

// 使用中間件處理404錯誤
app.use(function(req, res, next) {
    next(createError(404))
})

// 使用中間件處理錯誤
app.use(function(err, req, res, next) {
    res.locals.message = err.message
    ...
    res.render('error')
})

// 把app.js暴露出去
module.exports = app
5.中間件

中間件(Middleware)是一個函數(shù)田柔,它可以訪問請求對象俐巴,響應(yīng)對象和web應(yīng)用中處于請求-響應(yīng)循環(huán)流程中的中間件,一般被命名為next的變量

中間件功能包括:

  • 執(zhí)行任何代碼
  • 修改請求和響應(yīng)對象
  • 終結(jié)請求-響應(yīng)循環(huán)
  • 調(diào)用堆棧中的下一個中間件

如果中間件沒有終結(jié)請求-響應(yīng)循環(huán)硬爆,則必須調(diào)用next()方法將控制權(quán)交個下一個中間件欣舵,否則請求會被掛起

Express 應(yīng)用可以使用如下幾種中間件:

  • 應(yīng)用級中間件
  • 路由級中間件
  • 錯誤處理中間件
  • 內(nèi)置中間件
  • 第三方中間件

應(yīng)用級中間件綁定到app對象,使用 app.use() 和 app.METHOD()缀磕,其中METHOD是需要處理的HTTP請求方法缘圈,包括 GET、PUT袜蚕、POST等

let app = express()
// 沒有掛載路徑的中間件糟把,每個請求都會執(zhí)行該中間件
app.use((req, res, next) => {
    console.log(Data.now())
    next()
})
// 掛載至 /user/:id 的中間件,任何指向 /user/:id 的請求都會執(zhí)行它
app.use('/user/:id', (req, res, next) => {
    console.log(req.method)
    next()
})
// 路由及其處理函數(shù)(中間件系統(tǒng))牲剃,處理指向 /user/:id 的 GET 請求
app.get('/user/:id', (req, res, next) => {
    res.send('USER')
})

如果需要在中間件棧中跳過剩余中間件遣疯,調(diào)用 next('route') 方法將控制權(quán)交給下一個路由,next('route') 只對使用 app.METHOD() 或 router.METHOD() 加載的中間件有效

// 可以在一個掛載點上掛載一組中間件凿傅,從而創(chuàng)建一個子中間件棧
app.get('/users/:id', (req, res, next) => {
    // 如果 id 為 0缠犀,跳到下一個路由
    if (req.params.id === 0) next('route')
    else next()
}, (req, res, next) => {
    res.send('regular')
})
app.get('/users/:id', (req, res, next) => {
    res.send('special')
})

路由級中間件與應(yīng)用級中間件一樣,只是它綁定的對象為 express.Router()
使用 router.use() 和 router.METHOD() 函數(shù)加載路由級中間件

let router = express.Router()
router.get('/user/:id', (req, res, next) => {
    // 如果 id 為 0狭归,跳到下一個路由
    if (req.params.id === 0) next('route')
    else next()
}, (req, res, next) => {
    res.send('regular')
})
router.get('/users/:id', (req, res, next) => {
    res.send('special')
})

錯誤處理中間件和其他中間件定義類似夭坪,只是要使用4個參數(shù),即使參數(shù)用不到过椎,也必須聲明

app.use((err, req, res, next) => {
    console.error(err.stack)
    res.status(500).send('Something broke!')
})

內(nèi)置中間件主要有以下三種:

  1. express.static 提供靜態(tài)資源
  2. express.json 解析JSON格式的post請求參數(shù)
  3. express.urlencoded 解析Form-Data格式的post請求參數(shù)

第三方中間件可以為express應(yīng)用程序添加功能

let cookieParser = require('cookie-parser')
app.use(cookieParser())
6.路由

路由是指應(yīng)用程序的端點(URL)如何響應(yīng)客戶端的請求
可以使用Express app對象的方法定義路由室梅。例如,app.get()處理get請求疚宇,app.post()處理post請求亡鼠。也可以使用app.all()來處理所有HTTP請求,并使用app.use()將中間件指定為回調(diào)函數(shù)

let express = require('express')
let app = express()

app.get('/', (req, res) => {
    res.send('GET request to the homepage')
})
app.post('/', (req, res) => {
    res.send('POST request to the homepage')
})
// 對路由 '/secret' 的請求執(zhí)行處理程序敷待,無論請求方法是什么
app.all('/secret', (req, res, next) => {
    console.log('Accessing the secret section ...')
    next()
})

可以使用一組回調(diào)函數(shù)來處理路由

let cb1 = (req, res, next) => {
    console.log('111')
    next()
}
let cb2 = (req, res, next) => {
    console.log('222')
    next()
}
app.get('/', [cb1, cb2])

下表中響應(yīng)對象(res)的方法向客戶端返回響應(yīng)间涵,終結(jié)請求-響應(yīng)循環(huán)

方法 描述
res.download() 提示下載文件
res.end 終結(jié)響應(yīng)處理流程
res.json() 發(fā)送一個JSON格式的響應(yīng)
res.jsonp() 發(fā)送一個支持JSONP的JSON格式響應(yīng)
res.redirect() 重定向請求
res.render() 渲染視圖模板
res.send() 發(fā)送各種類型的響應(yīng)
res.sendFile() 以八位字節(jié)流的形式發(fā)送文件
res.sendStatus() 設(shè)置響應(yīng)狀態(tài)代碼,并將其以字符串的形式作為響應(yīng)體的一部分發(fā)送

可以使用app.route()創(chuàng)建鏈?zhǔn)铰酚商幚沓绦?/p>

app.route('/book')
    .get((req, res) => {
        res.send('Get a random book')
    })
    .post((req, res) => {
        res.send('Add a book')
    })
    .put((req, res) => {
        res.send('Update the book')
    })

可使用express.Router 類創(chuàng)建模塊化榜揖、可掛載的路由處理程序勾哩。Router 實例是一個完整的中間件和路由系統(tǒng)抗蠢,因此被稱為“mini-app”

下面的實例創(chuàng)建了一個路由模塊,并加載了一個中間件思劳,定義了一些路由迅矛,并且將它們掛載至應(yīng)用的路徑上

// birds.js
let express = require('express')
let router = express.Router()
// 該路由使用的中間件
router.use((req, res, next) => {
    console.log('Time: ', Date.now())
    next()
})
// 定義網(wǎng)站主頁的路由,匹配 /birds
router.get('/', (req, res) => {
    res.send('Birds home page')
})
// 定義 about 頁面的路由潜叛,匹配 /birds/about
router.get('/about', (req, res) => {
    res.send('About birds')
})
module.exports = router

// 在應(yīng)用中加載路由模塊
let birds = require('./birds')
...
app.use('/birds', birds)
7.使用session

在express中使用session秽褒,需要安裝并引入express-session,然后在入口文件(app.js)使用中間件

const session = require(‘express-session’)

app.use(session({
    name: '', // cookie的name
    secret: '', //通過設(shè)置的secret字符串威兜,來計算hash值并放在cookie中
    resave: true, // 強制保存session销斟,即使它沒有變化
    rolling: false, // 在每次請求時設(shè)置cookie,將重置cookie過期時間
    saveUninitialized: true, // 強制將未初始化的session存儲椒舵,默認(rèn)為true
    cookie: { maxAge: 1000 * 86400 }, // 設(shè)置cookie的過期時間蚂踊,單位為ms
    store: {} // session的存儲方式,默認(rèn)存儲在內(nèi)存中逮栅,可以設(shè)置持久化存儲在數(shù)據(jù)庫中
}))

// 可通過req.headers.cookie獲取到請求的cookie

持久化存儲session到mongodb中悴势,需要安裝并引入connect-mongo

let MongoStore = require(‘connect-mongo’)(session)
app.use(session({
    …,
    store: new MongoStore({
        url: 'mongodb://localhost:27017/dbname',
        touchAfter: 24 * 3600 // 多長時間往數(shù)據(jù)庫中更新一次存儲,除了在會話上更改某些數(shù)據(jù)外
    })
}))

九. Socket.io

Socket.io將Websocket和輪詢(Polling)機制以及其他的實時通信方式封裝成了通用的接口措伐,并且在服務(wù)端實現(xiàn)了這些實時機制的相應(yīng)代碼

1.服務(wù)端的使用
let socketIO = require('socket.io') // 引入socket.io
let io = socketIO(server) // 基于server(http.createServer創(chuàng)建的對象)生成socketIO實例對象特纤,
/*
    當(dāng)有客戶端向服務(wù)器建立連接的時候,‘connection’事件就會被激發(fā)侥加。
    對應(yīng)的回調(diào)函數(shù)就會執(zhí)行捧存。回調(diào)函數(shù)的參數(shù)socket就是客戶端與服務(wù)器的連接對象
*/
io.on('connection', socket => { 
    socket.emit('msg', '連接成功') // 建立連接時向客戶端發(fā)送消息
    // 監(jiān)聽客戶端發(fā)送的消息
    socket.on('msg', data => {
        socket.emit('msg', '收到担败!')
    })
    // 監(jiān)聽連接關(guān)閉
    socket.on('disconnect', () => {
        console.log('連接關(guān)閉')
    })
})

io.sockets.emit('String', msg) // 給所有客戶端廣播消息
io.sockets.socket(socketId).emit('String', msg) // 給指定客戶端廣播消息
2.客戶端的使用
<script src='/socket.io/socket.io.js'></script>
let socket = io.connect('http://127.0.0.1:3000') // 建立連接
socket.on('connect', () => {
    console.log('已建立連接')
    socket.emit('open') // 打開通道
})
// 監(jiān)聽服務(wù)器發(fā)送的消息
socket.on('msg', msg => {
    console.log(msg)
})
// socket.disconnect() // 調(diào)用disconnect()方法斷開連接
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昔穴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子提前,更是在濱河造成了極大的恐慌吗货,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狈网,死亡現(xiàn)場離奇詭異宙搬,居然都是意外死亡,警方通過查閱死者的電腦和手機拓哺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門勇垛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人士鸥,你說我怎么就攤上這事闲孤。” “怎么了烤礁?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵讼积,是天一觀的道長肥照。 經(jīng)常有香客問我,道長勤众,這世上最難降的妖魔是什么建峭? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮决摧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凑兰。我一直安慰自己掌桩,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布姑食。 她就那樣靜靜地躺著波岛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪音半。 梳的紋絲不亂的頭發(fā)上则拷,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音曹鸠,去河邊找鬼煌茬。 笑死,一個胖子當(dāng)著我的面吹牛彻桃,可吹牛的內(nèi)容都是我干的坛善。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼邻眷,長吁一口氣:“原來是場噩夢啊……” “哼眠屎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肆饶,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤改衩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后驯镊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體葫督,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年阿宅,在試婚紗的時候發(fā)現(xiàn)自己被綠了候衍。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡洒放,死狀恐怖蛉鹿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情往湿,我是刑警寧澤妖异,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布惋戏,位于F島的核電站,受9級特大地震影響他膳,放射性物質(zhì)發(fā)生泄漏响逢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一棕孙、第九天 我趴在偏房一處隱蔽的房頂上張望舔亭。 院中可真熱鬧,春花似錦蟀俊、人聲如沸钦铺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矛洞。三九已至,卻和暖如春烫映,著一層夾襖步出監(jiān)牢的瞬間沼本,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工锭沟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抽兆,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓冈钦,卻偏偏與公主長得像郊丛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瞧筛,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354