「本文正在参与技术专题征文Node.js进阶之路,点击查看详情」
01、初识nodejs
Nodejs在VSCode下代码智能提示
npm install --save-dev @types/node
🤔思考:为什么js可以在浏览器中被执行
不同的浏览器使用不同的js解析引擎
- Chrome 浏览器 => v8
- Firefox 浏览器 => OdinMonkey (奥丁猴)
- Safri 浏览器 => JSCore
- IE 浏览器 => Chakra (查克拉)
- etc.... 其中,Chrome 浏览器的V8 解析引擎性能最好!所以我们前端基本使用Chrome进行调试开发
🤔思考:为什么js可以操作DOM和BOM
每个浏览器都内置了DOM、BOM 这样的API函数,因此,在浏览器中的js才可以调用它们。
🤔思考:浏览器中的 js 的运行环境
运行环境是指代码正常运行所需的必要环境(比如种子发芽:需要水、空气、土)
总结:
1、V8引擎负责解析和执行js代码
2、内置API是由运行环境(浏览器)提供的特殊接口,只能在所属的运行环境中被调用
01、Node.js 简介
🤔思考:什么是Node.js
Node.js 是基于Chrome V8引擎的 js运行环境 解析和执行js代码
一段简单的js代码即可以在浏览器中运行,也可以放在node里面执行,放在浏览器里执行就是前端开发,放在node环境下就是后端开发
Node.js 中的 js 运行环境
总结:
1、浏览器是js的前端运行环境
2、Node.js是js的后端运行环境
3、Node.js中无法调用DOM和BOM 等浏览器内置API
🤔思考:Node.js可以做什么
Node.js 作为一个js的运行环境,仅仅提供了基础的功能和API。然而,基于Node.js提供的这些基础功能,很多强大的工作和框架如雨后春笋,层出不穷,所以学会了Node.js,可以让前端程序员胜任更多的工作和岗位
🤔思考:Node.js好学吗???怎么学???
浏览器中的 js学习路径 js基础语法 + 浏览器内置API(DOM + BOM) + 第三方库(jq)
Node.js的学习路径
js基础语法 + Node.js内置API模块(fs、path、http等) + 第三方API模块(express、mysql等)
02、fs文件系统模块
🤔思考:什么是fs文件系统模块?
fs模块是Node.js官方文档提供的、用来操作文件的模块,提供了一系列方法和属性,用来满足用户对文件的操作需求
例如:
- fs.readFile()方法,用来读取指定文件的内容
- fs.writeFile()方法,用来向指定文件写入内容
在js代码中我们,使用fs模块操作文件,需要先引入它
const fs = require('fs');
🤔思考:读取指定文件中的内容?
使用fs.readFile()方法,可以读取指定文件中的内容,方法如下
fs.readFile(path[,options],callback)
其中被中括号包裹的是可选参数,其他的是必选参数
参数解读:
- 参数1:必选参数,字符串,表示文件路径
- 参数2:可选参数,表示以什么编码格式读取文件
- 参数3:必选参数,文件读完后,通过回调函数拿到读取的结果
以utf8的编码格式读取,读取指定文件内容,并打印err和dataStr的值:
const fs = require('fs');
fs.readFile('./files/1.txt', 'utf8', (err, dataStr) => {
console.log(err); // null
console.log('-------');
console.log(dataStr); // hello
});
如果文件读取成功err的值为null, dataStr为文件内容
如果文件读取失败err的值为错误对象, dataStr为undefined
🤔思考:如何向指定文件中写入内容?
使用fs.writeFile()方法,可以向指定文件中写入内容,方法如下:
fs.writeFile(file,data[,options],callback)
参数解读:
- 参数1: file必选参数,需要指定一个文件路径的字符串,表示文件的存放路径
- 参数2: data必选参数,表示要写的内容
- 参数3: options可选参数,表示以什么编码格式写文件,默认utf8
- 参数3: callback必选参数,文件写完后,写入完成后的回调函数
const fs = require('fs');
fs.writeFile('./1.txt', 'world', err => {
console.log(err); // 成功则为null
});
练习:考试成绩整理
使用fs文件系统模块,将素材目录下score.txt文件中的考试数据,整理到score-ok.txt文件中, 整理前,score.txt文件中的数据格式如下:
我们整理后的格式为
核心实现步骤
- 导入fs文件模块
- 使用fs.readFile(),读取score.txt中的内容
- 判断文件是否读取失败
- 文件读取成功后,处理数据
- 将处理好的数据调用fs.writeFile()写入score-ok.txt中
const fs = require('fs');
fs.readFile('./score.txt', 'utf8', (err, dataStr) => {
if (err) {
console.log('文件读取失败');
return
}
// 先把数据按照空格分割
const dataStrArr = dataStr.split(' ');
console.log('dataStrArr: ', dataStrArr); // [ '小红=99', '小黄=100', '小绿=66' ]
// 循环分割后的数组,对每一项数据进行字符串的替换操作
const arrNew = [];
dataStrArr.forEach(item => {
arrNew.push(item.replace('=', ': '));
});
console.log(arrNew); // [ '小红: 99', '小黄: 100', '小绿: 66' ]
// 将新数组的每一项进行字符串合并\r\n代表回车换行
const newStr = arrNew.join('\r\n');
console.log('newStr: ', newStr);
// 将处理好的数据写入心的文件里面
fs.writeFile('./score-ok.txt', newStr, err => {
if (err) {
console.log('写入文件失败!!!');
return
}
console.log('成功了');
});
});
🤔思考:fs模块路径动态拼接的问题?
在使用fs模块操作文件时,如果提供的操作路径是以./或者../开头的相对路径时,很容易出现路径动态拼接错误的问题
原因:代码在运行的时候,会以执行node命令时所处的目录,动态拼接出被操作文件的完整路径。
const fs = require('fs');
// 不要使用相对路径,绝对路径也尽量不要使用(移植性太低)
// 采用__dirname,表示当前文件所处的目录,比较恒定的值
fs.readFile(__dirname + '/1.txt', 'utf8', (err, dataStr) => {
if (err) return console.log('文件读取失败!');
console.log(dataStr);
});
03、path路径模块
path路径模块是Node.js官方提供的、用来处理路径的模块,它提供了一系列的属性和方法,用来满足用户对路径处理的需求
- path.jion()方法,用来将多个路径片段拼接成一个完整的路径字符串
- path.basename()方法,用来从路径字符串中,将文件名解析出来
如果要在js代码中,使用path模块来处理路径,则需要使用如下的方法引入它
🤔思考:路径拼接?
使用path.join()方法,可以实现多个路径片段的拼接,方法如下:
path.join([...paths])
参数解读:
- 参数1: paths string 路径片段的序列
- 返回值: 拼接好的路径
const path = require('path');
const pathStr = path.join('/a', '/b/c', '../', './d', 'e');
console.log('pathStr: ', pathStr); // \a\b\d\e
const pathStr2 = path.join(__dirname, './1.txt');
console.log('pathStr2: ', pathStr2); // 当前文件所处目录
注意:今后凡是涉及到路径拼接的操作,都要使用path.jion(),不要直接使用+进行字符串的拼接 path.jion()方法可以写文件的相对路径.
🤔思考:jion和resolve的区别?
const path = require('path');
let myPath = path.join(__dirname,'/img/so');
let myPath2 = path.join(__dirname,'./img/so');
let myPath3 = path.resolve(__dirname,'/img/so');
let myPath4 = path.resolve(__dirname,'./img/so');
console.log(__dirname); //D:\myProgram\test
console.log(myPath); //D:\myProgram\test\img\so
console.log(myPath2); //D:\myProgram\test\img\so
console.log(myPath3); //D:\img\so<br>
console.log(myPath4); //D:\myProgram\test\img\so
🤔思考:获取路径中的文件名?
使用path.basename()方法,可以实现获取路径中的文件名,方法如下:
const path = require('path');
const fpath = '/a/b/c/index.html';
const fullName = path.basename(fpath);
console.log('fullName: ', fullName); // index.html
// 第二个参数是为了去掉扩展名
const nameWithoutExt = path.basename(fpath, '.html');
console.log('nameWithoutExt: ', nameWithoutExt); // index
🤔思考:获取路径中的文件扩展名?
使用path.extname()方法,可以实现获取路径中的文件扩展名,方法如下:
path.extname(path)
参数解读:
- 参数1: 必选参数 path string 文件路径
- 返回值: 文件扩展名
const path = require('path');
const fpath = '/a/c/index.html';
const fext = path.extname(fpath);
console.log('fext: ', fext); // .html
04、http模块
🤔思考:什么是http模块?
回顾:什么事客户端?什么是服务器?
在网络节点中,负责消耗资源的电脑,叫做客户端,负责提供网络资源的电脑,叫做服务器。
http模块是Node.js官方提供的、用来创建web服务器的模块,通过http模块提供的http.createSever()方法,就能把一台普通的电脑,变成web服务器,从而对外提供web资源服务。
如果想使用http模块创建web服务器,则需要先导入它:
const http = require('http');
在Node.js中,我们不需要Apache等这些第三方web服务器软件,因为我们可以基于Node.js提供的http模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外部提供web服务。
🤔思考:服务相关的概念?
ip地址
ip地址就是互联网上每台计算机的唯一地址,因此ip地址具有唯一性,如果把个人电脑,比做一台电话,那么ip地址就相当于电话号码,只有对方在知道ip地址的情况下,才能与对应的电脑之间进行数据痛信。
ip地址的格式:通常用点分十进制表示成(a.b.c.d)的形式,其中a,b,c,d都是0-255之间的十进制整数,例如:127.0.0.1
域名和域名服务器
尽管ip地址能够唯一的标识网络上的计算机,但是ip地址是一串长数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型的地址方案,即所谓得域名地址。
ip地址和域名是一一对应的关系,这份对应关系存在一种叫做域名服务器的电脑中,使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供ip地址和域名之间的转换服务的服务器。
注意:
- 单纯使用ip地址,互联网中的电脑也能正常工作,有了域名的加持,能让互联网的世界变得更加方便。
- 在开发测试期间,127.0.0.1对应的域名是localhost,它们都代表我们自己的电脑,在使用上没有任何区别
端口号
计算机中的端口号,就好像生活中的门牌号一样,通过门牌号,外卖小哥可以在整栋大楼众多的房间中,准确把外卖送到你的手中。
同理,在一台电脑中,可以运行成百上千个web服务,每个web服务都对应一个端口号,客户端发送过来的网络请求,通过端口号,可以被准确地交给对应web服务进行处理。
注意:
- 一个端口号只能对应一个web服务
- 在实际应用中,url中的80端口号可以被省略
🤔思考:服务相关的概念?
创建web服务器的基本步骤
- 导入http模块
- 创建web服务器实例 调用http.createServer()方法
- 为实例绑定request事件,监听客户端的请求
- 启动服务器
// http模块
const http = require('http');
// 服务器实例对象
const server = http.createServer();
// 使用on()方法,为服务器绑定一个request 事件
server.on('request', (req,res) => {
// 只要有客户端请求。就会触发 request 事件,从而调用这个事件的处理函数
console.log('someone visit our server');
})
// 调用listen()方法
server.listen('80', _ => {
console.log('server is running');
})
🤔思考:req请求对象?
只要服务器接收到了客户端的请求,就会调用通过server.on()为服务器绑定的request事件处理函数。 如果想在事件处理函数中,访问客户端相关的数据或者属性,可以使用如下方式:
server.on('request', (req,res) => {
// 只要有客户端请求。就会触发 request 事件,从而调用这个事件的处理函数
console.log('someone visit our server');
// req是请求对象,它包含了与客户端想逛的数据和属性:
// req.url 是客户端请求的url地址 端口号后面的就是请求地址
// req.method 是客户端的 method请求类型
})
🤔思考:res响应对象?
在服务器的request事件处理函数中,如果想访问与服务器相关的属性和数据,可以使用如下方式:
server.on('request', (req,res) => {
// 只要有客户端请求。就会触发 request 事件,从而调用这个事件的处理函数
console.log('someone visit our server');
// res是响应对象,它包含了与服务器相关的属性和方法
// res.end() 方法的作用
// 想客户端发送指定的内容,并结束这次请求的处理过程
res.end('hi')
})
🤔思考:解决中文乱码问题?
// 防止中文乱码问题,需要设置响应头 Content-Type 的值为 text/html; charset=utf-8
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end('你好')
🤔思考:根据不通的url响应不同的html?
核心步骤:
- 获取请求的url地址
- 设置默认的响应内容为 404 Not Found
- 判断用户请求是否为 / 或者 /index.html首页
- 判断用户请求是否为 /about.html
- 设置Content-Type响应头,防止中文乱码
- 使用res.end()结束响应内容
const http = require('http')
const server = http.createServer();
server.on('request', (req,res) => {
const url = req.url;
let content = '404 Not found';
if (url === '/' || url === '/index.html'){
content = '<h1>首页</h1>';
} else if (url === '/about.html'){
content = '<h1>关于</h1>';
}
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(content);
})
server.listen('90', ()=>{
console.log('server is running');
})
返回一个html文件
const path = require('path');
const fs = require('fs');
const http = require('http');
const server = http.createServer();
server.on('request', (req, res) => {
const url = req.url;
let fpath = '';
if (url === '/') {
fpath = path.join(__dirname, './page/index.html');
} else {
fpath = path.join(__dirname, '/page', url);
}
fs.readFile(fpath, 'utf8', (err, dataStr) => {
if (err) return res.end('404');
res.end(dataStr);
})
})
server.listen(90, ()=>{
console.log('server is running');
})
05、模块化
- 模块化的好处
- 能够知道CommonJs规定了那些内容
- 能够说出Node.js中三大分类各自是什么
- 能够使用npm管理包
- 能够了解什么是规范的包结构
- 能够了解模块的加载机制
🤔思考:什么是模块化?
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分为若干模块的过程。对整个系统来说,模块是可组合、分解和更换的单元。
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并相互依赖的多个小模块。
把代码进行模块化的好处:
- 提高了代码的复用性
- 提高了代码的可维护性
- 可以实现按需加载
模块化规范:
模块化规范就是对代码进行模块化的拆分与组合时,需要遵守哪些规则:
- 使用什么语法格式引入其他模块
- 在模块中使用什么样的语法格式向外暴露成员
模块化规范的的好处:
大家都遵守同样的模块化规范写代码,降低了沟通成本,极大方便了各个模块之间的相互调用,约定大于配置,利人利己。
🤔思考:node.js中的模块分类?
Node.js 中根据模块的来源不同,将模块分为了3大类,分别是:
- 内置模块(如fs模块、http模块等)
- 自定义模块(用户创建的每个js模块,都是自定义模块)
- 第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载)
🤔思考:加载模块?
使用强大的require()方法,可以加载需要的内置模块,用户自定义模块,第三方模块进行使用
🤔思考:模块作用域?
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。
🤔思考:模块作用域的好处?
防止全局变量污染的问题。
在一个自定义模块中,默认情况下,module.exports = {},在外界使用require导入一个自定义模块的时候,得到的成员就是那个模块中,通过module.exports指向的那个对象。
exports对象
由于module.exports单词写起来比较复杂,为了简化向外共享成员的代码,Node提供了exports对象,默认情况下,module.exports和exports指向同一个对象,最终共享的结果还是以module.exports指向的对象为准。
注意
时刻谨记,require()模块时,得到的永远都是module.exports指向的对象
为了防止混乱,建议大家不要在一个模块里同时使用module.exports和exports
🤔思考:Node.js中的模块化规范?
Node.js遵循了CommonJs模块化规范,CommonJs规定了模块的特性和各模块之间如何相互依赖。
CommonJs规定
- 每个模块内部,module变量代表当前模块
- module变量是一个对象,它的exports属性(module.exports)是对外的接口
- 加载某个模块,其实是加载的module.exports
06、NPM与包
🤔思考:什么是包?
Node.js中的第三方模块又叫做包
🤔思考:包的来源?
不同于内置模块和自定义模块,包是由第三方个人或者团队开发的,免费供所有人使用
🤔思考:为什么需要包?
由于Node.js只提供了一些底层的API,导致基于内置模块进行项目开发时,效率很低。包是基于内置模块封装出来的,提供了更高级、更方便的API,提高了开发效率。
🤔思考:初次装包多了哪些文件?
初次装完包后,在项目文件夹下多一个叫做node_modules的文件夹和package-lock.json的配置文件。
其中:
node_modules文件夹用来存放所有已安装到项目中的包。require()导入第三方包时,就是从这个目录中查找并安装包。
package-lock.json 配置文件用来记录node_modules目录下的每一个包的下载信息,例如包的名字,版本号和下载地址等。
注意:程序猿千万不要手动修改node_modules或者package-lock.json文件中的任何代码,npm包管理工具会自动维护它们。
🤔思考:安装指定版本的包?
npm i lodash@4.17.21
🤔思考:包的语义化版本?
包的版本号是以“点分十进制”形式定义的,总共有3位数字,例如2.24.4
其中每一位数字含义如下:
第一位数字:大版本
第二位数字:功能版本
第三位数字:bug修复版本
🤔思考:开发属于自己的包?
初始化包的基本结构
需要新建一个文件夹作为包的根目录,其中包含:
- index.js 入口文件
- package.json 包管理配置
- README.md 说明文档
{
"name": "zy-tools", // 包的名称
"version": "1.0.0", // 版本号
"main": "index.js", // 入口
"description": "提供了格式化时间、htmlEscape相关的功能", // 检索描述
"keywords": ["dataFormat","escape"], // 关键词
"license": "ISC" // 开源许可协议
}
🤔思考:登录npm && 发包?
- nrm list
- nrm use npm
- 切换npm官网服务不要用镜像服务
- 终端输入npm login
- 发包:npm publish
- 删包:npm unpublish 包名 --force
07、Express
🤔思考:什么是Express?
官方:Express是基于node.js平台,快速、开放、极简的web开发框架 通俗理解:Express的作用和node.js内置的http模块类似,是专门用来创建web服务器的。 本质:一个npm上的第三方的包,提供了快速创建web服务器的便捷方法
🤔思考:常见的服务器?
- web网站服务器:专门对外提供web网页资源的服务器
- API接口服务器:专门对外提供api接口的服务器
🤔思考:express的使用?
// 导入express
const express = require('express');
// 创建web服务器 express()返回值是一个实例
const app = express();
// 启动web服务器
app.listen(80, _=>{
console.log('server is running!');
});
// 监听客户端的get请求,并向客户端响应具体的内容
app.get('/user', (req,res)=>{
res.send({name:'zy',age:18});
});
// 监听客户端的post请求,并向客户端响应具体的内容
app.post('/user', (req,res)=>{
res.send('请求成功');
});
// 获取url中携带的查询参数
// 通过req.query对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数
app.get('/', (req,res)=>{
// req.query 默认是一个对象
// 客户端使用?name=zy&age=20 这种字符串形式,发送到服务器的参数
// 可以通过req.query 对象访问到,例如:
// req.query.age req.query.name
console.log(req.query); // { age: '10', name: 'zy' }
});
// express.static()
// express提供了一个非常好用的函数,叫做express.static(),通过它,我们可以非常便捷地 创建一个静态资源服务器,
// 例如:通过下面代码就可以将public目录下的图片、css文件、js文件对外开放访问了
// app.use(express.static('public'));
// 注意:express在指定的静态目录中查找文件,并对外提供资源的访问路径。因此,存放静态文 件的目录不会出现在url中。
// 挂载路径前缀
// app.use('/public', express.static('public'));
// 托管多个静态资源目录,多次调用express.static()函数
// 访问静态资源时,express.static()函数会根据目录的添加顺序查找所需的文件
app.use(express.static('./static'));
08、Express中的路由
🤔思考:什么是路由?
广义:路由就是映射关系
🤔思考:Express中的路由?
在Express中,路由是指客户端的请求与服务器处理函数之间的映射关系。
Express中的路由由三部分组成:请求类型、请求的url地址,处理函数。格式如下:
app.METHOD(PATH,HANDLER)
🤔思考:模块化路由?
为了方便对路由进行模块化管理,Express不建议将路由直接挂在app上,而是推荐将路由抽离为单独的模块。
步骤如下:
- 创建路由模块对应的.js文件
- 调用express.Router()函数创建路由对象
- 向路由对象上挂载具体的路由
- 使用module.exports向外共享路由对象
- 使用app.use()函数注册路由模块
// 导入express
const express = require('express');
// 创建路由对象
const router = express.Router();
// 挂载具体的路由
router.get('/user/list', (req,res)=>{
res.send('success');
});
router.post('/user/add', (req,res)=>{
res.send('add success');
});
// 向外导出路由
module.exports = router;
09、Express中间件
🤔思考:什么是中间件?
中间件:特指业务流程的中间处理环节
现实生活中的例子:
在处理污水的时候,一般要经过3个处理环节,从而保证处理过后的废水,达到排放指标。
🤔思考:中间件的调用流程?
当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。
🤔思考:next函数的作用?
next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。
// 定义一个简单的中间件函数
const mv = (req,res,next)=>{
console.log('这是一个简单的中间函数');
// 把流转关系,转交给下一个中间件或路由
next();
}
🤔思考:全局生效的中间件?
客户端发起的任何请求,到达服务器之后,都会触发中间件,叫做全局生效的中间件。通过调用app.use(中间件函数),即可定义一个全局生效的中间件
app.use(mv)
🤔思考:中间件在实际开发中的作用?
多个中间件之间,共享一份req和res,基于这样的特性,我们可以在上游的中间件中,统一为req和res对象添加自定义属性和方法,供下游的中间件或者路由进行使用。简化代码的书写。
// 中间件的作用
const express = require('express');
const app = express();
// 全局生效的中间件简化写法
app.use((req,res,next)=>{
// 获取到请求到达服务器的时间
const time = new Date();
// 为req对象,挂载自定义属性,从而把时间共享给后面的所有路由
req.startTime = time;
next();
});
app.get('/',(req,res)=>{
// const time = new Date();
res.send('home page' + req.startTime)
})
app.get('/user',(req,res)=>{
// const time = new Date();
res.send('user page'+ req.startTime)
})
app.listen(80,_=>{
console.log('server is running');
})
🤔思考:定义多个全局中间件?
可以使用app.use()连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用。
// 定义多个全局中间件
const express = require('express');
const app = express();
// 定义第一个全局中间件
app.use((req,res,next)=>{
console.log('调用了第一个');
next();
});
// 定义第二个全局中间件
app.use((req,res,next)=>{
console.log('调用了第二个');
next();
});
app.get('/',(req,res)=>{
res.send('home page')
})
app.get('/user',(req,res)=>{
res.send('user page')
})
app.listen(80,_=>{
console.log('server is running');
})
🤔思考:局部生效的中间件?
不使用app.use()定义的中间件,叫做局部生效的中间件
// 局部生效的中间件
const express = require('express');
const app = express();
const mv1 = (req,res,next)=>{
console.log('这是一个局部的中间函数1');
next();
}
const mv2 = (req,res,next)=>{
console.log('这是一个局部的中间函数2');
next();
}
// 中间件只在这个函数里面生效
app.get('/', mv1, mv2, (req,res)=>{
res.send('home page')
})
app.get('/user',(req,res)=>{
res.send('user page')
})
app.listen(80,_=>{
console.log('server is running');
})
🤔思考:使用中间件的注意事项?
- 一定要在路由之前定义中间件
- 客户端发送来的请求,可以连续调用多个中间件进行处理
- 执行完中间件的业务代码之后,不要忘记调用next()函数
- 为了防止代码逻辑混乱,调用next()之后不要再写额外的代码
- 连续调用多个中间件时,多个中间件之间,共享req和res对象
🤔思考:中间件的分类?
- 应用级别的中间件:通过app.use()或app.get()、app.post(),绑定到app实例上的。
- 路由级别的中间件:绑定在express.Router()实例上的中间件
- 错误级别的中间件:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的情况。格式(err,req,res,next)
const express = require('express');
const app = express();
app.get('/', (req,res)=>{
throw new Error('服务器异常')
res.send('home page')
})
// 错误级别中间件要写在路由下面进行捕获
app.use((err,req,res,next)=>{
console.log('发生了错误'+ err.message);
res.send(err.message)
});
app.listen(80,_=>{
console.log('server is running');
})
- 内置的中间件:Express4.16.0版本后,内置了3个常用的中间件,极大的提高了Express项目的开发效率和体验: 1、express.static 快速托管静态资源的内置中间件。
2、express.json 解析JSON格式的请求数据。
// 内置中间件
const express = require('express');
const app = express();
// 解析客户端的json
app.use(express.json());
app.post('/', (req,res)=>{
// 默认情况下,如果不配置解析表单的中间req.bodyundefined
console.log(req.body);
res.send('home page')
})
app.listen(80,_=>{
console.log('server is running');
})
3、express.urlencoded 解析URL-encoded格式的请求数据。
- 第三方的中间件:如body-parser
10、Express编写接口
// 接口编写
const express = require('express');
const app = express();
const router = require('./10');
const bodyParser = require('body-parser');
const cors = require('cors');
// jsonp要在cors前进行注册
app.get('/api/jsonp', (req,res)=>{
// 得到函数的名称
const funcName = req.query.callback
// 定义要发送给客户端的数据对象
const data = {name:'zs',age:18}
// 拼接出一个函数的调用
const scriptStr = `${funcName}(${JSON.stringify(data)})`
// 把拼接的字符串返回给客户端
res.send(scriptStr)
})
app.use(cors);
app.use(bodyParser.urlencoded({extended:false}));
app.use('/api',router)
app.listen(80,_=>{
console.log('server is running');
})
// api 路由模块
const express = require('express');
const apiRouter = express.Router();
apiRouter.get('/',(req,res)=>{
// 获取客户端参数
const query = req.query;
res.send({
status: 0,
msg: '成功',
data: query
});
})
apiRouter.post('/add',(req,res)=>{
// 获取客户端通过请求体,发送到服务器的url-encoded 数据
const body = req.body;
res.send({
status: 0,
msg: '成功',
data: body
});
})
module.exports = apiRouter;
🤔思考:解决跨域?
- 使用cors中间件为主流方案
- JSONP 只能get请求
🤔思考:什么是cors?
CORS跨域资源共享,由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端js代码跨域获取资源。
浏览器的同源策略默认会阻止网页跨域获取资源,但如果服务器配置了CORS相关的HTTP相应头,就可以解除浏览器端的跨域访问限制。
🤔思考:cors的注意事项?
- cors主要在服务端进行配置,客户端无须做任何配置
- cors在浏览器中有兼容性问题,ie10+
🤔思考:jsonp接口?
- 获取客户端发送过来的回调函数的名字
- 得到要通过JSONP形式发送给客户端的数据
- 拼出一个函数调用的字符串
- 响应给客户端的
11、Web开发模式
1、服务端渲染:服务器发送给客户端的HTML页面,服务端通过字符串的拼接,动态生成的。因此不需要Ajax这样的技术进行请求。
服务端渲染优点:
- 前端耗时少
- 有利于seo
服务端渲染缺点:
- 占用大量的服务端资源
- 不利于前后端分离,开发效率低
2、前后端分离:依赖于Ajax这样的技术的广泛应用。后端只需要提供接口,前端进行接口的请求。
前后端分离优点:
- 开发体验好
- 用户体验好
- 减轻服务端渲染压力
前后端分离缺点:
- 不利于seo 可通过ssr进行解决
3、如何选择开发模式
比如企业级网站,主要是展示、交互少、需要良好的seo,那就采用服务渲染
管理后台交互多的,不考虑seo,可以使用前后端分离的模式
首屏服务端渲染、其它页面前后端分离
12、身份认证
通过一定的手段对用户进行身份的验证
- 服务端渲染使用Session认证机制
- 前后端分离使用JWT认证机制
13、Session
HTTP协议的无状态性
客户端每次HTTP请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次HTTP请求的状态。
🤔思考:cookie?
cookie是存储在用户浏览器中的一段不超过4kB的字符串,它是由name,value和其他几个用于控制Cookie有效期、安全性、使用范围的可选属性。
不同域名下的cookie各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的cookie一同发送给服务器。
cookie的特性:
- 自动发送
- 域名独立
- 过期时限
- 4KB限制
cookie在身份认证中的作用:
客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的cookie,客户端会将cookie存在浏览器中。
随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的cookie,通过请求头的形式发送给服务器,服务器即可验证客户端的身份。
cookie不具有安全性:
由于cookie是存储在浏览器中的,而且浏览器提供了读写cookie的API,因此很容易被伪造,不建议将重要的隐私数据,通过cookie发送给服务器。
session的工作原理
session的局限性
session认证需要配合cookie才能实现,由于cookie默认不支持跨域, 当跨域请求的时候,需要很多的额外配置。非跨域是推荐使用