Nodejs基礎(chǔ)部分
為什么要學(xué)習(xí)Node?
- Node使用Js語(yǔ)法去開(kāi)發(fā)后端應(yīng)用
- 一些公司要求前端掌握Node開(kāi)發(fā),公司業(yè)務(wù)已經(jīng)在用Node開(kāi)發(fā)
- Node生態(tài)系統(tǒng)比較活躍,有大量的開(kāi)源庫(kù)可以使用
- 現(xiàn)在又很多的前端開(kāi)發(fā)工具大多都是基于Node開(kāi)發(fā)的
Node是什么?
Node是一個(gè)基于Chrome V8引擎的Javascript代碼運(yùn)行環(huán)境
瀏覽器(軟件)能夠運(yùn)行Javascript代碼,瀏覽器就是Javascript代碼的運(yùn)行環(huán)境
Node(軟件)能夠運(yùn)行Javascript代碼,Node就是Javascript代碼的運(yùn)行環(huán)境
在瀏覽器中全局對(duì)象是window,在node中全局對(duì)象是global
global對(duì)象下面同樣也有console.log\setTimeout等方法,只不過(guò)和window重名,并不是window的方法
Node運(yùn)行環(huán)境搭建
1. 安裝
- Node官方推薦大家使用Node穩(wěn)定版本(LTS版本)
- NodeJs官網(wǎng)
- 可以使用系統(tǒng)命令行工具powershell(在開(kāi)始菜單搜索
powershell即可打開(kāi))查看當(dāng)前node安裝版本
2. 安裝失敗解決方法
1. 錯(cuò)誤代號(hào) 2502、2503
失敗原因:系統(tǒng)賬戶權(quán)限不足
解決:
以管理員身份運(yùn)行powershell命令行工具
輸入運(yùn)行安裝包命令 msiexec /package (node安裝包位置路徑\安裝包名字)
比如msiexec /package C:\Node\node\node-v10.13.0-x64.msi
2. 執(zhí)行命令報(bào)錯(cuò)
命令行中輸入 node -v 報(bào)錯(cuò)
失敗原因: Node安裝目錄寫(xiě)入環(huán)境變量失敗
解決:將Node安裝目錄手動(dòng)添加到環(huán)境變量,配置完畢重啟命令行窗口
PATH環(huán)境變量:存儲(chǔ)系統(tǒng)中的目錄,在命令行中執(zhí)行命令的時(shí)候系統(tǒng)會(huì)自動(dòng)去這些目錄中查找命令的位置
Nodejs入門(mén)
1. Nodejs的組成
- Javascript 由三部分組成: ECMAScript、DOM、BOM
- Nodejs是由ECMAScript及Node環(huán)境提供的一些附加API組成,包括文件、網(wǎng)絡(luò)、路徑等等一些更加強(qiáng)大的API
2. Nodejs基礎(chǔ)語(yǔ)法
- 所有的ECMAScript語(yǔ)法在Node環(huán)境中都可以使用,也就是說(shuō)Js中變量聲明,循環(huán),條件語(yǔ)句等等,在Node環(huán)境下都可以使用
小技巧:
- 找到要打開(kāi)的文件目錄,按住shift鍵,點(diǎn)擊右鍵,會(huì)出現(xiàn)‘在此處打開(kāi)powershell窗口’,可以直接在命令行工具中打開(kāi)想要執(zhí)行的文件
- 在命令行工具中執(zhí)行node命令時(shí),如果文件名過(guò)長(zhǎng),我們只需要輸入文件名的前幾個(gè)字符,然后按下Tab鍵,命令行工具會(huì)自動(dòng)補(bǔ)全我們要打開(kāi)的文件名,以及文件的路徑。
- 之前執(zhí)行過(guò)的命令只需要按鍵盤(pán)的上鍵就可以輸出執(zhí)行過(guò)的命令行
- 命令行中輸入clear然后按回車(chē),所有之前輸出的命令都被清除掉了
Nodejs模塊化開(kāi)發(fā)
1. 開(kāi)發(fā)規(guī)范
- Nodejs規(guī)定一個(gè)Javascript文件就是一個(gè)模塊,模塊內(nèi)部定義的變量和函數(shù)默認(rèn)情況下外部無(wú)法得到
- 模塊內(nèi)部可以使用
exports對(duì)象進(jìn)行成員導(dǎo)出,使用require方法進(jìn)行導(dǎo)入其他模塊
2. 成員導(dǎo)出
var version = 1.0;
const sayHi = name => `你好,${ name }`;
exports.version = version;
exports.sayHi = sayHi;
3. 成員導(dǎo)入
let a = require('./b.js');
// 導(dǎo)入時(shí)后綴可以去掉
console.log(a.version)
console.log(a.sayHi('zhangsan'));
4. 成員導(dǎo)出的另一種方式
module.exports.version = version;
module.exports.sayHi = sayHi;
exports 是 module.exports 的別名(地址引用關(guān)系),導(dǎo)出對(duì)象最終以 module.exports 為準(zhǔn)
Nodejs系統(tǒng)模塊
系統(tǒng)模塊: Node運(yùn)行環(huán)境提供的api,因?yàn)檫@些api都是以模塊化的方式開(kāi)發(fā)的,所以我們又稱(chēng)Node運(yùn)行環(huán)境提供的api為系統(tǒng)模塊
一、 fs模塊(文件操作系統(tǒng))
二、 path模塊 (路徑操作)
1. 路徑拼接
為什么要進(jìn)行路徑拼接?
- 不同的操作系統(tǒng)路徑的分隔符不統(tǒng)一
- /public/uploads/a
- Windows上是\
- Linux上是/
- 我們現(xiàn)在寫(xiě)的代碼有可能要運(yùn)行在Linux操作系統(tǒng)的,因?yàn)長(zhǎng)inux通常被用作運(yùn)行網(wǎng)站的服務(wù)器
比如網(wǎng)站中的頭像上傳功能,用戶上傳的頭像文件實(shí)際上是要存儲(chǔ)在服務(wù)器硬盤(pán)的某個(gè)文件夾中的,那么我們?cè)诔绦蛭募幸胝业竭@個(gè)文件夾,就必須要拼接這個(gè)文件夾的路徑,我們就需要使用系統(tǒng)模塊path,它在內(nèi)部會(huì)判斷你當(dāng)前使用的操作系統(tǒng)是什么,然后使用操作系統(tǒng)對(duì)應(yīng)的路徑分隔符進(jìn)行拼接。
語(yǔ)法: path.join('路徑', '路徑', ...)
const path = require('path');
const finaPath = path.join('public', 'uploads', 'a');
console.log(finaPath)
相對(duì)路徑VS絕對(duì)路徑
- 大多數(shù)時(shí)候使用絕對(duì)路徑,因?yàn)橄鄬?duì)路徑有時(shí)候相對(duì)的是命令行工具的當(dāng)前工作目錄
- 在讀取文件或設(shè)置文件路徑時(shí)都會(huì)選擇絕對(duì)路徑
- 使用__dirname獲取當(dāng)前文件所在的絕對(duì)路徑
fs.readFile(path.join(__dirname, 'demo'), 'utf-8', (err, doc) => {
if(err == null) {
console.log(doc)
}
})
Nodejs第三方模塊
什么是第三方模塊?
別人寫(xiě)好的,具有特定功能的,我們能直接使用的模塊就是第三方模塊,由于第三方模塊通常都是由多個(gè)文件組成并且被放置在一個(gè)文件夾中,所有又稱(chēng)為包。
1. 獲取第三方模塊
npmjs.com: 第三方模塊的存儲(chǔ)和分發(fā)倉(cāng)庫(kù)
npm(node package manager): node的第三方模塊管理工具
- 下載:npm install 模塊名稱(chēng)
- 卸載:npm uninstall 模塊名稱(chēng)
全局安裝和本地安裝
- 命令行工具:全局安裝
- 庫(kù)文件:本地安裝
2. nodemon 第三方模塊
下載:
npm install nodemon -g
nodemon是一個(gè)命令行工具,用于輔助開(kāi)發(fā)
在Nodejs中,每次修改文件都要在命令行中重新執(zhí)行該文件,非常繁瑣
nodemon能夠在文件修改后自動(dòng)執(zhí)行
3. nrm 第三方模塊
- 下載
npm install -g nrm
- 這個(gè)模塊的主要作用就是切換第三方模塊的下載地址
- 因?yàn)閚pm的下載地址在國(guó)外,下載過(guò)程時(shí)間往往比較長(zhǎng),使用
nrm ls命令可以查看當(dāng)前可切換的所有地址 - 然后我們使用命令
nrm use taobao就可以切換下載地址為淘寶鏡像 - 我們重新運(yùn)行使用npm install ***的時(shí)候,會(huì)發(fā)現(xiàn)下載速度明顯提升,主要原因就是切換了下載地址為國(guó)內(nèi)的淘寶鏡像
4. Gulp 第三方模塊
基于node平臺(tái)開(kāi)發(fā)的前端構(gòu)建工具
- 項(xiàng)目上線,html、css、js合并壓縮
- 語(yǔ)法轉(zhuǎn)換(less、es6...)
- 公共文件抽離
- 修改文件瀏覽器自動(dòng)刷新
gulp使用
- 庫(kù)文件下載:
npm install gulp
項(xiàng)目的根目錄下建立
gulpfile.js文件,這個(gè)文件名是固定的不能更改重構(gòu)項(xiàng)目的文件夾結(jié)構(gòu),
src目錄放置源代碼,dist目錄放置構(gòu)建后文件在
gulpfile.js中編寫(xiě)任務(wù)在命令行工具中執(zhí)行
gulp任務(wù)
gulp 提供的方法
-
gulp.src()獲取任務(wù)要處理的文件 -
gulp.dest()輸出文件 -
gulp.task()建立gulp任務(wù) -
gulp.watch()監(jiān)聽(tīng)文件變化 -
gulp.pipe()執(zhí)行函數(shù)
gulp 命令行工具
命令行工具下載:
npm install gulp-cli -g
gulp 插件
gulp本身屬于輕內(nèi)核的第三方模塊,所提供的方法只有上面四種,所有的其他功能都是通過(guò)插件的方式實(shí)現(xiàn)的。
比如:
- gulp-htmlmin:html文件壓縮
- gulp-csso:壓縮css
- gulp-babel:Javascript語(yǔ)法轉(zhuǎn)化
- gulp-less:less語(yǔ)法轉(zhuǎn)化
- gulp-uglify:壓縮混淆Javascript
- gulp-file-include:公共文件包含
- browsersync:瀏覽器實(shí)時(shí)同步
var gulp = require('gulp');//基礎(chǔ)庫(kù)
// 引入我們的gulp組件
/*html*/
var sass = require('gulp-ruby-sass'), // CSS預(yù)處理/Sass編譯
cssmin = require('gulp-minify-css'), //壓縮css
useref = require('gulp-useref'),//
rev = require('gulp-rev'),
htmlmin =require('gulp-htmlmin'),
revReplace = require('gulp-rev-replace'),
revCollector = require('gulp-rev-collector'), //gulp-rev插件 用于html更改版本后的路徑替換
uglify = require('gulp-uglify'), // JS文件壓縮
eslint = require('gulp-eslint'),
imagemin = require('gulp-imagemin'), // imagemin 圖片壓縮
// pngquant = require('imagemin-pngquant'), // imagemin 深度壓縮
rename = require('gulp-rename'), // 文件重命名
changed = require('gulp-changed'), // 只操作有過(guò)修改的文件
concat = require("gulp-concat"), // 文件合并
clean = require('gulp-clean'); // 文件清理
sourcemaps = require('gulp-sourcemaps');
var livereload = require('gulp-livereload'),//網(wǎng)頁(yè)自動(dòng)刷新
webserver =require('gulp-webserver');//本地服務(wù)器
var usemin = require('gulp-usemin');
var babel = require('gulp-babel');//配置es6
var plumber = require('gulp-plumber'); //輸出錯(cuò)誤日志
var sequence = require('gulp-sequence');
/* 全局設(shè)置
* -------------------------------*/
var srcPath ={
html: 'src',
sass: 'src/sass',
css:'src/css',
script:'src/js',
image: 'src/images',
json: 'src/json'
};
var destPath = {
html: 'dist',
css: 'dist/css',
script: 'dist/js',
image: 'dist/images'
};
/*html 樣式 圖片 js處理*/
//更換html里面的路徑
gulp.task('htmlmin',function(){
var opations ={
removeComments: true,//清除HTML注釋
collapseWhitespace: true,//壓縮HTML
collapseBooleanAttributes: true,//省略布爾屬性的值 <input checked="true"/> ==> <input />
removeEmptyAttributes: true,//刪除所有空格作屬性值 <input id="" /> ==> <input />
//removeScriptTypeAttributes: true,//刪除<script>的type="text/javascript"
//removeStyleLinkTypeAttributes: true,//刪除<style>和<link>的type="text/css"
//minifyJS: true,//壓縮頁(yè)面JS
minifyCSS: true//壓縮頁(yè)面CSS
};
gulp.src(destPath.html)
.pipe(htmlmin(opations))
.pipe(gulp.dest(destPath.html))
});
// 清理文件
gulp.task('clean', function() {
return gulp.src( [destPath.css+'/*.css',srcPath.css+'/*.css'], {read: false} ) // 清理maps文件
.pipe(clean());
});
gulp.task('rev',function(){
return gulp.src(['src/json/rev-manifest.json',srcPath.html+'/**/*.html'])
.pipe( revCollector({
replaceReved: true
}) )
.pipe(changed( destPath.html ))
.pipe(gulp.dest( destPath.html ));
});
gulp.task("sass",function(){
return sass('src/sass/**/*.sass',{style: 'compact', sourcemap: true})
.on('error', function(err){
console.error('Error!',err.message);//顯示編譯錯(cuò)誤信息
})
.pipe(sourcemaps.init())//地圖存放
.pipe(rename({ suffix: '.min' })) // 重命名
.pipe(gulp.dest('src/css'))
});
gulp.task('cssmin', function () {
gulp.src('src/css/*.css')
.pipe(cssmin())
.pipe(rev())
.pipe(gulp.dest('dist/css'))
.pipe(rev.manifest())
.pipe(gulp.dest(srcPath.json));
});
gulp.task('sequence', function (done) {
sequence(
'clean',['sass','cssmin','rev'],done)
});
gulp.task('eslint',function(){
return gulp.src([srcPath.script+'/!*.js','!node_modules/!**'])
//.pipe(eslint({configFile: 'eslintrc.json'}))
.pipe(eslint.format())
.pipe(eslint.failAfterError());
});
gulp.task('script', function() {
return gulp.src( [srcPath.script+'/*.js'] ) // 指明源文件路徑、并進(jìn)行文件匹配,排除 .min.js 后綴的文件
.pipe(changed( destPath.script )) // 對(duì)應(yīng)匹配的文件
.pipe(sourcemaps.init()) // 執(zhí)行sourcemaps
.pipe(rename({ suffix: '.min' })) // 重命名
.pipe(uglify()) // 使用uglify進(jìn)行壓縮,并保留部分注釋
.pipe(sourcemaps.write('maps')) // 地圖輸出路徑(存放位置)
.pipe(gulp.dest( destPath.script )); // 輸出路徑
});
// imagemin 圖片壓縮
gulp.task('images', function(){
return gulp.src( srcPath.image+'/**/*' ) // 指明源文件路徑,如需匹配指定格式的文件,可以寫(xiě)成 .{png,jpg,gif,svg}
.pipe(changed( destPath.image ))
.pipe(imagemin({
progressive: true, // 無(wú)損壓縮JPG圖片
svgoPlugins: [{removeViewBox: false}] // 不要移除svg的viewbox屬性
//use: [pngquant()] // 深度壓縮PNG
}))
.pipe(gulp.dest( destPath.image )); // 輸出路徑
});
gulp.task('concat', function () {
return gulp.src( srcPath.script+'/*.min.js' ) // 要合并的文件
.pipe(concat('libs.js')) // 合并成libs.js
.pipe(rename({ suffix: '.min' })) // 重命名
.pipe(gulp.dest( destPath.script )); // 輸出路徑
});
gulp.task('useref', function () {
return gulp.src('src/*.html')
.pipe(useref())
.pipe(gulp.dest('src'));
});
// 文件合并
gulp.task('concat', function () {
return gulp.src( srcPath.script+'/*.min.js' ) // 要合并的文件
.pipe(concat('libs.js')) // 合并成libs.js
.pipe(rename({ suffix: '.min' })) // 重命名
.pipe(gulp.dest( destPath.script )); // 輸出路徑
});
// 本地服務(wù)器
gulp.task('webserver', function() {
gulp.src( destPath.html ) // 服務(wù)器目錄(.代表根目錄)
.pipe(webserver({ // 運(yùn)行g(shù)ulp-webserver
livereload: true, // 啟用LiveReload
open: true // 服務(wù)器啟動(dòng)時(shí)自動(dòng)打開(kāi)網(wǎng)頁(yè)
}));
});
// 默認(rèn)任務(wù)
gulp.task('webserver',function(){
gulp.src('./')//服務(wù)器跟目錄
.pipe(webserver({
livereload:true,//啟用livereload
open:true //服務(wù)器啟動(dòng)時(shí)自動(dòng)打開(kāi)頁(yè)面
}))
});
//壓縮合并js 還有css
gulp.task('usemin', function() {
return gulp.src(srcPath.html+'/**/*.html')
.pipe(usemin({
js: [ uglify()],
css:[ cssmin() ]
}))
.pipe(gulp.dest(srcPath.html));
});
// 監(jiān)聽(tīng)任務(wù)
gulp.task('watch',function(){
gulp.watch( 'src/sass/**/*.sass', ['sequence']);/// / 監(jiān)聽(tīng)根目錄下所有.html文件
});
gulp.task('default',['webserver','watch']); //默認(rèn)任務(wù)
/*es6*/
gulp.task('es6', function() {
return gulp.src('src/es6/app.js')
.pipe(plumber())
.pipe(babel({
presets: ['es2015']
}))
.pipe(gulp.dest('dist/js'));
});
/*-------------- 發(fā)布環(huán)境*/
/* = 發(fā)布環(huán)境( Release Task )
-------------------------------------------------------------- */
// 樣式處理
gulp.task('sassRelease', function () {
return sass( srcPath.css, { style: 'compressed' }) // 指明源文件路徑、并進(jìn)行文件匹配(編譯風(fēng)格:壓縮)
.on('error', function (err) {
console.error('Error!', err.message); // 顯示錯(cuò)誤信息
})
.pipe(gulp.dest( destPath.css )); // 輸出路徑
});
// 腳本壓縮&重命名
gulp.task('scriptRelease', function() {
return gulp.src( [srcPath.script+'/*.js','!'+srcPath.script+'/*.min.js'] ) // 指明源文件路徑、并進(jìn)行文件匹配,排除 .min.js 后綴的文件
.pipe(rename({ suffix: '.min' })) // 重命名
.pipe(uglify({ preserveComments:'some' })) // 使用uglify進(jìn)行壓縮,并保留部分注釋
.pipe(gulp.dest( destPath.script )); // 輸出路徑
});
// 打包發(fā)布
gulp.task('release', ['clean'], function(){ // 開(kāi)始任務(wù)前會(huì)先執(zhí)行[clean]任務(wù)
return gulp.start('sassRelease','scriptRelease','images'); // 等[clean]任務(wù)執(zhí)行完畢后再執(zhí)行其他任務(wù)
});
/* = 幫助提示( Help )
-------------------------------------------------------------- */
gulp.task('help',function () {
console.log('----------------- 開(kāi)發(fā)環(huán)境 -----------------');
console.log('gulp default 開(kāi)發(fā)環(huán)境(默認(rèn)任務(wù))');
console.log('gulp html HTML處理');
console.log('gulp sass 樣式處理');
console.log('gulp script JS文件壓縮&重命名');
console.log('gulp images 圖片壓縮');
console.log('gulp concat 文件合并');
console.log('----------------發(fā)布環(huán)境-----------------');
console.log('gulp release 打包發(fā)布');
console.log('gulp clean 清理文件');
console.log('gulp sassRelease 樣式處理');
console.log('gulp scriptRelease 腳本壓縮&重命名');
console.log('---------------------------------------------');
});
package.json 文件
package.json 是項(xiàng)目的描述文件,記錄了當(dāng)前項(xiàng)目信息,例如項(xiàng)目名稱(chēng)、版本、作者、GitHub地址、當(dāng)前項(xiàng)目依賴(lài)了哪些第三方模塊
使用 npm init -y 命令可以快速生成該文件 -y意思就是不填寫(xiě)任何信息全部使用默認(rèn)。
使用 npm install 可以自動(dòng)下載描述文件中的所有第三方模塊
1. 項(xiàng)目依賴(lài)
- 在項(xiàng)目開(kāi)發(fā)階段和線上運(yùn)行階段,都需要依賴(lài)的第三方模塊,稱(chēng)為項(xiàng)目依賴(lài)
- 使用
npm install命令下載會(huì)默認(rèn)被添加到package.json文件的dependencies字段中
2. 開(kāi)發(fā)依賴(lài)
- 在項(xiàng)目開(kāi)發(fā)階段需要依賴(lài),線上運(yùn)行階段不需要依賴(lài)的第三方包,稱(chēng)為開(kāi)發(fā)依賴(lài)
- 使用
npm install命令下載會(huì)默認(rèn)被添加到package.json文件的devDependencies字段中
3. package-lock.json文件的作用
- 鎖定包的版本,確保再次下載的時(shí)候不會(huì)因?yàn)榘姹静煌a(chǎn)生問(wèn)題
- 加快下載速度,因?yàn)樵撐募呀?jīng)記錄了項(xiàng)目所依賴(lài)的第三方包的樹(shù)狀結(jié)構(gòu)和包的下載地址,重新安裝時(shí)只需下載即可,不需要做額外工作
Nodejs模塊的加載機(jī)制
- require方法根據(jù)模塊路徑查找模塊,如果時(shí)完整路徑,直接引入模塊
- 如果模塊后綴省略,先找同名js文件再找同名文件夾
- 如果找到了同名文件夾,找文件夾中的index.js
- 如果文件夾中沒(méi)有index.js就會(huì)去當(dāng)前文件夾中的package.js文件中查找選項(xiàng)中的入口文件
Nodejs服務(wù)器端部分
- 網(wǎng)站應(yīng)用程序主要是由客戶端和服務(wù)器端組成
- 客戶端:在瀏覽器中運(yùn)行的部分,就是用戶可以看到并與之交互的界面程序,使用html\css\js構(gòu)建
- 服務(wù)器端:在服務(wù)器中運(yùn)行的部分,負(fù)責(zé)存儲(chǔ)數(shù)據(jù)和處理應(yīng)用邏輯
- Node網(wǎng)站服務(wù)器:能夠接收客戶端請(qǐng)求,并且能夠響應(yīng)請(qǐng)求
1. 創(chuàng)建web服務(wù)器
// 引用系統(tǒng)模塊
const http = require('http');
// 創(chuàng)建web服務(wù)器
const app = http.createServer();
// 當(dāng)客戶發(fā)送請(qǐng)求的時(shí)候
// 為服務(wù)器添加request事件
app.on('request', (req, res) => {
// 請(qǐng)求響應(yīng)
res.end('<h1>hi, user</h1>');
})
// 監(jiān)聽(tīng)端口,listen方法
app.listen(3000)
console.log('服務(wù)器已啟動(dòng),訪問(wèn)localhost:3000')
2. http請(qǐng)求與響應(yīng)處理
客戶端和服務(wù)器端的溝通規(guī)范
HTTP: 超文本傳輸協(xié)議
報(bào)文
在http響應(yīng)和請(qǐng)求過(guò)程中傳遞的數(shù)據(jù)塊就叫報(bào)文,包括要傳送的數(shù)據(jù)和一些附加信息,并且要遵守規(guī)定好的格式
請(qǐng)求報(bào)文
客戶端對(duì)服務(wù)器端說(shuō)的話
請(qǐng)求方式:
- GET 獲取數(shù)據(jù)的請(qǐng)求一般用get
- POST 添加數(shù)據(jù)的請(qǐng)求一般用post,一般的邏輯比如登錄操作,也用post
const http = require('http');
const app = http.createServer();
app.on('request', (req, res) => {
// 獲取請(qǐng)求方式
console.log(req.method);
})
app.listen(3000)
通過(guò)地址欄輸入網(wǎng)址的形式一般發(fā)送的是get請(qǐng)求
表單提交一般用post
// method 指定當(dāng)前表單的提交方式
// action 指定當(dāng)前表單提交的地址
<form method="post" action="http://localhost:3000">
<input type="submit" value="提交"/>
</form>
請(qǐng)求地址:
app.on('request', (req, res) => {
// 通過(guò)req.method 來(lái)獲取請(qǐng)求方式
console.log(req.method)
// 通過(guò) req.url 來(lái)獲取請(qǐng)求地址
console.log(req.url)
// 獲取請(qǐng)求報(bào)文信息
console.log(req.headers)
})
下面我們要實(shí)現(xiàn)根據(jù)不同的請(qǐng)求地址響應(yīng)不同的頁(yè)面
const http = require('http');
const app = http.createServer();
app.on('request', (req, res) => {
if( req.url == '/index' || req.url == '/') {
res.end('我是首頁(yè)')
} else if(req.url == '/list') {
res.end('我是list')
} else {
res.end('頁(yè)面不存在')
}
})
app.listen(3000)
請(qǐng)求參數(shù):
get請(qǐng)求參數(shù):
在服務(wù)器端如何獲取到請(qǐng)求參數(shù)?
const http = require('http');
// 用于處理url地址的內(nèi)置模塊
const url = require('url);
const app = http.createServer();
app.on('request', (req, res) => {
// 通過(guò)url.parse()方法可以返回請(qǐng)求地址以及參數(shù)的一些內(nèi)容
// 第一個(gè)參數(shù)為要解析的url地址
// 第二個(gè)參數(shù)為將查詢(xún)參數(shù)解析成對(duì)象的形式
let {query, pathname} = url.parse(req.url, true);
if( pathname == '/index' || pathname == '/') {
res.end('我是首頁(yè)')
} else if(pathname == '/list') {
res.end('我是list')
} else {
res.end('頁(yè)面不存在')
}
})
app.listen(3000)
post請(qǐng)求參數(shù):
- 參數(shù)被放置在請(qǐng)求體中進(jìn)行傳輸
- 獲取post參數(shù)需要使用data事件和end事件
- 使用querystring系統(tǒng)模塊將參數(shù)轉(zhuǎn)換為對(duì)象格式
// html部分
<form action="http://localhost:3000" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="提交">
</form>
// js
const http = require('http');
const app = http.createServer();
// 處理請(qǐng)求參數(shù)模塊
const querystring = require('querystring');
app.on('request', (req, res) => {
// post是通過(guò)事件來(lái)接收的,data事件和end事件
// data當(dāng)請(qǐng)求參數(shù)傳遞的時(shí)候觸發(fā)data事件
// 當(dāng)參數(shù)傳遞完成的時(shí)候觸發(fā)end事件
// post參數(shù)不是一次就接收完的,我們需要先聲明一個(gè)變量
let postParams = '';
// 監(jiān)聽(tīng)參數(shù)傳輸事件
req.on('data', params => {
postParams += params;
})
// 監(jiān)聽(tīng)參數(shù)傳輸完畢事件
req.on('end', () => {
// 這個(gè)時(shí)候我們得到的內(nèi)容是一個(gè)參數(shù)字符串(username=zhangsan&password=1234)
// 這個(gè)時(shí)候我們還可以使用url模塊來(lái)處理參數(shù)嗎?
// url模塊是用來(lái)處理請(qǐng)求地址的,現(xiàn)在我們是直接想處理這樣的一個(gè)字符串
// 如果這時(shí)候要轉(zhuǎn)換成對(duì)象的形式
console.log(querystring.stringify(postParams));
})
// 對(duì)于客戶端的每一次請(qǐng)求,服務(wù)器端都要去做出響應(yīng),所以end必須要加!否則就會(huì)處于一個(gè)等待狀態(tài)
res.end('OK')
})
app.listen(3000)
響應(yīng)報(bào)文
服務(wù)器端對(duì)客戶端說(shuō)的話
HTTP狀態(tài)碼
- 200請(qǐng)求成功
- 404請(qǐng)求的資源沒(méi)有找到
- 500服務(wù)器端錯(cuò)誤
- 400客戶端請(qǐng)求有語(yǔ)法錯(cuò)誤
設(shè)置狀態(tài)碼
const http = require('http');
const app = http.createServer();
app.on('request', (req, res) => {
// 設(shè)置狀態(tài)碼
res.writeHead(400)
res.end('hello')
})
app.listen(3000)
內(nèi)容類(lèi)型
const http = require('http');
const app = http.createServer();
app.on('request', (req, res) => {
// 第一個(gè)參數(shù)為狀態(tài)碼
// 第二個(gè)參數(shù)為一個(gè)對(duì)象,可以設(shè)置內(nèi)容類(lèi)型,編碼類(lèi)型
res.writeHead(200, {
'content-type': 'text/plain;charset=utf8'
})
res.end('<h1>你好nodejs</h1>')
})
app.listen(3000)
路由
路由是指客戶端請(qǐng)求地址與服務(wù)器端程序代碼的對(duì)應(yīng)關(guān)系,簡(jiǎn)單的說(shuō),就是請(qǐng)求什么響應(yīng)什么
// 實(shí)現(xiàn)路由功能
// 獲取客戶端的請(qǐng)求方式
// 獲取客戶端的請(qǐng)求地址
const http = require('http');
const url = require('url');
const app = http.createServer();
app.on('request', (req, res) => {
// 獲取請(qǐng)求方式
const method = req.method.toLowerCase();
// 獲取請(qǐng)求地址
const pathname = url.parse(req.url).pathname
res.writeHead('200', {
'content-type': 'text/html;charset=utf8'
})
if(method == 'get') {
if(pathname == '/' || pathname == '/index') {
res.end('home')
} else if (pathname == '/list') {
res.end('list')
} else {
res.end('no page')
}
}else if(method == 'post') {
}
})
app.listen(3000)
靜態(tài)資源訪問(wèn)
服務(wù)器不需要處理,可以直接響應(yīng)給客戶端的資源就是靜態(tài)資源比如css、js、image文件
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');
const app = http.createServer();
app.on('request', (req, res) => {
let pathname = url.parse(req.url).pathname;
pathname == '/' ? 'default.html' : pathname;
// 拼接查找靜態(tài)文件存儲(chǔ)的地址
let realPath = path.join(__dirname, 'public' + pathname);
// 利用第三方模塊mime返回要訪問(wèn)的文件的類(lèi)型
let type = mime.getType(realPath);
// 讀取文件
fs.readFile(realPath, (error, result) => {
if(error != null) {
res.writeHead(404, {
'content-type': 'text/html;charset=utf8'
})
res.end('文件讀取失敗!');
return;
}
res.writeHead(200, {
'content-type': type
})
res.end(result);
})
})
app.listen(3000)
動(dòng)態(tài)資源訪問(wèn)
相同的請(qǐng)求地址可以傳遞不同的請(qǐng)求參數(shù),得到不同的響應(yīng)資源,這就是動(dòng)態(tài)資源
3. Nodejs異步編程
1. 同步API,異步API
同步API:只有當(dāng)前API執(zhí)行完成后,才能繼續(xù)執(zhí)行下一個(gè)API
console.log(111)
console.log(222)
// 111,222
異步API:當(dāng)前API的執(zhí)行不會(huì)阻塞后續(xù)代碼的執(zhí)行
console.log(111)
setTimeout(() => {
console.log(222)
}, 2000)
console.log(333)
// 打印結(jié)果為111,333,222
// 也就是說(shuō)定時(shí)器并沒(méi)有阻塞后邊代碼的執(zhí)行
同步API可以從返回值中拿到API執(zhí)行的結(jié)果,但是異步API是不可以的
// 同步
function sum (a, b) {
return a + b
}
const result = sum(1, 2)
// 異步
function getMsg() {
setTimeout(() => {
return {msg: 'hello'}
}, 2000)
}
const msg = getMsg() // undefined
// 異步API中我們是無(wú)法通過(guò)返回值的方式去拿到返回結(jié)果的
異步API可以使用回調(diào)函數(shù)拿到返回結(jié)果
function getMsg(callback) {
setTimeout(() => {
callback({
msg: 'hello'
})
}, 2000)
}
getMsg((data) => {
console.log(data) // hello
})
同步API和異步API的執(zhí)行順序不一樣,同步從上到下依次執(zhí)行,前面的代碼會(huì)阻塞后面的代碼的執(zhí)行
異步API不會(huì)等待API執(zhí)行完成后再向下執(zhí)行代碼
可以用Promise解決Nodejs異步編程回調(diào)地獄問(wèn)題
function getData() {
setTimeout(() => {
var name = '張三';
return name;
}, 100)
}
console.log(getData())// 這時(shí)候我們是獲取不到內(nèi)部name的數(shù)據(jù)的
解決方法一:利用增加一個(gè)callback的方式
function getData(callback) {
setTimeout(() => {
var name = '張三';
callback(name);
}, 100)
}
getData(function(aaa) {
console.log(aaa) // 這個(gè)aaa就是上面的name
})
解決方法二:Promise解決
var p = new Promise(function(resole, reject) {
setTimeout(() => {
var name = '張三';
resolve(name);
}, 100)
})
// 獲取name
p.then((data) => {
console.log(data); // 此時(shí)的data就是name的數(shù)據(jù)
})
使用async和await:
/**
* 普通方法
function test() {
return '你好,nodejs';
}
console.log(test());
*/
async function test() {
return '你好,nodejs';
}
console.log(test()); // Promise { '你好,nodejs' }
async function main() {
var data = await test() // await必須得用在async里面,獲取異步方法里面的數(shù)據(jù)
console.log(data);
}
main();
異步函數(shù)(async和await)是異步編程語(yǔ)法的終極解決方案,它可以讓我們將異步代碼寫(xiě)成同步形式,讓代碼不再有回調(diào)函數(shù)嵌套,使代碼更清晰
async關(guān)鍵字
- 普通函數(shù)定義前加async關(guān)鍵字,普通函數(shù)變成異步函數(shù)
- 異步函數(shù)默認(rèn)返回Promise對(duì)象
- 在異步函數(shù)內(nèi)部使用return關(guān)鍵字進(jìn)行結(jié)果返回,結(jié)果會(huì)被包裹的promise對(duì)象中return關(guān)鍵字替代了resolve方法
- 在異步函數(shù)內(nèi)部使用throw關(guān)鍵字拋出程序異常
- 調(diào)用異步函數(shù)then方法獲取異步函數(shù)執(zhí)行結(jié)果
- 調(diào)用異步函數(shù)catch方法獲取異步函數(shù)執(zhí)行的錯(cuò)誤信息
await關(guān)鍵字
- await只能出現(xiàn)在異步函數(shù)中
- await 后面只能寫(xiě)Promise對(duì)象,寫(xiě)其他類(lèi)型的API是不可以的
- await 關(guān)鍵字可以暫停異步函數(shù)向下執(zhí)行,直到promise返回結(jié)果
數(shù)據(jù)庫(kù)及環(huán)境搭建
1. 為什么要使用數(shù)據(jù)庫(kù)
- 動(dòng)態(tài)網(wǎng)站中的數(shù)據(jù)都是存儲(chǔ)在數(shù)據(jù)庫(kù)中
- 數(shù)據(jù)庫(kù)可以用來(lái)持久存儲(chǔ)客戶端通過(guò)表單收集的用戶信息
- 數(shù)據(jù)庫(kù)軟件本身可以對(duì)數(shù)據(jù)進(jìn)行高效的管理
2. 什么是數(shù)據(jù)庫(kù)
數(shù)據(jù)庫(kù)即存儲(chǔ)數(shù)據(jù)的倉(cāng)庫(kù),可以將數(shù)據(jù)進(jìn)行有序的分門(mén)別類(lèi)的存儲(chǔ),它是獨(dú)立與語(yǔ)言之外的軟件,可以通過(guò)API操作它
常見(jiàn)的數(shù)據(jù)庫(kù)有:mysql、mongoDB、oracle
模板引擎
模板引擎是node的第三方模塊
讓開(kāi)發(fā)者以更加友好的方式拼接字符串,使項(xiàng)目代碼更加清晰易于維護(hù)
1. art-template模板引擎
是由騰訊公司出品,是目前運(yùn)行最快的模板引擎
下載: npm install art-template
使用 const template = require('art-template')引入模板引擎
告訴模板引擎要拼接的數(shù)據(jù)和模板在哪 const html = template('模板路徑', 數(shù)據(jù))
// app.js中引入模板引擎
const path = require('path')
const template = require('art-template')
// template方法是用來(lái)拼接字符串的
// 第一個(gè)參數(shù)是模板路徑,這里要寫(xiě)絕對(duì)路徑
// 第二個(gè)參數(shù)是要在模板中顯示的數(shù)據(jù), 對(duì)象類(lèi)型
// 返回拼接好的字符串
const views = path.join(__dirname, 'views', 'index.art')
const html = template(views, {
name: '張三',
age: 20
})
// index.art模板
// 普通的html結(jié)構(gòu)
<div>
<span>{{name}}</span>
<span>{{age}}</span>
</div>
模板語(yǔ)法
art-template同時(shí)支持兩種模板語(yǔ)法: 標(biāo)準(zhǔn)語(yǔ)法和原始語(yǔ)法
標(biāo)準(zhǔn)語(yǔ)法可以讓模板更容易讀寫(xiě),原始語(yǔ)法具有強(qiáng)大的邏輯處理能力
輸出
兩種語(yǔ)法都是可以進(jìn)行運(yùn)算并返回運(yùn)算結(jié)果的,比如三目運(yùn)算符
標(biāo)準(zhǔn)語(yǔ)法: {{ data }}
原始語(yǔ)法: <%= data %>
原文輸出
如果數(shù)據(jù)中攜帶html標(biāo)簽,默認(rèn)模板引擎是不會(huì)解析標(biāo)簽的,會(huì)將其轉(zhuǎn)義后輸出
假設(shè)content內(nèi)容為<h2>你好</h2>,如果我們需要將h2標(biāo)簽解析后輸出,我們需要如下操作:
標(biāo)準(zhǔn)語(yǔ)法: {{ @ content }}
原始語(yǔ)法:<%- content %>
條件判斷
在模板中可以根據(jù)條件來(lái)決定顯示哪塊html代碼
標(biāo)準(zhǔn)語(yǔ)法:{{ if 條件 }} ... {{ 條件 else if }} ... {{/if}}
{{ if age > 18}}
年齡大于18
{{ else if age < 15 }}
年齡小于十五
{{ else }}
年齡不符合
{{ /if }}
原始語(yǔ)法:<% if (條件) { %> ... <%} %>
<% if (age > 18) { %>
年齡大于18
<% } else if (age < 15) { %>
年齡小于15
<% } else { %>
年齡不符合要求
<% } %>
循環(huán)
標(biāo)準(zhǔn)語(yǔ)法:{{ each 數(shù)據(jù) }} {{ /each }}
{{ each target}}
{{ $index }} {{ $value }}
{{ /each }}
原始語(yǔ)法:
<% for( var i = 0; i < target.length; i++) { %>
<%= i %> <%= target[i] %>
<% } %>
// 標(biāo)準(zhǔn)語(yǔ)法
<ul>
{{each users}}
<li>
{{$value.name}}
{{$value.age}}
</li>
{{/each}}
</ul>
// 原始語(yǔ)法
<ul>
<% for( var i = 0; i < users.length; i++) { %>
<li>
<%= users[i].name %>
<%= users[i].age %>
</li>
<% } %>
</ul>
子模板
使用子模板可以將網(wǎng)站公共部分(頭部等)抽離到單獨(dú)的文件中
標(biāo)準(zhǔn)語(yǔ)法:{{ include '模板' }}
原始語(yǔ)法:<% include('模板') %>
{{ include './header.art' }}
<% include('./header.art') %>
模板繼承
使用模板繼承可以將網(wǎng)站html骨架抽離到單獨(dú)的文件中,其中頁(yè)面模板可以繼承骨架文件
P192-P200部分,未完成
Express框架
Express是一個(gè)基于Node平臺(tái)的web應(yīng)用程序開(kāi)發(fā)框架,它提供了一系列的強(qiáng)大特性,幫助你創(chuàng)建各種web應(yīng)用,我們可以使用 npm install express 命令下載,是node的第三方模塊
1. Express框架特性
(1)提供了方便簡(jiǎn)潔的路由定義方式
所謂簡(jiǎn)潔路由方式就比如我們之前使用router這個(gè)第三方模塊定義路由的方式,router這個(gè)第三方模塊實(shí)際上就是從express框架中抽取出來(lái)的
(2)對(duì)獲取http請(qǐng)求參數(shù)進(jìn)行了簡(jiǎn)化處理
框架提供了更加簡(jiǎn)潔的方式接收請(qǐng)求參數(shù),也就是說(shuō)使用express框架不再對(duì)請(qǐng)求參數(shù)的格式進(jìn)行轉(zhuǎn)化,框架讓我們拿到的直接就是對(duì)象類(lèi)型,現(xiàn)在我們也不需要對(duì)請(qǐng)求對(duì)象添加data事件以及end事件,框架內(nèi)部接收完請(qǐng)求參數(shù)并處理完以后,將參數(shù)作為請(qǐng)求對(duì)象的屬性讓我們直接獲取。
(3)對(duì)模板引擎支持程度高,方便渲染動(dòng)態(tài)html頁(yè)面
框架可以非常容易的和模板引擎協(xié)同工作,方便開(kāi)發(fā)
(4)框架提供了中間件機(jī)制,有效控制http請(qǐng)求
所謂中間件,我們暫且可以簡(jiǎn)單理解為對(duì)請(qǐng)求的攔截
(5)擁有大量第三方中間件對(duì)功能進(jìn)行擴(kuò)展
可以理解為基于框架還有另外的一些插件可以供我們實(shí)現(xiàn)各種功能
Express初體驗(yàn):
// 引入express框架
// 這個(gè)返回值其實(shí)是一個(gè)方法,通過(guò)調(diào)用這個(gè)方法我們就可以創(chuàng)建網(wǎng)站服務(wù)器
// 不再需要引入http模塊創(chuàng)建網(wǎng)站服務(wù)器
const express = require('express')
// 創(chuàng)建網(wǎng)站服務(wù)器
const app = express()
// express路由和router路由第三方模塊的使用方法一致
app.get('/', (req, res) => {
// 對(duì)客戶端進(jìn)行響應(yīng),不再使用原生node的end方法進(jìn)行響應(yīng)
// 取而代之的是send()
// 1.send會(huì)自動(dòng)檢測(cè)響應(yīng)內(nèi)容的類(lèi)型,幫我們把類(lèi)型自動(dòng)設(shè)置到響應(yīng)頭中
// 2.還可以幫我們?cè)O(shè)置響應(yīng)內(nèi)容的類(lèi)型編碼
// 3.還可以幫我們自動(dòng)設(shè)置http狀態(tài)碼
res.send('hello express')
})
app.get('/list', (req, res) => {
// 使用框架可以直接向客戶端響應(yīng)一個(gè)對(duì)象,這在原生的node中end是做不到的
res.send({name: 'zhangsan', age: 20})
})
// 監(jiān)聽(tīng)端口
app.listen(3000);
console.log('網(wǎng)站服務(wù)器啟動(dòng)成功');
2. 中間件
中間件實(shí)際上就是框架提供的一堆方法,可以接收客戶端發(fā)來(lái)的請(qǐng)求,可以對(duì)請(qǐng)求做出響應(yīng),也就是說(shuō)中間件的作用是專(zhuān)門(mén)用來(lái)接收請(qǐng)求處理請(qǐng)求的,對(duì)于同一個(gè)請(qǐng)求express運(yùn)行我們?cè)O(shè)置多個(gè)中間件,會(huì)按照中間件設(shè)置的順序依次處理
中間件分為兩部分:第一部分是框架提供的用于接收請(qǐng)求的中間件方法,第二部分是開(kāi)發(fā)人員提供的用于處理請(qǐng)求的方法(請(qǐng)求處理函數(shù))
比如路由就是中間件,get和post方法就是框架提供的接收請(qǐng)求的方式,方法的第二個(gè)參數(shù)就是開(kāi)發(fā)人員提供的用于處理請(qǐng)求的方法。
可以針對(duì)同一個(gè)請(qǐng)求,設(shè)置多個(gè)中間件,對(duì)同一個(gè)請(qǐng)求多次處理
默認(rèn)情況下,請(qǐng)求從上到下一次匹配中間件,一旦匹配成功,就會(huì)終止匹配
可以調(diào)用next方法將請(qǐng)求的控制權(quán)交給下一個(gè)中間件,知道遇到結(jié)束請(qǐng)求的中間件
app.get('/', (req, res, next) => {
req.name = '張三'
next()
})
app.get('/', (req, res) => {
req.send (req.name)
})
1. app.use中間件用法
我們有時(shí)候接收請(qǐng)求既想接收get請(qǐng)求,也想接收post請(qǐng)求,這時(shí)候我們就會(huì)用到app.use中間件
app.use中間件不區(qū)分接收的請(qǐng)求方式,匹配所有的請(qǐng)求方式,可以直接傳入請(qǐng)求處理函數(shù),代表接收所有的請(qǐng)求
app.use((req, res, next) => {
console.log(req.url)
next()
})
app.use第一個(gè)參數(shù)也可以傳入請(qǐng)求地址,代表不論什么請(qǐng)求方式,只要是這個(gè)請(qǐng)求地址就接收這個(gè)請(qǐng)求
app.use('/admin', (req, res, next) => {
console.log(req.url)
next()
})
// 中間件概念
const express = require('express')
const app = express()
// 接收所有請(qǐng)求的中間件
app.use((req, res, next) => {
console.log('走了app.use中間件1')
next()
})
app.use('/request', (req, res, next) => {
console.log('走了app.use中間件2')
next()
})
// 當(dāng)我們請(qǐng)求路徑設(shè)置為/list,那么此時(shí)就不會(huì)走/request這個(gè)中間件,所以只會(huì)打印'走了app.use中間件1'
app.get('/list', (req, res) => {
res.send('zhangsan')
})
// 監(jiān)聽(tīng)端口
app.listen(3000);
console.log('網(wǎng)站服務(wù)器啟動(dòng)成功');
2. 中間件應(yīng)用
2.1 路由保護(hù)
客戶端在訪問(wèn)需要登錄的頁(yè)面時(shí),可以先用中間件判斷用戶登錄狀態(tài),如果用戶未登錄,則攔截請(qǐng)求,直接響應(yīng),禁止用戶進(jìn)入需要登錄的頁(yè)面
// 中間件應(yīng)用
const express = require('express')
const app = express()
app.use('/admin', (req, res, next) => {
let isLogin = false;
if (isLogin) {
// 讓請(qǐng)求繼續(xù)向下執(zhí)行
next()
} else {
res.send('您還沒(méi)有登錄,拒絕訪問(wèn)')
}
})
app.get('/admin', (req, res) => {
res.send('您已經(jīng)登錄,可以訪問(wèn)')
})
// 監(jiān)聽(tīng)端口
app.listen(3000);
console.log('網(wǎng)站服務(wù)器啟動(dòng)成功');
2.2 網(wǎng)站維護(hù)公告
在所有路由的最上面定義接收所有請(qǐng)求的中間件,直接為客戶端做出響應(yīng),網(wǎng)站正在維護(hù)
// 中間件應(yīng)用
const express = require('express')
const app = express()
app.use((req, res, next) => {
// 此時(shí)并沒(méi)有調(diào)用next方法,此時(shí)不會(huì)繼續(xù)向下執(zhí)行
res.send('當(dāng)前網(wǎng)站正在維護(hù)')
})
app.use('/admin', (req, res, next) => {
let isLogin = false;
if (isLogin) {
// 讓請(qǐng)求繼續(xù)向下執(zhí)行
next()
} else {
res.send('您還沒(méi)有登錄,拒絕訪問(wèn)')
}
})
app.get('/admin', (req, res) => {
res.send('您已經(jīng)登錄,可以訪問(wèn)')
})
// 監(jiān)聽(tīng)端口
app.listen(3000);
console.log('網(wǎng)站服務(wù)器啟動(dòng)成功');
2.3 定義404頁(yè)面
// 中間件應(yīng)用
const express = require('express')
const app = express()
app.use('/admin', (req, res, next) => {
let isLogin = true;
if (isLogin) {
// 讓請(qǐng)求繼續(xù)向下執(zhí)行
next()
} else {
res.send('您還沒(méi)有登錄,拒絕訪問(wèn)')
}
})
app.get('/admin', (req, res) => {
res.send('您已經(jīng)登錄,可以訪問(wèn)')
})
// 在所有路由的最后面添加一個(gè)404頁(yè)面的中間件,并設(shè)置狀態(tài)碼為404,支持鏈?zhǔn)秸{(diào)用
app.use((req, res, next) => {
res.status(404).send('當(dāng)前頁(yè)面不存在')
})
// 監(jiān)聽(tīng)端口
app.listen(3000);
console.log('網(wǎng)站服務(wù)器啟動(dòng)成功');
3. 錯(cuò)誤處理中間件
在程序執(zhí)行過(guò)程中,不可避免的會(huì)出現(xiàn)一些無(wú)法預(yù)料的錯(cuò)誤,比如讀取文件失敗,數(shù)據(jù)庫(kù)連接失敗,express框架提供了錯(cuò)誤處理中間件來(lái)處理錯(cuò)誤,要知道的是,在執(zhí)行過(guò)程中一旦出現(xiàn)錯(cuò)誤就不能向下執(zhí)行了,如果想要在出錯(cuò)以后繼續(xù)向下執(zhí)行我們就需要在程序中捕獲錯(cuò)誤,加入錯(cuò)誤處理。
錯(cuò)誤處理中間件是一個(gè)統(tǒng)一處理錯(cuò)誤的地方
app.use((err, req, res, next) => {
res.status(500).send('服務(wù)器發(fā)生未知錯(cuò)誤)
})
接下來(lái)我們來(lái)寫(xiě)一個(gè)簡(jiǎn)單的錯(cuò)誤處理:
// 錯(cuò)誤處理中間件
const express = require('express')
const app = express()
app.get('/index', (req, res) => {
// 拋出一個(gè)錯(cuò)誤
// 同步處理代碼
throw new Error('程序發(fā)生了未知錯(cuò)誤')
})
// 設(shè)置錯(cuò)誤處理中間件
app.use((err, req, res, next) => {
res.status(500).send(err.message)
})
// 監(jiān)聽(tīng)端口
app.listen(3000);
console.log('網(wǎng)站服務(wù)器啟動(dòng)成功');
注意:錯(cuò)誤處理中間件只能處理同步代碼拋出的錯(cuò)誤,如果是異步代碼,錯(cuò)誤處理中間件是無(wú)法捕獲到的,這時(shí)候我們需要手動(dòng)去觸發(fā)這個(gè)錯(cuò)誤處理中間件
當(dāng)程序出錯(cuò)時(shí),調(diào)用next方法,并且將錯(cuò)誤信息通過(guò)參數(shù)形式傳遞給next方法就可以觸發(fā)錯(cuò)誤處理中間件
// 錯(cuò)誤處理中間件
const express = require('express')
const fs = require('fs')
const app = express()
app.get('/index', (req, res, next) => {
fs.readFile('/demo.txt', 'utf8', (err, result) => {
if(err != null) {
// 此時(shí)next傳遞了參數(shù),代表要傳遞給錯(cuò)誤處理中間件
next(err)
}
})
})
// 設(shè)置錯(cuò)誤處理中間件
app.use((err, req, res, next) => {
res.status(500).send(err.message)
})
// 監(jiān)聽(tīng)端口
app.listen(3000);
console.log('網(wǎng)站服務(wù)器啟動(dòng)成功');
4. 異步函數(shù)錯(cuò)誤捕獲
在nodejs中,異步API的錯(cuò)誤信息都是通過(guò)回調(diào)函數(shù)獲取的,支持Promise對(duì)象的異步API發(fā)生錯(cuò)誤可以通過(guò)catch方法捕獲,異步函數(shù)執(zhí)行如果發(fā)生錯(cuò)誤要如何捕獲錯(cuò)誤呢?
try catch可以捕獲異步函數(shù)以及其他異步代碼在執(zhí)行過(guò)程中發(fā)生的錯(cuò)誤,但是不能捕獲其他類(lèi)型API發(fā)生的錯(cuò)誤(比如說(shuō)回調(diào)函數(shù)或者Promise對(duì)象)
// 異步函數(shù)錯(cuò)誤的捕獲
const express = require('express')
const fs = require('fs')
const promisify = require('util').promisify;
const readFile = promisify(fs.readFile);
const app = express()
app.get('/index', async (req, res, next) => {
try {
await readFile('./aaa.js')
} catch (error) {
next(error)
}
})
// 設(shè)置錯(cuò)誤處理中間件
app.use((err, req, res, next) => {
res.status(500).send(err.message)
})
// 監(jiān)聽(tīng)端口
app.listen(3000);
console.log('網(wǎng)站服務(wù)器啟動(dòng)成功');
3. Express請(qǐng)求處理
1. 構(gòu)建模塊化路由
雖然我們現(xiàn)在可以用app.get 或者 app.post 方法可以創(chuàng)建一個(gè)路由,但是在真實(shí)開(kāi)發(fā)中路由是非常多的,如果我們把所有的路由信息羅列在一個(gè)文件中,那將是非常可怕的。為了解決這個(gè)問(wèn)題提供了模塊化構(gòu)建方式,可以將不同類(lèi)別的路由放置在不同的模塊中,方便管理
接下來(lái)我們看創(chuàng)建模塊化路由的基礎(chǔ)代碼:
// 構(gòu)建模塊化路由的基礎(chǔ)代碼
const express = require('express')
const app = express()
// 創(chuàng)建路由對(duì)象
const home = express.Router();
// 將路由和請(qǐng)求路徑進(jìn)行匹配
app.use('/home', home);
// 在home下繼續(xù)創(chuàng)建路由(二級(jí)路由)
home.get('/index', (req, res) => {
res.send('歡迎來(lái)到展示頁(yè)面')
})
// 監(jiān)聽(tīng)端口
app.listen(3000);
console.log('網(wǎng)站服務(wù)器啟動(dòng)成功');
參照上面的例子,我們可以把home和admin模塊分別放到單獨(dú)的js中,作為單獨(dú)的模塊,方便我們引用
// home.js
const home = express.Router();
home.get('/index', (req, res) => {
res.send('歡迎來(lái)到首頁(yè)')
})
module.exports = home
// admin.js
const admin = express.Router();
admin.get('/index', (req, res) => {
res.send('歡迎來(lái)到管理頁(yè)面')
})
moudle.exports = admin
// app.js
const home = require('./route/home');
const admin = require('./route/admin');
app.use('/home', home);
app.use('/admin', admin);
2. GET請(qǐng)求參數(shù)的獲取
Express框架中使用req.query即可獲取GET參數(shù),框架內(nèi)部會(huì)將GET參數(shù)轉(zhuǎn)換為對(duì)象并返回
const express = require('express')
const app = express()
app.get('/index', (req, res) => {
// 獲取get請(qǐng)求參數(shù)
res.send(req.query)
})
app.listen(3000)
3. POST請(qǐng)求參數(shù)的獲取
Express中接收post請(qǐng)求參數(shù)需要借助第三方包 body-parser
const express = require('express')
// 目前body-parser已經(jīng)被棄用,直接使用express就可以替代它
const bodyParser = require('body-parser')
const app = express()
// 攔截所有請(qǐng)求,對(duì)請(qǐng)求進(jìn)行處理
// extended: false 方法內(nèi)部使用querystring模塊處理請(qǐng)求參數(shù)的格式
// extended: true 方法內(nèi)部使用第三方模塊qs處理請(qǐng)求參數(shù)的方式
app.use(express.urlencoded({extended: false}))
app.post('/add', (req, res) => {
res.send(req.body)
})
app.listen(3000)
4. Express路由參數(shù)
const express = require('express')
const app = express()
app.get('/index/:id/:name', (req, res) => {
// 獲取get請(qǐng)求參數(shù) params 方法
res.send(req.params)
})
app.listen(3000)
// 訪問(wèn)地址 http://localhost:3000/index/123/zhangsan 返回值為:{ id: 123, name: zhangsan }
5. 靜態(tài)資源處理
通過(guò)Express內(nèi)置的express.static可以方便地托管靜態(tài)文件,例如img\css\js等文件
app.use(express.static('/static/public'));
使用app.use中間價(jià)攔截所有的請(qǐng)求,然后將請(qǐng)求交給express.static這個(gè)方法來(lái)處理,并且將靜態(tài)資源目錄告訴static方法,在方法內(nèi)部會(huì)判斷客戶端發(fā)來(lái)的請(qǐng)求,是否是靜態(tài)資源請(qǐng)求,如果是,方法內(nèi)部直接將靜態(tài)資源響應(yīng)給客戶端,中止當(dāng)前請(qǐng)求。如果發(fā)來(lái)的請(qǐng)求不是靜態(tài)資源請(qǐng)求,方法內(nèi)部會(huì)調(diào)用next方法將請(qǐng)求的控制權(quán)交給下一個(gè)中間件,開(kāi)啟靜態(tài)資源訪問(wèn)后,靜態(tài)資源就可以用這樣的方式訪問(wèn)了:
const express = require('express')
const path = require('path')
const app = express()
// 實(shí)現(xiàn)靜態(tài)資源訪問(wèn)功能
app.use(express.static(path.join(__dirname, 'public')))
// 還可以給文件路徑添加一個(gè)虛擬路徑
app.use('/aaa', express.static(path.join(__dirname, 'public')))
// 這樣我們通過(guò)絕對(duì)路徑 + /aaa/public 也可以訪問(wèn)到靜態(tài)資源文件
app.listen(3000)
6. express-art-template模板引擎
為了使art-template 模板引擎更好的和express框架配合,模板引擎官方在原art-template模板引擎的基礎(chǔ)上封裝了express-art-template
使用npm install art-template express-art-template命令進(jìn)行安裝
const express = require('express')
const path = require('path')
const app = express()
// 告訴express框架使用什么模板引擎渲染什么后綴的模板文件
app.engine('art', require('express-art-template'))
// 告訴express框架,模板存放的位置使什么
// 第一個(gè)參數(shù)views是固定的,不可變的
// 第二個(gè)views是文件夾名字,可變
app.set('views', path.join(__dirname, 'views'))
// 告訴express框架模板的默認(rèn)后綴是什么
app.set('view engine', 'art');
app.get('/index', (req, res) => {
// 在這里我們是用render方法渲染函數(shù),這個(gè)方法是express提供的
// 第一個(gè)參數(shù)是模板的名字,第二個(gè)參數(shù)是一個(gè)對(duì)象,這個(gè)對(duì)象里面的屬性在模板中是可以直接拿到的
// res.render這個(gè)方法內(nèi)部幫我們做了很多事情:
// 1. 拼接了模板路徑
// 2. 拼接了模板后綴
// 3. 哪一個(gè)模板和哪一個(gè)數(shù)據(jù)進(jìn)行拼接
// 4. 將拼接結(jié)果響應(yīng)給了客戶端
res.render('index', {
msg: 'message'
})
})
app.listen(3000)
app.locals對(duì)象
將變量設(shè)置到app.locals對(duì)象下面,這個(gè)數(shù)據(jù)在所有的模板中都可以獲取到
app.locals.users = [{
name: 'zhangsan',
age: 20
},{
name: 'lisi',
age: 22
}]