Nodejs知識(shí)點(diǎn)

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;

exportsmodule.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 第三方模塊

  1. 下載

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使用
  1. 庫(kù)文件下載:

npm install gulp

  1. 項(xiàng)目的根目錄下建立gulpfile.js文件,這個(gè)文件名是固定的不能更改

  2. 重構(gòu)項(xiàng)目的文件夾結(jié)構(gòu),src 目錄放置源代碼,dist 目錄放置構(gòu)建后文件

  3. gulpfile.js 中編寫(xiě)任務(wù)

  4. 在命令行工具中執(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
}]
?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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