NodeJS基礎與實戰
Node是什么
Node是一個基於Chrome V8引擎的JavaScript代碼運行環境。
運行環境:
- 瀏覽器(軟件)能夠運行JavaScript代碼,瀏覽器就是JavaScript代碼的運行環境
- Node(軟件)能夠運行JavaScript代碼,Node就是JavaScript代碼的運行環境
Node環境搭建
Node.js運行環境安裝

Node環境安裝失敗解決
1. 錯誤代號2502、2503
失敗原因:系統帳戶權限不足。

解決辦法:
1.以管理員身份運行powershell命令行工具
2.輸入運行安裝包命令 msiexec /package node安裝包位置
2. 執行命令報錯

失敗原因:Node安裝目錄寫入環境變量失敗
解決辦法:將Node安裝目錄添加到環境變量中
PATH環境變量
存儲系統中的目錄,在命令行中執行命令的時候系統會自動去這些目錄中查找命令的位置。

比如:將迅雷的exe文件目錄添加到系統變量path當中,在命令行中就可以用thunder來打開迅雷
Node快速入門
Node.js的組成
-
JavaScript 由三部分組成,ECMAScript,DOM,BOM。
-
Node.js是由ECMAScript及Node環境提供的一些附加API組成的,包括文件、網絡、路徑等等一些更加強大的 API。

Node.js基礎語法
所有ECMAScript語法在Node環境中都可以使用
在Node環境下執行代碼,使用Node命令執行后綴為.js的文件即可

命令行使用小技巧:
- clear 清除命令行
- node執行js的按tab自動補全
- 在工作目錄shift鼠標右鍵打開命令行免去切換到工作目錄
Node.js全局對象global
在瀏覽器中全局對象是window,在Node中全局對象是global。
Node中全局對象下有以下方法,可以在任何地方使用,global可以省略。
- console.log() 在控制台中輸出
- setTimeout() 設置超時定時器
- clearTimeout() 清除超時時定時器
- setInterval() 設置間歇定時器
- clearInterval() 清除間歇定時器
Node的模塊及第三方包
Node.js模塊化開發
JavaScript開發弊端
JavaScript在使用時存在兩大問題,文件依賴和命名沖突。

軟件中的模塊化開發
一個功能就是一個模塊,多個模塊可以組成完整應用,抽離一個模塊不會影響其他功能的運行。

Node.js中模塊化開發規范
- Node.js規定一個JavaScript文件就是一個模塊,模塊內部定義的變量和函數默認情況下在外部無法得到
- 模塊內部可以使用exports對象進行成員導出,使用require方法導入其他模塊。

模塊成員導出

模塊成員的導入

導入模塊時.js后綴可以省略
模塊成員導出的另一種方式

exports是module.exports的別名(地址引用關系),導出對象最終以module.exports為准
模塊導出兩種方式的聯系與區別

導出多個成員時使用:
module.exports = {
name: 'zhangsan',
...
}
導出單個成員時使用:
exports.version = version;
系統模塊
系統模塊概述
Node運行環境提供的API. 因為這些API都是以模塊化的方式進行開發的, 所以我們又稱Node運行環境提供的API為系統模塊

系統模塊fs文件操作

舉例:讀取同目錄下helloworld.js的內容
// 1.通過模塊的名字fs對模塊進行引用
const fs = require('fs');
// 2.通過模塊內部的readFile讀取文件內容
fs.readFile('./01.helloworld.js', 'utf8', (err, doc) => {
console.log(err);
console.log(doc);
});
注意:如果文件讀取正確 err是 null
系統模塊fs文件操作

舉例:向同級目錄下demo.txt寫內容,demo.js不存在則自動創建
const fs = require('fs');
fs.writeFile('./demo.txt', '即將要寫入的內容', err => {
if (err != null) { //或 if(err)
console.log(err);
return;
}
console.log('文件內容寫入成功');
})
系統模塊path路徑操作
為什么要進行路徑拼接:
- 不同操作系統的路徑分隔符不統一
- /public/uploads/avatar
- Windows 上是 \ /
- Linux 上是 /
路徑拼接語法

相對路徑VS絕對路徑
-
大多數情況下使用絕對路徑,因為相對路徑有時候相對的是命令行工具的當前工作目錄
-
在讀取文件或者設置文件路徑時都會選擇絕對路徑
-
使用__dirname獲取當前文件所在的絕對路徑
舉例:讀取絕對路徑下的helloworld.js
const fs = require('fs');
const path = require('path');
console.log(__dirname);
console.log(path.join(__dirname, '01.helloworld.js'))
fs.readFile(path.join(__dirname, '01.helloworld.js'), 'utf8', (err, doc) => {
console.log(err)
console.log(doc)
});
第三方模塊
別人寫好的、具有特定功能的、我們能直接使用的模塊即第三方模塊,由於第三方模塊通常都是由多個文件組成並且被放置在一個文件夾中,所以又名包。
第三方模塊有兩種存在形式:
-
以js文件的形式存在,提供實現項目具體功能的API接口。
-
以命令行工具形式存在,輔助項目開發
獲取第三方模塊:
-
下載:npm install 模塊名稱
-
卸載:npm unintall package 模塊名稱
全局安裝與本地安裝:
-
命令行工具:全局安裝
-
庫文件:本地安裝
nodemon
nodemon是一個命令行工具,用以輔助項目開發。
在Node.js中,每次修改文件都要在命令行工具中重新執行該文件,非常繁瑣。
使用步驟:
1.使用npm install nodemon –g 下載它
2.在命令行工具中用nodemon命令替代node命令執行文件

nrm
nrm ( npm registry manager ):npm下載地址切換工具
npm默認的下載地址在國外,國內下載速度慢
使用步驟:
1.使用npm install nrm –g 下載它
2.查詢可用下載地址列表 nrm ls
3.切換npm下載地址 nrm use 下載地址名稱
Gulp
基於node平台開發的前端構建工具
將機械化操作編寫成任務, 想要執行機械化操作時執行一個命令行命令任務就能自動執行了,用機器代替手工,提高開發效率。
Gulp作用
-
項目上線,HTML、CSS、JS文件壓縮合並
-
語法轉換(es6、less ...)
-
公共文件抽離
-
修改文件瀏覽器自動刷新
Gulp使用
-
使用npm install gulp下載gulp庫文件
-
在項目根目錄下建立gulpfile.js文件
-
重構項目的文件夾結構src目錄放置源代碼文件dist目錄放置構建后文件
-
在gulpfile.js文件中編寫任務
-
在命令行工具中執行gulp任務
注:要在命令行執行 npm install gulp-cli -g (全局安裝gulp命令)
Gulp中提供的方法
- gulp.src():獲取任務要處理的文件
- gulp.dest():輸出文件
- gulp.task():建立gulp任務
- gulp.watch():監控文件的變化
Gulp插件
-
gulp-htmlmin :html文件壓縮 (
npm install gulp-htmlmin) -
gulp-csso :壓縮css(
npm install gulp-csso) -
gulp-babel :JavaScript語法轉化(
npm install gulp-babel @babel/core @babel/preset-env) -
gulp-less: less語法轉化(
npm install gulp-less) -
gulp-uglify :壓縮混淆JavaScript(
npm install --save-dev gulp-uglify) -
gulp-file-include 公共文件包含(
npm install gulp-file-include) -
browsersync 瀏覽器實時同步
gulpfile.js代碼具體實現:
// 引用gulp模塊
const gulp = require('gulp');
const htmlmin = require('gulp-htmlmin');
const fileinclude = require('gulp-file-include');
const less = require('gulp-less');
const csso = require('gulp-csso');
const babel = require('gulp-babel');
const uglify = require('gulp-uglify');
// 使用gulp.task建立任務
// 1.任務的名稱
// 2.任務的回調函數
gulp.task('first', () => {
// 1.使用gulp.src獲取要處理的文件
//相當於復制操作
gulp.src('./src/css/base.css')
.pipe(gulp.dest('dist/css'));
});
// html任務
// 1.html文件中代碼的壓縮操作
// 2.抽取html文件中的公共代碼
gulp.task('htmlmin', () => {
gulp.src('./src/*.html')
.pipe(fileinclude())
// 壓縮html文件中的代碼
.pipe(htmlmin({ collapseWhitespace: true }))
.pipe(gulp.dest('dist'));
});
// css任務
// 1.less語法轉換
// 2.css代碼壓縮
gulp.task('cssmin', () => {
// 選擇css目錄下的所有less文件以及css文件
gulp.src(['./src/css/*.less', './src/css/*.css'])
// 將less語法轉換為css語法
.pipe(less())
// 將css代碼進行壓縮
.pipe(csso())
// 將處理的結果進行輸出
.pipe(gulp.dest('dist/css'))
});
// js任務
// 1.es6代碼轉換
// 2.代碼壓縮
gulp.task('jsmin', () => {
gulp.src('./src/js/*.js')
.pipe(babel({
// 它可以判斷當前代碼的運行環境 將代碼轉換為當前運行環境所支持的代碼
presets: ['@babel/env']
}))
.pipe(uglify())
.pipe(gulp.dest('dist/js'))
});
// 復制文件夾
gulp.task('copy', () => {
gulp.src('./src/images/*')
.pipe(gulp.dest('dist/images'));
gulp.src('./src/lib/*')
.pipe(gulp.dest('dist/lib'))
});
// 構建任務
gulp.task('default', ['htmlmin', 'cssmin', 'jsmin', 'copy']);
在命令行輸入:gulp default 就會依次執行命令
config
作用:允許開發人員將不同運行環境下的應用配置信息抽離到單獨的文件中,模塊內部自動判斷當前應用的運行環境,並讀取對應的配置信息,極大提供應用配置信息的維護成本,避免了當運行環境重復的多次切換時,手動到項目代碼中修改配置信息:
-
使用npm install config命令下載模塊
-
在項目的根目錄下新建config文件夾
-
在config文件夾下面新建default.json、development.json、production.json文件
-
在項目中通過require方法,將模塊進行導入
-
使用模塊內部提供的get方法獲取配置信息
- development.json:
{
"db": {
"user": "itcast",
"host": "localhost",
"port": "27017",
"name": "blog"
}
}
將敏感配置信息存儲在環境變量中:
-
在config文件夾中建立custom-environment-variables.json文件
-
配置項屬性的值填寫系統環境變量的名字
-
項目運行時config模塊查找系統環境變量,並讀取其值作為當前配置項屬於的值

// 引入mongoose第三方模塊
const mongoose = require('mongoose');
// 導入config模塊
const config = require('config');
console.log(config.get('db.host'))
// 連接數據庫
mongoose.connect(`mongodb://${config.get('db.user')}:${config.get('db.pwd')}@${config.get('db.host')}:${config.get('db.port')}/${config.get('db.name')}`, {useNewUrlParser: true })
.then(() => console.log('數據庫連接成功'))
.catch(() => console.log('數據庫連接失敗'))
區分開發環境與生產環境
電腦添加系統變量NODE_ENV,通過process.env獲取來判斷
if (process.env.NODE_ENV == 'development') {
// 開發環境
} else {
// 生產環境
}
bcrypt
bcrypt依賴的其他環境:
-
python 2.x
-
node-gyp
- npm install -g node-gyp
-
windows-build-tools
- npm install --global --production windows-build-tools
// 導入bcrypt模塊
const bcrypt = require('bcrypt');
// 生成隨機字符串 gen => generate 生成 salt 鹽
let salt = await bcrypt.genSalt(10);
// 使用隨機字符串對密碼進行加密
let pass = await bcrypt.hash('明文密碼', salt);
// 密碼比對
let isEqual = await bcrypt.compare('明文密碼', '加密密碼');
Joi
user.js:
// 驗證用戶信息
const validateUser = user => {
// 定義對象的驗證規則
const schema = {
username: Joi.string().min(2).max(12).required().error(new Error('用戶名不符合驗證規則')),
email: Joi.string().email().required().error(new Error('郵箱格式不符合要求')),
password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/).required().error(new Error('密碼格式不符合要求')),
role: Joi.string().valid('normal', 'admin').required().error(new Error('角色值非法')),
state: Joi.number().valid(0, 1).required().error(new Error('狀態值非法'))
};
// 實施驗證
return Joi.validate(user, schema);
}
// 將用戶集合做為模塊成員進行導出
module.exports = {
User,
validateUser
}
// 引入用戶集合的構造函數
const { User, validateUser } = require('../../model/user');
// 引入加密模塊
const bcrypt = require('bcrypt');
module.exports = async (req, res, next) => {
try {
await validateUser(req.body)
}catch (e) {
// 驗證沒有通過
// e.message
// 重定向回用戶添加頁面
// return res.redirect(`/admin/user-edit?message=${e.message}`);
// JSON.stringify() 將對象數據類型轉換為字符串數據類型
return next(JSON.stringify({path: '/admin/user-edit', message: e.message}))
}
// 根據郵箱地址查詢用戶是否存在
let user = await User.findOne({email: req.body.email});
// 如果用戶已經存在 郵箱地址已經被別人占用
if (user) {
// 重定向回用戶添加頁面
// return res.redirect(`/admin/user-edit?message=郵箱地址已經被占用`);
return next(JSON.stringify({path: '/admin/user-edit', message: '郵箱地址已經被占用'}))
}
// 對密碼進行加密處理
// 生成隨機字符串
const salt = await bcrypt.genSalt(10);
// 加密
const password = await bcrypt.hash(req.body.password, salt);
// 替換密碼
req.body.password = password;
// 將用戶信息添加到數據庫中
await User.create(req.body);
// 將頁面重定向到用戶列表頁面
res.redirect('/admin/user');
}
formidable
// 引入formidable模塊
const formidable = require('formidable');
// 創建表單解析對象
const form = new formidable.IncomingForm();
// 設置文件上傳路徑
form.uploadDir = "/my/dir";
// 是否保留表單上傳文件的擴展名
form.keepExtensions = false;
// 對表單進行解析
form.parse(req, (err, fields, files) => {
// fields 存儲普通請求參數
// files 存儲上傳的文件信息
});
package.json
node_modules文件夾的問題:
-
文件夾以及文件過多過碎,當我們將項目整體拷貝給別人的時候,,傳輸速度會很慢很慢.
-
復雜的模塊依賴關系需要被記錄,確保模塊的版本和當前保持一致,否則會導致當前項目運行報錯
package.json文件的作用
項目描述文件,記錄了當前項目信息,例如項目名稱、版本、作者、github地址、當前項目依賴了哪些第三方模塊等。
使用npm init -y命令生成。
項目依賴:
-
在項目的開發階段和線上運營階段,都需要依賴的第三方包,稱為項目依賴
-
使用npm install 包名命令下載的文件會默認被添加到 package.json 文件的 dependencies 字段中

開發依賴:
- 在項目的開發階段需要依賴,線上運營階段不需要依賴的第三方包,稱為開發依賴
- 使用npm install 包名 --save-dev命令將包添加到package.json文件的devDependencies字段中

package-lock.json文件的作用:
- 鎖定包的版本,確保再次下載時不會因為包版本不同而產生問題
- 加快下載速度,因為該文件中已經記錄了項目所依賴第三方包的樹狀結構和包的下載地址,重新安裝時只需下載即可,不需要做額外的工作
總結:
-
下載全部依賴:npm install
-
下載項目依賴:npm install --production
Node.js中模塊的加載機制
模塊查找規則-當模塊擁有路徑但沒有后綴時:

-
require方法根據模塊路徑查找模塊,如果是完整路徑,直接引入模塊。
-
如果模塊后綴省略,先找同名JS文件再找同名JS文件夾
-
如果找到了同名文件夾,找文件夾中的index.js
-
如果文件夾中沒有index.js就會去當前文件夾中的package.json文件中查找main選項中的入口文件
-
如果找指定的入口文件不存在或者沒有指定入口文件就會報錯,模塊沒有被找到
模塊查找規則-當模塊沒有路徑且沒有后綴時:

-
Node.js會假設它是系統模塊
-
Node.js會去node_modules文件夾中
-
首先看是否有該名字的JS文件
-
再看是否有該名字的文件夾
-
如果是文件夾看里面是否有index.js
-
如果沒有就會去當前文件夾中的package.json文件中查找main選項中的入口文件
-
如果找指定的入口文件不存在或者沒有指定入口文件就會報錯,模塊沒有被找到
創建web服務器

HTTP協議
報文
在HTTP請求和響應的過程中傳遞的數據塊就叫報文,包括要傳送的數據和一些附加信息,並且要遵守規定好的格式。

請求報文

響應報文

還有方法設置響應頭
response.setHeader('Content-Type', 'text/html');
當使用 response.setHeader()設置響應頭時,它們將與傳給 response.writeHead()的任何響應頭合並,其中 response.writeHead()的響應頭優先。
http請求與響應處理
Get請求參數處理

代碼:
// 用於創建網站服務器的模塊
const http = require('http')
const url = require('url')
// app對象就是網站服務器對象
const app = http.createServer()
// 當客戶端有請求來的時候
app.on('request', (req, res) => {
// 獲取請求報文信息
// req.headers
// console.log(req.headers['accept']);
res.writeHead(200, {
'content-type': 'text/html;charset=utf8'
})
console.log(req.url)
// 1) 要解析的url地址
// 2) 將查詢參數解析成對象形式
let { query, pathname } = url.parse(req.url, true)
console.log(query.name)
console.log(query.age)
if (pathname == '/index' || pathname == '/') {
res.end('<h2>歡迎來到首頁</h2>')
} else if (pathname == '/list') {
res.end('welcome to listpage')
} else {
res.end('not found')
}
if (req.method == 'POST') {
res.end('post')
} else if (req.method == 'GET') {
res.end('get')
}
})
// 監聽端口
app.listen(3000)
console.log('網站服務器啟動成功')
POST請求參數

// 用於創建網站服務器的模塊
const http = require('http');
// app對象就是網站服務器對象
const app = http.createServer();
// 處理請求參數模塊
const querystring = require('querystring');
// 當客戶端有請求來的時候
app.on('request', (req, res) => {
// post參數是通過事件的方式接受的
// data 當請求參數傳遞的時候出發data事件
// end 當參數傳遞完成的時候出發end事件
let postParams = '';
req.on('data', params => {
postParams += params; //字符串鍵值對形式
});
req.on('end', () => {
console.log(querystring.parse(postParams));
});
res.end('ok');
});
// 監聽端口
app.listen(3000);
console.log('網站服務器啟動成功');
路由
路由是指客戶端請求地址與服務器端程序代碼的對應關系。簡單的說,就是請求什么響應什么。


靜態資源與動態資源
-
服務器端不需要處理,可以直接響應給客戶端的資源就是靜態資源,例如CSS、JavaScript、image文件。
-
相同的請求地址不同的響應資源,這種資源就是動態資源。
客戶端請求方式:

靜態訪問:
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs')
const mime = require('mime') //需npm install mime
const app = http.createServer()
app.on('request', (req, res) => {
// 獲取用戶的請求路徑
let { pathname } = url.parse(req.url)
pathname = pathname == '/' ? '/default.html' : pathname
// 將用戶的請求路徑轉換為實際的服務器硬盤路徑
let realPath = path.join(__dirname, 'public' + pathname)
let type = mime.getType(realPath)
// 讀取文件
fs.readFile(realPath, (error, result) => {
// 如果文件讀取失敗
if (error) {
res.writeHead(404, {
'content-type': 'text/html;charset=utf8'
})
res.end('文件讀取失敗')
return
}
res.writeHead(200, {
'content-type': type
})
res.end(result)
})
})
app.listen(3000)
console.log('服務器啟動成功')
Node.js異步編程
使用回調函數獲取異步API執行結果

Node.js中的異步API

回調地獄
var fs = require('fs')
fs.readFile('./data/a.txt', 'utf8', function(err, data) {
if (err) {
// return console.log('讀取失敗')
// 拋出異常
// 1. 阻止程序的執行
// 2. 把錯誤消息打印到控制台
throw err
}
console.log(data)
fs.readFile('./data/b.txt', 'utf8', function(err, data) {
if (err) {
throw err
}
console.log(data)
fs.readFile('./data/c.txt', 'utf8', function(err, data) {
if (err) {
throw err
}
console.log(data)
})
})
})
Promise
Promise-api簡單使用
var fs = require('fs')
var p1 = new Promise(function (resolve, reject) {
fs.readFile('./data/a.txt', 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
var p2 = new Promise(function (resolve, reject) {
fs.readFile('./data/b.txt', 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
var p3 = new Promise(function (resolve, reject) {
fs.readFile('./data/c.txt', 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
p1
.then(function (data) {
console.log(data)
// 當 p1 讀取成功的時候
// 當前函數中 return 的結果就可以在后面的 then 中 function 接收到
// 當你 return 123 后面就接收到 123
// return 'hello' 后面就接收到 'hello'
// 沒有 return 后面收到的就是 undefined
// 上面那些 return 的數據沒什么卵用
// 真正有用的是:我們可以 return 一個 Promise 對象
// 當 return 一個 Promise 對象的時候,后續的 then 中的 方法的第一個參數會作為 p2 的 resolve
//
return p2
}, function (err) {
console.log('讀取文件失敗了', err)
})
.then(function (data) {
console.log(data)
return p3
})
.then(function (data) {
console.log(data)
console.log('end')
})
promise封裝
var fs = require('fs')
function pReadFile(filePath) {
return new Promise(function (resolve, reject) {
fs.readFile(filePath, 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
pReadFile('./data/a.txt')
.then(function (data) {
console.log(data)
return pReadFile('./data/b.txt')
})
.then(function (data) {
console.log(data)
return pReadFile('./data/c.txt')
})
.then(function (data) {
console.log(data)
})
注意:捕獲錯誤
promise.then((result) => {
console.log(result);
})
.catch((err)=> {
console.log(err);
})
//或
promise.then((result) => {
console.log(result);
},(err)=>{
console.log(err);
})
promise應用場景
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<form action="00-js中的一等公民函數.js" id="user_form">
</form>
<script type="text/html" id="tpl">
<div>
<label for="">用戶名</label>
<input type="text" value="{{ user.username }}">
</div>
<div>
<label for="">年齡</label>
<input type="text" value="{{ user.age }}">
</div>
<div>
<label for="">職業</label>
<select name="" id="">
{{ each jobs }} {{ if user.job === $value.id }}
<option value="{{ $value.id }}" selected>{{ $value.name }}</option>
{{ else }}
<option value="{{ $value.id }}">{{ $value.name }}</option>
{{ /if }} {{ /each }}
</select>
</div>
</script>
<script src="node_modules/art-template/lib/template-web.js"></script>
<script src="node_modules/jquery/dist/jquery.js"></script>
<script>
// 用戶表
// 其中一個接口獲取用戶數據
// 職業:2
// 職業信息表
// 其中一個接口獲取所有的職業信息
//封裝的ajax方法回調方式
get('http://127.0.0.1:3000/users/4', function (userData) {
get('http://127.0.0.1:3000/jobs', function (jobsData) {
var htmlStr = template('tpl', {
user: JSON.parse(userData),
jobs: JSON.parse(jobsData)
})
console.log(htmlStr)
document.querySelector('#user_form').innerHTML = htmlStr
})
})
//jquery中的ajax方法支持promise寫法
var data = {}
$.get('http://127.0.0.1:3000/users/4')
.then(function (user) {
data.user = user
return $.get('http://127.0.0.1:3000/jobs')
})
.then(function (jobs) {
data.jobs = jobs
var htmlStr = template('tpl', data)
document.querySelector('#user_form').innerHTML = htmlStr
})
//封裝的ajax支持promise
var data = {}
pGet('http://127.0.0.1:3000/users/4')
.then(function (user) {
data.user = user
return pGet('http://127.0.0.1:3000/jobs')
})
.then(function (jobs) {
data.jobs = jobs
var htmlStr = template('tpl', data)
document.querySelector('#user_form').innerHTML = htmlStr
})
//封裝的ajax支持promise
function pGet(url, callback) {
return new Promise(function (resolve, reject) {
var oReq = new XMLHttpRequest()
// 當請求加載成功之后要調用指定的函數
oReq.onload = function () {
// 我現在需要得到這里的 oReq.responseText
callback && callback(JSON.parse(oReq.responseText))
resolve(JSON.parse(oReq.responseText))
}
oReq.onerror = function (err) {
reject(err)
}
oReq.open("get", url, true)
oReq.send()
})
}
// 這個 get 是 callback 方式的 API
// 可以使用 Promise 來解決這個問題
function get(url, callback) {
var oReq = new XMLHttpRequest()
oReq.onload = function () {
callback(oReq.responseText)
}
oReq.open("get", url, true)
oReq.send()
}
</script>
</body>
</html>
異步函數async
異步函數是異步編程語法的終極解決方案,它可以讓我們將異步代碼寫成同步的形式,讓代碼不再有回調函數嵌套,使代碼變得清晰明了。
async關鍵字

await關鍵字
-
await關鍵字只能出現在異步函數中
-
await promise await后面只能寫promise對象 寫其他類型的API是不不可以的
-
await關鍵字可是暫停異步函數向下執行 直到promise返回結果
const fs = require('fs');
// 改造現有異步函數api 讓其返回promise對象 從而支持異步函數語法
const { promisify } = require('util')
// 調用promisify方法改造現有異步API 讓其返回promise對象
const readFile = promisify(fs.readFile);
async function run () {
let r1 = await readFile('./1.txt', 'utf8')
let r2 = await readFile('./2.txt', 'utf8')
let r3 = await readFile('./3.txt', 'utf8')
console.log(r1)
console.log(r2)
console.log(r3)
}
run();
mongoDB
環境搭建
-
使用Node.js操作MongoDB數據庫需要依賴Node.js第三方包mongoose
-
項目目錄下使用npm install mongoose命令下載
但首先我們得啟動數據庫服務,否則MongoDB將無法連接。默認是開啟的。使用命令行工具:net start mongoDB
數據庫連接與創建
簡單使用:
// 引入mongoose第三方模塊 用來操作數據庫
const mongoose = require('mongoose')
// 數據庫連接
mongoose
.connect('mongodb://localhost/playground', {
useUnifiedTopology: true,
useNewUrlParser: true
})
// 連接成功
.then(() => console.log('數據庫連接成功'))
// 連接失敗
.catch(err => console.log(err, '數據庫連接失敗'))
// 創建集合規則
const courseSchema = new mongoose.Schema({
name: String,
author: String,
isPublished: Boolean
})
const Course = mongoose.model('Course', courseSchema) // 創建構造函數
Course.create({
name: 'Javascript123',
author: '黑馬講師',
isPublished: false
}).then(result => {
console.log(result)//返回插入文檔的數據 對象格式
})
//或使用:
const course = new Course({
name: 'node.js基礎',
author: '黑馬講師',
isPublished: true
})
// 將文檔插入到數據庫中
course.save()
mongoose驗證
創建集合規則時,可以設置當前字段的驗證規則,驗證失敗就則輸入插入失敗。
-
required: true 必傳字段
-
minlength:3 字符串最小長度
-
maxlength: 20 字符串最大長度
-
min: 2 數值最小為2
-
max: 100 數值最大為100
-
enum: ['html', 'css', 'javascript', 'node.js']
-
trim: true 去除字符串兩邊的空格
-
validate: 自定義驗證器
-
default: 默認值
-
message 自定義錯誤信息
獲取錯誤信息:error.errors['字段名稱'].message
使用:
const postSchema = new mongoose.Schema({
title: {
type: String,
// 必選字段
required: [true, '請傳入文章標題'],
// 字符串的最小長度
minlength: [2, '文章長度不能小於2'],
// 字符串的最大長度
maxlength: [5, '文章長度最大不能超過5'],
// 去除字符串兩邊的空格
trim: true
},
age: {
type: Number,
// 數字的最小范圍
min: 18,
// 數字的最大范圍
max: 100
},
publishDate: {
type: Date,
// 默認值
default: Date.now
},
category: {
type: String,
// 枚舉 列舉出當前字段可以擁有的值
enum: {
values: ['html', 'css', 'javascript', 'node.js'],
message: '分類名稱要在一定的范圍內才可以'
}
},
author: {
type: String,
validate: {
validator: v => {
// 返回布爾值
// true 驗證成功
// false 驗證失敗
// v 要驗證的值
return v && v.length > 4
},
// 自定義錯誤信息
message: '傳入的值不符合驗證規則'
}
}
})
const Post = mongoose.model('Post', postSchema)
Post.create({ title: 'aa', age: 60, category: 'java', author: 'bd' })
.then(result => console.log(result))
.catch(error => {
// 獲取錯誤信息對象
const err = error.errors
// 循環錯誤信息對象
for (var attr in err) {
// 將每個字段的錯誤信息並打印到控制台中
console.log(err[attr]['message'])
}
})
增刪改查
增
方式一:
- 創建schema(表的規則)使用new mongoose.Schema
- 創建集合(構造函數) mongoose.model('表名',schema)
- 使用Course.create({...})或用new Couese({...}) 最后調用save()方法
const courseSchema = new mongoose.Schema({...})
const Course = mongoose.model('Course', courseSchema) // courses
Course.create({
name: 'Javascript123',
author: '黑馬講師',
isPublished: false
}).then(result => {
console.log(result)//返回插入文檔的數據 對象格式
}).catch(err=>{
console.log(err)
})
//或使用
const course = new Course({
name: 'node.js基礎',
author: '黑馬講師',
isPublished: true
})
// 將文檔插入到數據庫中
course.save()
方式二:導入現有數據表
mongoimport –d 數據庫名稱 –c 集合名稱 –file 要導入的數據文件(相對於命令行工具路徑)
注:先找到mongodb數據庫的安裝目錄,將安裝目錄下的bin目錄放置在環境變量中才能使用mongoimport 命令
查
//查詢用戶集合中的所有文檔
User.find().then(result => console.log(result));//數組
//通過_id字段查找文檔(條件查詢)
User.find({_id: '5c09f267aeb04b22f8460968'}).then(result => console.log(result)) //數組
//findOne方法返回一條文檔 默認返回當前集合中的第一條文檔
User.findOne({name: '李四'}).then(result => console.log(result)) //對象
//查詢用戶集合中年齡字段大於20並且小於40的文檔
User.find({age: {$gt: 20, $lt: 40}}).then(result => console.log(result))
//查詢用戶集合中hobbies字段值包含足球的文檔
User.find({hobbies: {$in: ['足球']}}).then(result => console.log(result))
//選擇要查詢的字段
User.find().select('name email -_id').then(result => console.log(result))
//根據年齡字段進行升序排列
User.find().sort('age').then(result => console.log(result))
//根據年齡字段進行降序排列
User.find().sort('-age').then(result => console.log(result))
// 查詢文檔跳過前兩條結果 限制顯示3條結果
User.find()
.skip(2)
.limit(3)
.then(result => console.log(result))
更新


updateMany第一個傳空對象表示全部數據
result返回:

刪
// 查找到一條文檔並且刪除
// 返回刪除的文檔
// 如何查詢條件匹配了多個文檔 那么將會刪除第一個匹配的文檔
User.findOneAndDelete({_id: '5c09f267aeb04b22f8460968'}).then(result => console.log(result))
// 刪除多條文檔
User.deleteMany({}).then(result => console.log(result))
集合關聯
通常不同集合的數據之間是有關系的,例如文章信息和用戶信息存儲在不同集合中,但文章是某個用戶發表的,要查詢文章的所有信息包括發表用戶,就需要用到集合關聯。
-
使用id對集合進行關聯
-
使用populate方法進行關聯集合查詢

代碼示例:
// 用戶集合規則
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
}
})
// 文章集合規則
const postSchema = new mongoose.Schema({
title: {
type: String
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
})
// 用戶集合
const User = mongoose.model('User', userSchema)
// 文章集合
const Post = mongoose.model('Post', postSchema)
// 創建用戶
User.create({name: 'itheima'}).then(result => console.log(result));
// 創建文章
Post.create({titile: '123', author: '5c0caae2c4e4081c28439791'}).then(result => console.log(result));
Post.find()
.populate('author')
.then(result => console.log(result))
mongoDB數據庫添加賬號
-
以系統管理員的方式運行powershell
-
連接數據庫 mongo
-
查看數據庫 show dbs
-
切換到admin數據庫 use admin
-
創建超級管理員賬戶 db.createUser()
-
切換到blog數據 use blog
-
創建普通賬號 db.createUser()
-
卸載mongodb服務
- 停止服務 net stop mongodb
- mongod --remove
-
創建mongodb服務
- mongod --logpath="C:\Program Files\MongoDB\Server\4.1\log\mongod.log" (mongodb安裝目錄)--dbpath="C:\ProgramFiles\MongoDB\Server\4.1\data" --install –-auth
-
啟動mongodb服務 net start mongodb
-
在項目中使用賬號連接數據庫
- mongoose.connect('mongodb://user:pass@localhost:port/database')
Node中使用模板引擎
首先:在命令行工具中使用 npm install art-template 命令進行下載

條件判斷:

子模版
使用子模板可以將網站公共區塊(頭部、底部)抽離到單獨的文件中。


代碼示例:
04.js
const template = require('art-template');
const path = require('path');
const views = path.join(__dirname, 'views', '04.art');
const html = template(views, {
msg: '我是首頁'
});
console.log(html);
view文件夾下的04.art
{{ include './common/header.art' }}
//<% include('./common/header.art') %>
<div> {{ msg }} </div>
{{ include './common/footer.art' }}
//<% include('./common/footer.art') %>
view文件夾下common文件夾寫的header,footer
我是頭部
我是底部
模板繼承
使用模板繼承可以將網站HTML骨架抽離到單獨的文件中,其他頁面模板可以繼承骨架文件。
模板繼承示例

模板繼承示例

代碼示例:

05.js
const template = require('art-template');
const path = require('path');
const views = path.join(__dirname, 'views', '05.art');
const html = template(views, {
msg: '首頁模板'
});
console.log(html);
views文件夾下05.art
{{extend './common/layout.art'}}
{{block 'content'}}
<p>{{ msg }}</p>
{{/block}}
{{block 'link'}}
<link rel="stylesheet" type="text/css" href="./main.css">
{{/block}}
common文件夾下的layout.art
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
{{block 'link'}} {{/block}}
</head>
<body>
{{block 'content'}} {{/block}}
</body>
</html>
模板配置
1.向模板中導入變量 template.defaults.imports.變量名 = 變量值;
2.設置模板根目錄 template.defaults.root = 模板目錄
3.設置模板默認后綴 template.defaults.extname = '.art'
導入變量dateformat格式化時間
首先:項目文件下 npm install dateformat
代碼示例:
06.js
const template = require('art-template');
const path = require('path');
const dateFormat = require('dateformat');
// 設置模板的根目錄
template.defaults.root = path.join(__dirname, 'views');
// 導入模板變量
template.defaults.imports.dateFormat = dateFormat;
// 配置模板的默認后綴
template.defaults.extname = '.html'; //設置了的話 模板引擎渲染的時候可省略文件后綴
const html = template('06.art', {
time: new Date()
});
console.log(template('07', {}));
console.log(html);
views文件夾下的06.art
{{ dateFormat(time, 'yyyy-mm-dd')}}
案列
案例目錄:

第三方模塊router:
-
獲取路由對象
-
調用路由對象提供的方法創建路由
-
啟用路由,使路由生效

第三方模塊serve-static:
-
引入serve-static模塊獲取創建靜態資源服務功能的方法
-
調用方法創建靜態資源服務並指定靜態資源服務目錄
-
啟用靜態資源服務功能

入口函數app.js
// 引入http模塊
const http = require('http');
// 引入模板引擎
const template = require('art-template');
// 引入path模塊
const path = require('path');
// 引入靜態資源訪問模塊
const serveStatic = require('serve-static');
// 引入處理日期的第三方模塊
const dateformat = require('dateformat');
const router = require('./route/index');
// 實現靜態資源訪問服務
const serve = serveStatic(path.join(__dirname, 'public'))
// 配置模板的根目錄
template.defaults.root = path.join(__dirname, 'views');
// 處理日期格式的方法
template.defaults.imports.dateformat = dateformat;
// 數據庫連接
require('./model/connect');
// 創建網站服務器
const app = http.createServer();
// 當客戶端訪問服務器端的時候
app.on('request', (req, res) => {
// 啟用路由功能
router(req, res, () => {})
// 啟用靜態資源訪問服務功能
serve(req, res, () => {})
});
// 端口監聽
app.listen(80);
console.log('服務器啟動成功');
數據庫連接: model文件夾下connect.js
const mongoose = require('mongoose');
// 連接數據庫
mongoose.connect('mongodb://localhost/playground', { useNewUrlParser: true })
.then(() => console.log('數據庫連接成功'))
.catch(() => console.log('數據庫連接失敗'))
數據庫創建表: model文件夾下user.js
const mongoose = require('mongoose');
// 創建學生集合規則
const studentsSchema = new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 2,
maxlength: 10
},
age: {
type: Number,
min: 10,
max: 25
},
sex: {
type: String
},
email: String,
hobbies: [ String ],
collage: String,
enterDate: {
type: Date,
default: Date.now
}
});
// 創建學生信息集合
const Student = mongoose.model('Student', studentsSchema);
// 將學生信息集合進行導出
module.exports = Student;
路徑處理:route文件夾下index.js
// 引入router模塊
const getRouter = require('router');
// 獲取路由對象
const router = getRouter();
// 學生信息集合
const Student = require('../model/user');
// 引入模板引擎
const template = require('art-template');
// 引入querystring模塊
const querystring = require('querystring');
// 呈遞學生檔案信息頁面
router.get('/add', (req, res) => {
let html = template('index.art', {});
res.end(html);
})
// 呈遞學生檔案信息列表頁面
router.get('/list', async (req, res) =>{
// 查詢學生信息
let students = await Student.find();
console.log(students);
let html = template('list.art', {
students: students
})
res.end(html)
})
// 實現學生信息添加功能路由
router.post('/add', (req, res) => {
// 接收post請求參數
let formData = '';
req.on('data', param => {
formData += param;
});
req.on('end', async () => {
await Student.create(querystring.parse(formData))
res.writeHead(301, {
Location: '/list'
});
res.end()
})
});
module.exports = router;
views文件夾下index.art
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>學生檔案</title>
<link rel="stylesheet" href="./css/main.css">
</head>
<body>
<form action="/add" method="post">
<fieldset>
<legend>學生檔案</legend>
<label>
姓名: <input class="normal" type="text" autofocus placeholder="請輸入姓名" name="name">
</label>
<label>
年齡: <input class="normal" type="text" placeholder="請輸入年齡" name="age">
</label>
<label>
性別:
<input type="radio" value="0" name="sex"> 男
<input type="radio" value="1" name="sex"> 女
</label>
<label>
郵箱地址: <input class="normal" type="text" placeholder="請輸入郵箱地址" name="email">
</label>
<label>
愛好:
<input type="checkbox" value="敲代碼" name="hobbies"> 敲代碼
<input type="checkbox" value="打籃球" name="hobbies"> 打籃球
<input type="checkbox" value="睡覺" name="hobbies"> 睡覺
</label>
<label>
所屬學院:
<select class="normal" name="collage">
<option value="前端與移動開發">前端與移動開發</option>
<option value="PHP">PHP</option>
<option value="JAVA">JAVA</option>
<option value="Android">Android</option>
<option value="IOS">IOS</option>
<option value="UI設計">UI設計</option>
<option value="C++">C++</option>
</select>
</label>
<label>
入學日期: <input type="date" class="normal" name="enterDate">
</label>
<label class="btn">
<input type="submit" value="提交" class="normal">
</label>
</fieldset>
</form>
</body>
</html>
views文件夾下list.art
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>學員信息</title>
<link rel="stylesheet" href="./css/list.css">
</head>
<body>
<table>
<caption>學員信息</caption>
<tr>
<th>姓名</th>
<th>年齡</th>
<th>性別</th>
<th>郵箱地址</th>
<th>愛好</th>
<th>所屬學院</th>
<th>入學時間</th>
</tr>
{{each students}}
<tr>
<th>{{$value.name}}</th>
<th>{{$value.age}}</th>
<th>{{$value.sex == '0' ? '男' : '女'}}</th>
<th>{{$value.email}}</th>
<th>
{{each $value.hobbies}}
<span>{{$value}}</span>
{{/each}}
</th>
<th>{{$value.collage}}</th>
<th>{{dateformat($value.enterDate, 'yyyy-mm-dd')}}</th>
</tr>
{{/each}}
</table>
</body>
</html>
Express框架
概述
Express是一個基於Node平台的web應用開發框架,它提供了一系列的強大特性,幫助你創建各種Web應用。我們可以使用 npm install express 命令進行下載。
特性:
-
提供了方便簡潔的路由定義方式
-
對獲取HTTP請求參數進行了簡化處理
-
對模板引擎支持程度高,方便渲染動態HTML頁面
-
提供了中間件機制有效控制HTTP請求
-
擁有大量第三方中間件對功能進行擴展
路由處理
原生node與express路由處理對比:

請求參數處理
原生node與express請求參數處理對比:

初體驗
// 引入express框架
const express = require('express');
// 創建網站服務器
const app = express();
// send()
// 1. send方法內部會檢測響應內容的類型
// 2. send方法會自動設置http狀態碼
// 3. send方法會幫我們自動設置響應的內容類型及編碼
app.get('/' , (req, res) => {
res.send('Hello. Express');
})
//可直接發送對象
app.get('/list', (req, res) => {
res.send({name: '張三', age: 20})
})
app.listen(3000);
console.log('網站服務器啟動成功');
中間件
中間件就是一堆方法,可以接收客戶端發來的請求、可以對請求做出響應,也可以將請求繼續交給下一個中間件繼續處理。

概述
中間件主要由兩部分構成,中間件方法以及請求處理函數。
中間件方法由Express提供,負責攔截請求,請求處理函數由開發人員提供,負責處理請求。

可以針對同一個請求設置多個中間件,對同一個請求進行多次處理。默認情況下,請求從上到下依次匹配中間件,一旦匹配成功,終止匹配。可以調用next方法將請求的控制權交給下一個中間件,直到遇到結束請求的中間件。

app.use

應用
- 路由保護,客戶端在訪問需要登錄的頁面時,可以先使用中間件判斷用戶登錄狀態,用戶如果未登錄,則攔截請求,直接響應,禁止用戶進入需要登錄的頁面。
代碼示例:
app.js中:
// 導入express-session模塊
const session = require('express-session');
// 攔截請求 判斷用戶登錄狀態
app.use('/admin', require('./middleware/loginGuard'));
loginGuard.js中:
const guard = (req, res, next) => {
// 判斷用戶訪問的是否是登錄頁面
// 判斷用戶的登錄狀態
// 如果用戶是登錄的 將請求放行
// 如果用戶不是登錄的 將請求重定向到登錄頁面
if (req.url != '/login' && !req.session.username) {
res.redirect('/admin/login');
} else {
// 如果用戶是登錄狀態 並且是一個普通用戶
if (req.session.role == 'normal') {
// 讓它跳轉到博客首頁 阻止程序向下執行
return res.redirect('/home/')
}
// 用戶是登錄狀態 將請求放行
next();
}
}
module.exports = guard;
- 網站維護公告,在所有路由的最上面定義接收所有請求的中間件,直接為客戶端做出響應,網站正在維護中。
- 自定義404頁面
錯誤處理中間件

捕獲錯誤

處理請求
模塊化路由
作用:模塊化路由,方便處理二級路由

GET參數的獲取
Express框架中使用req.query即可獲取GET參數,框架內部會將GET參數轉換為對象並返回。

POST參數的獲取
Express中接收post請求參數需要借助第三方包 body-parser。
// 引入express框架
const express = require('express');
const bodyParser = require('body-parser');
// 創建網站服務器
const app = express();
// 攔截所有請求
// extended: false 方法內部使用querystring模塊處理請求參數的格式
// extended: true 方法內部使用第三方模塊qs處理請求參數的格式
app.use(bodyParser.urlencoded({extended: false}))
app.post('/add', (req, res) => {
// 接收post請求參數
res.send(req.body)
})
// 端口監聽
app.listen(3000);
app.use方法
app.use(fn ({a: 2}))
function fn (obj) {
return function (req, res, next) {
if (obj.a == 1) {
console.log(req.url)
}else {
console.log(req.method)
}
next()
}
}
app.get('/', (req, res) => {
// 接收post請求參數
res.send('ok')
})
Express路由參數

靜態資源的處理
通過Express內置的express.static可以方便地托管靜態文件,例如img、CSS、JavaScript 文件等。
const express = require('express');
const path = require('path');
const app = express();
// 實現靜態資源訪問功能
app.use(express.static(path.join(__dirname, 'public')))
// 端口監聽
app.listen(3000);
express-art-template
-
為了使art-template模板引擎能夠更好的和Express框架配合,模板引擎官方在原art-template模板引擎的基礎上封裝了express-art-template。
-
使用npm install art-template express-art-template命令進行安裝。
app.locals 對象
const express = require('express');
const path = require('path');
const app = express();
// 模板配置
app.engine('art', require('express-art-template'))
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'art');
app.locals.users = [{
name: 'zhangsan',
age: 20
},{
name: '李四',
age: 30
}]
app.get('/index', (req, res) => {
res.render('index', {
msg: '首頁'
})
});
app.get('/list', (req, res) => {
res.render('list', {
msg: '列表頁'
});
})
// 端口監聽
app.listen(3000);
index.art就可以拿到公共數據users

express-session
const session = require('express-session');
app.use(session({ secret: 'secret key' }));
用戶登陸成功后將信息存儲再req.session中實現登錄攔截

express-formidable
const express = require('express');
const formidableMiddleware = require('express-formidable');
app.use(formidableMiddleware({
// 文件上傳目錄
uploadDir: path.join(__dirname, 'public', 'uploads'),
// 最大上傳文件為2M
maxFileSize: 2 * 1024 * 1024,
// 保留文件擴展名
keepExtensions: true
}));
