一. 簡介
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ù):
- exports:用來將模塊內(nèi)部的局部變量或局部函數(shù)暴露給外部
- require:用來引用外部的模塊
- module:代表當(dāng)前模塊本身,exports就是module的屬性,我們既可以用exports導(dǎo)出,也可以用module.exports導(dǎo)出
- __filename:當(dāng)前模塊的完整路徑
- __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)用骨架
- 使用 npm install express-generator -g 全局安裝
- 通過 express --view=ejs myapp 快速創(chuàng)建應(yīng)用(ejs為使用的模板檐春,myapp為應(yīng)用名稱)
- 啟動應(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)置中間件主要有以下三種:
- express.static 提供靜態(tài)資源
- express.json 解析JSON格式的post請求參數(shù)
- 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()方法斷開連接