文章目录
01 浏览器中的JavaScript运行环境
-
浏览器中的JavaScript的组成部分
js核心语法
WebAPI:BOM、DOM -
为什么JavaScript可以在浏览器中执行?
浏览器中有JavaScript解析引擎;不同浏览器会有不同JavaScript解析引擎
Chrome
=>V8
Firefox
=>OdinMonkey
safri
=>JSCore
IE
=>Chakra
其中,V8性能最好 -
为什么JavaScript可以操作DOM和BOM?
每个浏览器都内置了DOM和BOM这样的API函数,浏览器可以调用它们。 -
浏览器中的JavaScript运行环境
(1) 运行环境指代码正常运行所需的必要环境,比如Chrome浏览器运行环境:V8解析引擎、内置API函数
(2) V8 解析引擎 负责解析和执行 JavaScript代码
(3) 内置API是由运行环境提供的特殊接口,只能在所属的运行环境中被调用 -
JavaScript能否做后端开发?
能,但是要借助Node.js运行环境
02 什么是Node.js
- 什么是Node.js?
Node.js是一个基于Chrome V8引擎的JavaScript 运行环境 - 浏览器是JavaScript的前端运行环境,Node.js是JavaScript的后端运行环境
- Node.js中无法调用DOM和BOM等浏览器内置API
- Node.js可以做什么?
Node.js作为一个JavaScript的运行环境,仅仅提供了基础的功能和API。基于Node.js提供的这些二基础功能,出现了很多强大的工具和框架,所以这是学习其他框架的基础。
03 安装Node.js
- 官网下载:https://nodejs.org/en/
- LTS和Current两个版本的区别:企业推荐LTS,更稳定;Current为新特性版
- 查看已安装的版本号:在终端输入命令
node -v
,终端是专门为开发人员设计的,用于实现人机交互
04 使用Node运行JS代码
- 打开终端,输入
node 要执行的js文件路径
- 终端命令:
cd 要切换到的目录
- 终端的快捷打开方式,shift
键+鼠标右键
,可以快捷打开当前目录的终端,这样打开的终端是新版本终端Power Shell,不是旧版cmd - 终端常用快捷键:
↑
键,快速定位到上一次执行命令,不用再敲一次命令了tab
键,可以快速补全文件路径esc
键,夸苏清空当前已输入的命令cls
命令,清空终端
05 fs模块 文件操作
05.1 什么是fs模块
- 什么是fs文件模块系统?
fs模块
是Node.js官方提供的、用来操作文件的模块。它提供了一系列的方法和属性,用来满足对文件的操作需求
例如:fs.readFile()
方法,用来读取指定文件的内容,fs.writeFile()
方法,用来向指定文件写入内容等 - 导入fs模块:
const fs = require('fs')
05.2 读取指定文件内容 fs.readFile()
-
语法:
fs.readFile(参数1,参数2,参数3)
参数1:必选,表示文件路径
参数2:可选,表示文件编码格式
参数3:必选,表示文件读取完成胡的回调函数,函数有两个参数,第一个是失败的结果,第二个是成功的结果,如果读取成功或者失败,另一个参数就会是null// 1. 导入模块 const fs = require('fs'); // 2. 读取文件 fs.readFile('./文本文件.txt','utf8',function(err, dataStr){ console.log(err); console.log('--------'); console.log(dataStr); })
-
判断读取成功与否
判断两个参数是否为null,err为null表示成功,否则失败const fs = require('fs'); fs.readFile('./文本文件.txt','utf8',function(err, dataStr){ if(err===null) console.log('读取成功 '+dataStr); else return console.log('读取失败 '+err.message) })
05.3 写入文件内容 fs.writeFile()
- 语法格式:
fs.writeFilse(file, data, [ options], callback)
- 参数1:必选,表示文件存放路径
参数2:必选,表示写入的内容
参数3:写入内容的编码格式
参数4:可选,文件写入完成后的回调函数,只有一个参数err,如果写入失败,err为一个错误对象,如果成功,err为null - 当文本路径正确但是目录下无实际名称文件时,会自动生成新的文件;但是当文件路径错误时,会报错比如C://filess/1.txt,实际cpan并没有filess文件夹,就会报错,如果有filess文件夹,但是没有1.txt,就会生成txt文件。
05.4 fs-整理成绩案例
案例描述:使用fs模块,将素材目录下 成绩.txt 文件中的考试数据整理到 成绩-ok.txt 文件中,并且原文件都在同一行,希望存到目标文件中一个人一行。
// 1.导入fs模块
const fs = require('fs');
// 2.读取文件内容
fs.readFile('D:/学习/前端/练习代码合集/004nodejs/成绩.txt','utf8',function(err,datastr){
// 3.判断是否读取成功
if(err){
return console.log('读取文件失败,'+err.message);
}
console.log('读取文件成功'+datastr);
// 4.处理读取到的数据
const arrOld = datastr.split(' ');
const arrNew = [];
arrOld.forEach(function(item){
arrNew.push(item.replace('=',':'));
})
const newStr = arrNew.join('\r\n') // 要把数组转换成字符串类型
// 5.写入新的文件
fs.writeFile('./成绩-ok.txt',newStr,function(err){
if(err){
console.log('写入失败,'+err.message);
}
else{
console.log('写入成功');
}
})
})
05.5 fs-路径动态拼接问题
- 问题:在使用fs模块操作文件时,如果提供 ./ 或者 …/ 这样开头的目录,很容易出现动态路径拼接错误问题
- 原因:在node执行时,会以执行node命令时所处的目录,动态拼接出被操作的文件的完整路径
- 解决方法1:出现错误是因为用来相对路径,所以我们在使用时应该尽量使用完整的绝对路径,但是这种写法也有问题,移植性差
- 解决方法2:
__dirname
,用它拼接目录,表示当前文件所处的目录fs.readFile(__dirname+'/文本文件.txt','utf8',function(err, dataStr) {} )
,当要执行的文件位置更换时,也不会影响内部代码。
05.6 fs-使用注意事项
- fs的写只能创建新文件,不能创建文件夹
- fs的写每次都是将新内容完全覆盖旧内容
06 path模块 路径操作
06.1 path模块是什么
- path模块是Node.js官方提供的、用来处理路径的模块。提供了一系列方法和属性,用来满足用户对路径处理的需求,例如:
path.join()
方法,用来将多个路径片段拼接成一个完整的路径字符串path.bathname()
方法,用来从路径字符串中,将文件名解析出来
- 导入:
const path = require('path');
06.2 路径拼接 path.join()
- 实现路径拼接,
../
会抵消前一层路径 - 语法:
path.join( path1,path2,path3...)
,返回stringconsole.log(path.join('/app','/a','/b','../','/c')) // 输出:app/a/c console.log(path.join(__dirname,'a'))
- 路径拼接尽量用path,用+号连接容易出错
06.3 获取路径文件名 path.bathname()
- 使用path.bathname(),获取路径最后的一部分,也就是路径中的文件名
- 语法:
path.bathname(fpath, [ext])
,第一个参数必选,表示文件路径字符串,第二个参数可选,表示文件扩展名,如果有,就会从结果中删除这个扩展名;返回stringlet fpath = 'a/b/c/d/index.html' let a = path.basename(fpath); // a = 'index.html' let b = path.basename(fpath,'.html'); // b = 'index'
06.4 获取文件扩展名 path.extname()
- 语法:
path.extname(fpath)
,返回字符串let fpath = 'a/b/c/d/index.html' let a = path.basename(fpath); // a ='.html'
07 fs+path综合案例
功能:将一个html文件(含style和script标签)拆分为三个文件:html、css、js
const fs = require('fs');
const path = require('path');
// 1.创建两个正则表达式匹配style和script标签
const regStyle = /<style>[\s\S]*<\/style>/; // /s /S表示空格和非空格,*表示可以出现任意多次
const regScript = /<script>[\s\S]*<\/script>/;
// 2.读取文件,成功的话就把读到的文本拆解成html、css、js文件
fs.readFile(path.join(__dirname, './时钟案例.html'), 'utf-8', (err, data) => {
if (err) return console.warn("读取文件失败" + err.message);
console.log(data);
// 读取成功,拆解文本
resolveCss(data);
resolveJS(data);
resolveHTML(data);
})
// 3.1 处理css的方法
function resolveCss(htmlStr) {
const cssStr = regStyle.exec(htmlStr); // exec正则表达式的方法,返回匹配的字符串数组
const newCss = cssStr[0].replace('<style>', '').replace('</style>', '');
fs.writeFile(path.join(__dirname, './时钟案例/css.css'), newCss, (err) => {
if (err) return console.warn('写入css失败!' + err.message);
console.log('写入css成功');
})
}
// 3.2 处理js的方法
function resolveJS(htmlStr) {
const jsStr = regScript.exec(htmlStr); // exec正则表达式的方法,返回匹配的字符串数组
const newJS = jsStr[0].replace('<script>', '').replace('</script>', '');
fs.writeFile(path.join(__dirname, './时钟案例/js.js'), newJS, (err) => {
if (err) return console.warn('写入js失败!' + err.message);
console.log('写入js成功');
})
}
// 3.3 处理纯html
function resolveHTML(htmlStr) {
const newHtml = htmlStr.replace(regStyle, '<link rel="stylesheet" href="./css.css"/>')
.replace(regScript, '<script src="./js.js"></script>');
fs.writeFile(path.join(__dirname, './时钟案例/html.html'), newHtml, (err) => {
if (err) return console.warn('写入html失败!' + err.message);
console.log('写入html成功');
})
}
08 http模块
08.1 什么是http模块
- http模块时Node.js官方提供的、用来创建web服务器的模块。通过http模块提供的http.creatServer()方法,就可以把一台普通的电脑变成一台Web服务器,从而对外提供Web资源。
- 导入:
const http = require('http')
- 作用:在Node.js中不需要安装第三方web服务器软件了,它提供的这个模块可以直接造服务器(应该是它内部实现了这些功能)
08.2 服务器相关概念
- IP地址:互联网上每台机器的唯一地址,IP具有唯一性,可以在终端输入
ping 网址
来获得某个网站服务器的IP地址,127.0.0.1
,代表本机IP地址。只有知道对方的IP地址才能实现数据通信。
格式:“点分十进制”=>a.b.c.d
,取值在0~255之间 - 域名和域名服务器:域名服务器就是可以根据域名转换成域名服务器访问,localhost就是本机域名
- 端口号:不同服务的数据交换端口不同,格式
IP:端口号
,只有80端口可以省略
08.3 创建基本web服务器
基本步骤
-
导入http模块
const http = require('http')
-
创建web服务器实例
const server = http.creatServer()
-
为服务器绑定事件,比如request事件,监听客户端的请求
server.on('request',(req, res) =>{ console.log('yes') })
-
启动服务器,实例的listen方法
server.listen(80, ()=>{ 内容 })
,参数为端口号,回调函数
在vscode使用ctrl+c快捷键停止服务器
要注意80端口号一般被其他程序占用了,不要用,使用其他端口号在访问时不能省略端口号// 这里没写响应,浏览器拿不到数据,所以页面会一直转圈圈 const http = require('http'); const server = http.createServer(); server.on('request', function (req, res) { console.log("收到客服端请求"); }) server.listen(8080, function () { console.log('启动服务器 http://localhost:8080'); })
08.4 req请求对象
只要服务器接收到了客服端的请求,绑定了请求函数,那么成功请求后请求函数request回调函数的参数req里面就会存放于客户端相关的数据和属性
// req.url 客户端请求的url地址
console.log(req.url);
// req.method 是客服端请求的方式
console.log(req.method) // 'GET'
08.5 res响应对象
-
在服务器的request中,res放服务器响应的内容,内容会显示在浏览器上;
-
中文会乱码,要设置响应头:
res.setHeader('Content-Type', 'text/html; charset=utf-8')
,意思是响应的内容编码格式为utf-8// 第一个参数为要设置的响应头,后面是内容 res.setHeader('Content-Type','text/html;charset=utf-8');
08.6 根据不同url响应不同html内容
- 获取请求的url地址
- 设置默认的响应内容为 404 Not Found
- 判断请求的是否为首页(
/
或/index.html
),或者请求的是/about.html
,根据请求页面响应不同内容 - 设置utf-8解码格式响应头,避免中文乱码
- 使用res.end()响应内容
const http = require('http');
const server1 = http.createServer();
server1.on('request', (req, res) => {
let content = '<h1>404 Not Found</h1>';
const url = req.url;
if (url === '/' || url === '/index.html') {
content = '<h1>首页</h1>';
}
else if(url === '/about.html'){
content = '<span>这是about页</span>';
}
res.setHeader('Content-type', 'text/html; charset=utf-8');
res.end(content);
})
server1.listen(8080, () => {
console.log('成功开启服务器');
console.log('http://localhost:8080/');
console.log('http://localhost:8080/index.html');
console.log('http://localhost:8080/about.html');
console.log('http://localhost:8080/nothave.html');
})
09 fs+path+http综合实例
根据浏览器访问的网址,回应磁盘中不同文件的内容。
客户端是不能直接访问我们的磁盘的,但是服务器可以,所以自己这个服务器就充当了一个字符串搬运工。
浏览器通过http://localhost:8080/html.html
或http://localhost:8080/
访问
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer();
server.on('request', (req,res) => {
const url = req.url.toString(); // 拿到客户端请求的地址
console.log(url);
// 拼接路径,我这里这个js文件和要展示的网页文件不在同一个文件夹,所以要注意凭借的正确性,
// 而在html文件中,css和js的引入都是__dirname,所以会自动到html所在目录下拼接路径
// 这就体现了__dirname的可移植性
let fpath = '';
if (url === '/') {
fpath = path.join(__dirname, '../clock/html.html');
}
else {
fpath = path.join(__dirname, '../clock', url);
}
console.log(fpath);
fs.readFile(fpath, 'utf-8', (err, data) => { // 读文件
if (err) return res.end('404 Not Found.');
res.end(data);
})
});
server.listen(8080, () => {
console.log('http://localhost:8080/html.html');
})
10 模块化
10.1 模块化的定义
指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程;对整个系统来说,模块是可组合、可分解和可更换的单元。
编程中的模块化就是遵守固定规则,把一个大文件拆成相互依赖的多个小模块。
10.2 Node.js中的模块化
- Node.js中的模块化分为三大类:
- 内置模块:由Node.js官方提供,例如fs、path、http
- 自定义模块:用户创建的.js文件,都是自定义模块
- 第三方模块:不是前两种,第三方开发者开发的模块,使用时要自己下载引入
- 加载模块
使用强大的require(参数)
方法就可以加载三种模块,但是自定义模块的加载参数是路径(可以省略扩展名),另外两种是模块名。
实际上require的原理是在当前位置执行模块代码
10.3 模块作用域
和函数作用域类似,在自定义模块中的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,就叫模块作用域。
防止了全局变量污染的问题。
10.4 向外共享模块作用域中的成员
-
module对象
在每个.js自定义模块中都有一个module对象,在里面存储了和当前模块有关的信息console.log(module); // 打印当前模块内部的信息,默认情况下为空{}
-
在自定义模块中,可以使用
module.exports
对象,将模块内的部分成员共享出去,供外界使用;外界使用require()
方法自定义模块时,得到的就是module.exports
指向的对象。// 模块 let a = 1; // 给module添加属性 module.exports.username = 'Jack'; // 给module添加方法 module.exports.sayHello = function() { console.log('Hello,I am Jack') }
// 使用模块 const m1 = require('./module1.js'); console.log(m1); // {username:'Jack',sayHello:[Function]} console.log(m1.username); // 'Jack' m1.sayHello(); // 'Hello,I am Jack'
-
注意:使用require()导入模块时,导入的结果永远以 module.export指向的对象 为准。
module.exports.name = 'Jack'; module.exports{ username:'Rose' } // 到这里,module.exports里面就没有name属性,只有username属性了
-
exports对象
,和module.exports指向同一个地址,只是简写。 -
使用误区:exports默认和module.exports指向同一个地址,但是当exports指向改变时,module.exports不会变,所以不要拿exports重新指向新对象来覆盖内容,最终还是以module.exports为准。
module.exports.name = 'Jack'; exports = { username = '张三' } // name:'Jack'
示例分析:只是改变了exports的指向,module.exports的指向不变。
一个模块尽量只是用一种,避免出错
10.5 Node.js中的模块化规范
Node.js遵循CommonJS模块化规范,它规定了:
- 每个模块内部,module变量代表当前模块
- module变量是一个对象,它的exports属性是对外接口
- 加载每个模块时,加载的是exports属性,使用require()方法加载模块。
11 npm与包
11.1 什么是包
node.js的第三方模块又叫做包,Node.js中的包都是免费且开源的,都是基于内置模块封装出来的。
11.2 下载
- 国外的nmp,lnc公司旗下 包搜索网址:https://www.npmjs.com/,包下载服务器地址:https://registry.npmjs.org/
- 实际上在安装Node.js的时候就安装了包管理工具,叫
Node Package Manager(简称 npm包管理工具)
,在终端用npm -v
就可以查看版本号;用这个工具就可以下载包了。
11.3 案例-使用第三方包格式化时间
-
在项目中安装格式化时间的包
在终端使用:npm install 包的完整名称
,也可以简写成npm i 包完整名称
npm install moment
-
使用require()导入格式化时间的包
const moment = require('moment');
-
参考moment的官方API文档 对时间进行格式化
const moment = require('moment'); const dt = moment().format('YYYY-MM-DD HH:mm:ss'); console.log(dt);
11.4 npm的使用与注意点
- 首次安装一个包后,会在项目文件下多两个文件,叫做
node_modules
文件夹和package-lock.json
的配置文件。
其中,node_modules文件夹用来存放所有已经安装到项目中的包,package-lock.json用来记录目录下每一个包的下载信息,例如名字、版本号、下载地址等等。不要手动修改他们 - 想要安装指定版本的包,就在包名后面用
@版本号
的格式,比如npm i moment@2.22.2,新的会覆盖旧的
11.5 包管理配置文件——package.json
- npm规定,在项目根目录中,必须提供一个叫做
package.json
的包配置文件。用来记录一些配置信息,例如:项目名称版本号描述、用到的包、包的作用(开发or部署) - 团队之间共享项目时,会剔除包,所以要标明用了哪些包,并且在项目中,要把node_modules文件加添加到
.gitignore
忽略文件中,避免有的包太大,传输太慢,资源浪费。 - 使用
npm init -y
命令,就会在当前项目中创建package.json文件;只能在英文的目录下成功运行,npm install命令安装时,会自动添加包名和版本号到这个包管理文件中,不需要手动维护。 - dependencies结点,存开发和项目上线后都要用到的包,配置文件中的一个对象,记录了install操作,我们可以运行
npm install
或者npm i
,就会按照dependencies结点读取包名称和版本号,下载项目依赖的所有包。 - devDependencies结点,记录只在开发用到的包,而在上线后不会用到。使用
npm install 包名 --save-dev
表示记录到dev节点中,简写为npm i 包名 -D
。
11.6 卸载包 npm uninstall
使用npm uninstall 包名
就可以卸载包,并且修改配置文件中它的信息。
11.7 解决下包速度慢的问题
淘宝在国内搭建了一个服务器,专门把国外的包同步到国内服务器,我们可以到这个服务器下载,速度就提高了。
- 方法1:切换镜像源
npm config get registry
查看当前的下载镜像源npm config set registry=http://registry.npm.taobao.org/
切换下载镜像源为淘宝镜像源- 之后的下载就是在淘宝镜像源了
- 方法2:nrm工具
- 安装nrm工具,
npm install nrm -g
- 查看可用镜像源,
nrm ls
- 切换镜像源,
nrm use taobao
,名称在查看时会给出
- 安装nrm工具,
11.8 包的分类
- 包分为项目包和全局包,
- 项目包:就是之前说的那两种,安装到项目中的包;
- 全局包:安装到npm的安装路径下的包,可用供所有项目使用。
npm install 包名 -g
就是把包安装为全局包,卸载也要加-g;只有工具性质的包才有全局安装的意义,
11.9 i5ting_toc 全局包
一个可用把md文档转为html页面的小工具,使用步骤如下:
- 将 i5ting_toc 安装为全局包,
npm install i5ting_toc -g
- 调用 i5ting_toc ,轻松实现 md 转 html 功能,
i5ting_toc -f 要转换的md文件路径 -o
11.10 开发自己的包 (跳过了)
开发包发布包都跳了
11.15 模块加载机制
- 优先从缓存中找
- 内置模块的加载优先级最高,也就是说如果有第三方模块和官方内置模块同名,require加载的也是官方内置模块
- 如果加载自定义模块没有以 ./ 或者 …/ 开头,就会被当作内置模块和第三方模块加载,当找不到这个模块的时候就会报错。
- 如果自定义模块导入时省略了后缀名,就会按照 确切文件名 → 补全.js → 补全.json → 补全.node → 加载失败这样的顺序找,一直到最后找不到,加载失败。
- 将目录用来加载时,先找目录下的package.json文件,没有的话就加载 index.js文件,都没有的话就加载失败了。
12 Express
12.1 Express概念
- 官方概念:Express是基于nodejs平台,快速、开发、极简的Web开发框架。
- 通俗的理解:Express的作用和nodejs内置http模块类似,专门用来开发Web服务器。
- 本质是一个npm第三方工具包,提供了快速创建Web服务器的便捷方法。
- Express的中文官网:http://www.expressjs.com.cn/。
- 它的开发效率比http高得多。
12.2 Express的基本使用
-
下载这个包
npm i express@4.17.1
-
导入模块
const express = require('express');
-
创建服务器
const app = express();
-
启动服务器
app.listen(8080, () => { console.log('http://127.0.0.1:8080'); })
-
监听GET请求:
app.get('请求URL',(req, res)=>{ /* 处理函数 */ } )
-
监听POST请求:
app.post('请求URL',(req, res)=>{ /* 处理函数 */ } )
-
把内容响应给客户端:
res.send(内容)
,响应内容可以是JSON对象{ a:x,b:y },也可以是文本内容 ‘str’ -
获取URL携带的查询参数:通过
req.query
对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数;
客户端通过**?name1=value1&name2=value2**这种查询字符串的方式,发送到服务器,服务器自动解析到req.query内,
默认情况下是一个空对象,// http://localhost:8080/index1?name=Jack&age=19 console.log(req.query); // { name: 'Jack', age: '19' }
-
获取URL中的动态参数:通过
req.params
对象,可用访问到URL中通过:匹配到的动态参数。// http://localhost:8080/index/value1/value2 // http://localhost:8080/index/1 app.get('/index2/:id', (req, res) => { res.send(req.params); });
const express = require('express');
const app = express();
app.listen('8080', () => {
console.log('成功启动服务器;');
console.log(`可以访问
http://localhost:8080/index1
http://localhost:8080/index2 `);
});
app.get('/index1', (req, res) => {
res.send('返回的一些数据');
console.log(req.query); // http://localhost:8080/index1?name=Jack&age=19 { name: 'Jack', age: '19' }
});
app.get('/index2', (req, res) => {
res.send({ name: 'Jack', age: 18, sex: '男' });
res.send(req.params);
});
12.3 Express静态资源处理
- express.static()
一个Express提供的函数,可以通过它非常便捷地创建一个静态资源服务器,要使用app.use()
方法来配置express.static()
。
这样就向外提供了clock静态资源,但是在访问时URL中不会出现clock,即文件目录名。app.use(express.static('./clock')); //clock为文件夹,目录下有三个子文件 // http://loaclhost:3000/image/1.jpg
- 托管多个静态资源目录:多次调用express.static()就行了,访问时会根据添加顺序查找文件
- 挂载路径前缀:如果想在访问的URL路径前面挂在托管的静态资源的文件名(前缀),加上就行这个字符串参数就行
app.use('/clock', express.static('./clock')); //clock为文件夹,目录下有三个子文件 // http://loaclhost:3000/clock/image/1.jpg
12.4 nodemon
nodemon的作用
在编写Node.js文件的时候,如果修改了代码,需要频繁的重启项目,非常繁琐。现在可以用nodemon自动帮我们重启项目,它能监听项目。
使用
- 安装
npm install -g nodemon
- 使用
第一次运行项目时,使用nodemon命令替换node命令,启动后的项目只要代码发生了更新(保存后),就会自动程序执行项目。nodemon .\01server.js
12.5 express路由
什么是路由
广义上来讲,路由就是映射关系。
express中的路由
在express中,路由就是值客户端与服务器处理函数之间的映射关系。
在express中路由由三部分组成:请求的类型(method)、请求的URL地址(path)、处理函数(handler)。app.method(path,handler)
路由的匹配过程
每当一个请求到达服务器后,需要先经过路由匹配,只有匹配成功之后,才会调用对应的处理函数。在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的URL都匹配成功了,express才会将这次请求转交给对应的function函数进行处理。
路由模块化
为了方便对路由进行模块化管理,express官方不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块。
将路由抽离为单独的模块的步骤如下:
- 创建路由模块对应的 .js 文件
- 调用
express.Router()
函数创建路由对象 - 向路由对象上挂载具体路由
- 使用
module.exports
向外共享路由对象 - 使用
app.use()
函数注册路由模块,这个函数是注册全局中间件// 路由模块 // 引入express模块 const express = require('express'); // 创建路由对象 const router = express.Router(); // 在路由对象上挂载路由 router.get('/router', (req, res) => { res.end('Get express router.') }) // 向外暴露路由对象 module.exports = router;
// 使用 const express = require('express'); const app = express(); // 引入路由模块,创建实例对象 const router = require('./router.js'); // 注册中间件 app.use(router); app.listen(8080, () => { console.log('http://localhost:8080/router') })
为路由模块添加前缀
类似于托管静态资源的添加前缀格式,在app.use()函数中增加前缀字符串参数即可
app.use('/api',router);
12.6 express中间件
概念
中间件就是业务处理的中间环节。
当一个请求到达Express服务器后,可以连续调用多个中间件,从而对这次请求进行预处理。
意思是中间件在路由之前,只有中间件函数执行完成了,才能执行路由函数(对请求的处理)
中间件格式
app.get(req, res, next)
本质就是一个function处理函数,中间件的参数中,必须包含next函数参数,放在req、res之后,而路由处理函数只包含req和res两个参数,以此可区分中间件和路由处理函数
// 定义中间件
const mw = function (req, res, next) {
console.log('最简单的中间件函数');
next();
}
next函数的作用
next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由
在当前中间件业务处理完成之后,必须调用next函数。
全局生效中间件
客户端发起任何请求,到达服务器后,都会触发中间件,叫做全局生效的中间件。
通过调用 app.use
即可定义一个全局生效的中间件。
// 定义中间价
const mw = function (req, res, next) {
console.log('最简单的中间价函数');
next();
}
// 将mw注册为全局生效的中间件
app.use(mw);
定义全局中间件的简化
将定义和全局生效合成一步
// 这里中间件就是一个匿名函数了
app.use(function (req, res, next) {
console.log('最简单的中间价函数');
next();
});
中间件的作用
多个中间件之间是可以共享同一份req和res的,基于这样的特性,我们可以在上游给req或res对象添加自定义方法或属性,供下游中间件和路由使用。
// 使用
const express = require('express');
const app = express();
const mw = function (req, res, next) {
// 获取请求到达服务器的时间
const time = Date.now();
req.startTime = time; //这里
next();
}
app.use(mw);
app.get('/',(req, res)=>{
console.log(req.startTime);
}
app.listen(8080, () => {
console.log('http://localhost:8080/router')
})
这个服务器在 mw 路由中挂载了自定义属性 startTime ,存请求到达服务器的时间,提供给后面的路由使用。
const time = Date.now();
req.startTime = time; //这里
定义多个全局中间件
可以使用app.use()
连续定义多个全局中间件。客户端请求到达服务器之后,会按照use中的定义顺序依次进行调用
app.use(mw1).use(mw2)
局部生效中间件
不使用use添加中间件,而是在路由中引入,这样引入的中间件就只在当前路由中生效。
在中间可以引入任意多个中间件函数,中括号可要可不要。
app.get('/',[mw1,mw2,...], (req, res)=>{
res.send('good');
})
中间件的5个注意事项
- 必须在路由之前注册中间件
- 客户端发过来的请求可以连续调用多个中间件
- 中间件最后必须调用next函数
- 为了防止代码混乱,不要在next()之后再写代码
- 连续调用多个中间件时,共享req和res对象
中间件的分类
为了方便理解和使用,官方把常见中间件分为5大类:
1. 应用级别的中间件
通过app.use()或app.get()或app.post()绑定在app(Express实例)上的。
const express = require('express');
const app = express();
app.use(mw);
2. 路由级别的中间件
绑定到Router()实例上的中间件
const express = require('express');
const app = express();
const router = express.Router();
router.use(function(req, res, next){
next();
})
3. 错误级别的中间件
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:错误级别中间件的处理函数中,必须有4个形参,(err, req, res, next)
在项目实际运行中,一旦发生错误(抛出异常),整个项目就会崩溃停止,这里错误级别中间件就可以捕获这样的错误,对它进行处理。
错误级别的中间件一定要放在所有中间件包括路由之后
4. Express内置的中间件
express内置了3个常用中间件:
express.staic
快速托管静态资源中间件,无兼容性,任何版本express中都可用express.json
解析JSON格式的请求体数据,有兼容性,仅在4.16.0+版本中可用express.urlencoded
解析 URL -encoded 格式的请求体数据,有兼容性,仅支持4.16.0+版本
这两个解析后的数据都是挂载到req.body
上,默认为空对象
app.use(express.json());
app.use(express.urlencoded({ extend:false }))
// 这样配置后,这两个中间体就会根据请求体的数据格式自动解析并且将数据挂载到req.body
5. 第三方的中间件
例如:body-parser
这个第三方中间件,可以用来解析请求体数据,用法和官方内置的类似。
使用步骤如下:
- 运行
npm install body-parser
安装中间件 - require导入中间件
- app.use()使用中间件
parse = require('body-parser');
app.use(parse.urlencoded({ extend:false }))
自定义中间件
实现步骤
手动实现一个类似于express.urlencode这样的中间件,解析POST提交到服务器的表单数据。
- 定义中间件
- 监听req的data
- 监听req的end
- 使用querystring模块解析请求体数据
- 将解析出来的对象挂载到body
- 将自定义中间件封装为模块
12.7 使用express写接口
创建基本服务器
// api路由模块
// 导入express创建路由实例
const express = require(‘express’);
const router = express.Router();
// 挂载对应的路由,也就是接口
// 1.GET接口
router.get(‘/get‘,(req,res)=>{
//通过req.query获取客户端通过查询字符串,发送到服务端的数据
const query = req.query;
// 调用res.send向客户端响应处理的结果
// 传递的数据由前后端协商好
res.send({
status: 0, //0表示处理成功,1表示失败
msg: ‘GET 请求成功’, // 状态的描述
data: query // 需要响应给客户端的数据
})
});
// 2.POST接口
router.post(‘/post’, (req, res)=>{
// 通过req.body获取请求体中包含 url-encoded格式的数据
// 这种格式的数据必须通过配置中间件来解析,在服务器中通过use配置
const body = req.body;
res.send({
status: 0,
msg: ‘POST 请求成功‘,
data: body
})
})
// 暴露路由
module.exports = router;
// 服务器
// 导入express模块
const express = require(‘express’);
// 创建服务器实例
const app = express();
// 添加中间件,包括路由
// 配置数据解析表单数据的中间件
app.use(express.urlencoded({ extended:false }));
// api路由模块引入
const router = require(‘./apiRouter’);
// 把路由模块挂载到服务器上, 加上前缀api
app.use(‘/api’,router);
// 启动服务器
app.listen(8080,()=>{
console.log(‘express server running at http://localhost:8080’);
})
接口跨域问题
协议、域名、端口号 任何一项不同就存在跨域问题。
解决接口跨域问题的方案主要有两种:
- CORS(主流方案,推荐)
- JSONP(有缺陷的方案,只支持GET请求)
使用CORS跨域共享资源
cors是Express的一个第三方中间件,直接安装即可,使用步骤:
- 运行
npm install cors
安装中间件 - 使用
const cors = require('cors')
导入中间件 - 在路由之前
调用app.use()
配置中间件
这样就可以实现跨域了。
1)CORS原理
- 什么是CORS?
CORS是由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源。 - 注意事项
2.1 CORS主要在服务器进行配置,客户端浏览器无须做任何额外的配置,就可以请求开启了CORS的接口
2.2 CORS有兼容性问题。
2)CORS响应头部
- Access-Control-Allow-Origin
相当于设置了res.setHeader(‘Access-Control-Allow-Origin’, ‘*’)
,通配符为*,表示允许来自任何域的请求。 - …-…-…-Header
默认情况下,CORS仅支持客户端向服务器发送9个特定请求头,如果超过了就会失败,那么就需要用这个响应头进行声明,允许某些请求头,否则就会失败。
例如res.setHeader(‘Accss-Control-Allow-Header’, ‘Content-Type, X-Custom-Hader’)
,就是声明允许这两种请求头。 - …-…-…-Methods
默认情况下CORS只允许GET、POST、HEAD请求方式,如果要允许其他请求方式,就要用这个响应头声明。用*号通配符可以设置允许所有请求方式。
3)CORS请求的分类
根据请求头和请求方式的不同分为两大类:简单请求、预检请求
1.简单请求
同时满足两大条件,就是简单请求
- 请求方式:GET、POST、HEAD三者之一
- 请求头部不超过CORS默认的9种
2.预检请求
- 不是简单请求就是预检请求。
- 在浏览器与服务器正式通信前,浏览器会先发送OPTIONS请求进行预检,以获知服务器是否允许实际请求,只有在预检请求成功响应之后才会发送携带真实数据的请求。以下几类请求都需要进行预检请求:
- 请求方式为GET、POST、HEAD之外的请求,比如DELETE请求
- 请求头中包含了自定义字段
- 向服务器发送了application/json格式的数据
3.两种请求的区别
简单请求只会发生一次请求,预检请求会发生两次请求,OPTION之后才会发起真正的请求。
使用JSONP接口
1)JSONP的概念及特点
概念:浏览器通过script标签的src属性,请求服务器上 的数据,同时,服务器返回一个调用。这种请求数据的方式就叫做JSONP。
特点:
- JSONP不属于真正的Ajax请求,因为它没有使用XMLHttpRequest这个对象
- JSONP仅支持GET请求
2)创建JSONP接口
1.注意事项
如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前声明JSONP的接口,否则JSONP接口会被处理成开启了CORS的接口。
// JSONP接口,不会配置CORS
app.get('/api/jsonp',(req,res)=>{})
// CORS中间件
app.use(cors())
// 这个接口会配置CORS
app.get('/api/get',(req,res)=>{})
2.实现
- 获取客户端发送过来的回调函数的名字
- 得到要通过JSONP形式发送给客户端的数据
- 根据前两步得到的数据,拼接出一个函数调用的字符串
- 把上一步拼接得到的字符串响应给客户端script标签进行解析执行
- 在网页中(客户端)使用jQuery发起JSONP请求,调用$.ajax()函数,提供JSONP的配置选项,从而发起JSONP请求
app.get('api/jsonp',(req, res)=>{
// 1.
const funcName = req.query.callback
// 2.
const data = {
name = 'Jack',
age = 16
}
// 3.
const scriptStr = `${funcName}(${JSON.stringify(data)})`
// 4.
res.send(scriptStr)
})