Rest&Ajax
参考:[哔站REST和AJAX李立超]
前言:早些年的前后端没有分离的时候,服务器的结构是基于MVC模式
- M:Model—数据模型
- V:View— 视图用来呈现
- C:Controller — 控制器,赋值加载数据并选择视图来呈现数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o8vJDt3g-1669952844808)(E:\test\笔记\JS\image\MVC.jpg)]
MVC模式的缺点
(1)现在的应用场景,一个应用通常都会有多个端(client)存在:web端 移动app端 PCh5端,如果服务器直接返回html页面,那么服务器就只能为web端提供服务,其他类型的客户端还需要单独开发服务器,这样就提高了开发和维护的成本
(2)降低系统的性能,由于视图不能直接访问数据库需要控制器来帮助因此降低了性能
(3)不适合小型的应用程序,因为花费大量的时间将MVC用于到规模规模不大的程序中得不偿失,而且增加了代码和工作量
(4)由于模型和视图要严格分离,这样给调试增添了困难,而且级联层级的修改是自上而下的如果某块业务只需要新增一个视图,而这个新增就会因为MVC模式思想的限制而去增加业务逻辑层和数据访问层的代码。
如何解决上面的问题呢?
传统的服务器需要坐两件事情,第一个加载数据,第二个要将模型渲染金视图
- 解决方案就是将前后端分离,渲染视图的功能从服务器中剥离出来,服务器只负责向客户端返回数据,渲染视图的工作由客服端自行完成。分离以后,服务器只提供数据,一个服务器可以同时为多种客户端提供服务器,同时将视图渲染的工作交给客户端以后,简化了服务器代码的编写,降低了维护成本
前后端分离之后,需要统一一下前后端通信的风格,这时候就需要REST
REST
REST:REpresentational State Transfer表示层状态的传输
- 其实就是一种服务器的设计风格
- 主要特点,服务器只返回数据,最常用的数据格式是JSON。由于JSON能直接被JavaScript读取,所以,以JSON格式编写的REST风格的API具有简单、易读、易用的特点
REST规范
REST规范定义了资源的通用访问格式,虽然不是强制要求,但遵守规范可以让人易于理解
请求的方法
- GET 加载数据
- POST 新建或添加数据
- PUT 添加或修改数据
- PATCH 修改数据
- DELETE 删除数据
- OPTION 由浏览器自动发送,检查请求的一些权限
API接口
-
获取用户列表
GET /api/user
-
获取用户详情
GET /api/user/:id
-
新建用户
POST /api/user
-
删除用户
DELETE /api/user/:id
。。。。。
跨域处理cors
什么是跨域?
-
如果两个网站的完整的域名不相同就会跨域
a网站:http://haha.com
b网站:http://heihei.com
a访问b网站就会产生跨域
解决跨域处理前要明确一件事情,这个限制是浏览器限制还是服务器限制的跨域
先看一个案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>rest&ajax</title>
</head>
<body>
<button id="btn">获取数据</button>
<script>
const oBtn = document.getElementById('btn')
oBtn.onclick = ()=>{
const xhr = new XMLHttpRequest()
xhr.open('get', 'http://localhost:3000/api/user')
xhr.send()
}
</script>
</body>
</html>
// 获取用户列表
app.get('/api/user', (req, res) => {
console.log('我收到了user请求');
res.send({
code: 200,
data: userLists
})
})
服务端打印出了:我收到了user请求,说明请求已经到达了服务器。所以是浏览器为了数据安全进行的限制,跨域是在保护服务器
,避免服务器的数据被他人随意获取
解决方案
1、JSONP(现在使用比较少)
2、服务器设置允许跨域的头,原理就是告诉浏览器不要保护我,我可以向这个客户端发送数据(推荐使用)
- Access-Control-Allow-Origin:允许跨域的域名,只能设置一个,*表示允许所有网站都可以访问
- Access-Control-Allow-Methods:允许的请求方式
- Access-Control-Allow-Headers:允许传递的请求头
app.use((req, res, next)=>{
// res.setHeader("Access-Control-Allow-Origin", '*')
res.setHeader("Access-Control-Allow-Origin", 'http://127.0.0.1:5501/') // 允许这个路径访问
res.setHeader("Access-Control-Allow-Methods", "GET,POST")
res.setHeader("Access-Control-Allow-Headers", "Content-type")
next()
})
AJAX
前后端分离后,后端只负责把数据传给前端,前端渲染页面。关键前端怎么获取到后端传输的数据呢?这时就需要AJAX了。
-
A 异步 、J JavaScript、A 和、X xml,就是异步的JavaScript和xml,它的作用就是通过js向服务器发送请求来加载数据,其中xml是早期的Ajax使用的数据格式,目前数据都是使用JSON
-
可以选择的方案:
- XMLHTTPRequest(xhr)
- Fetch
- axios
XMLHTTPRequest(xhr,浏览器原生)
-
创建一个新的xhr对象,xhr
const xhr = new XMLHttpRequest()
-
设置请求的信息,与服务端进行连接
xhr.open('get', 'http://localhost:3000/user')
-
发送请求
xhr.send()
-
数据加载完毕
xhr.onload
-
响应状态码
xhr.status
-
读取响应信息
xhr.response
-
响应数据格式(将返回的数据设置为JSON格式)
xhr.responseType = 'json'
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TU00bETU-1669952844810)(E:\test\笔记\JS\image\responseType.png)]
示例:
oBtn.onclick = ()=>{
const xhr = new XMLHttpRequest()
xhr.open('get', 'http://localhost:3000/api/user')
xhr.send()
// 处理返回的数据
// 异步
// 设置返回数据格式
xhr.responseType = 'json'
// 给xhr绑定一个onload事件解决异步问题,等待数据返回成功,获取response
xhr.onload = function () {
// xhr.status // 响应状态码
if (xhr.status === 200) {
// 读取响应数据 xhr.response()
let res = xhr.response
//如果没有设置xhr.responseType = 'json', xhr.response默认返回的数据格式是string
// res = JSON.parse(res)
console.log(res);
if (res.code === 200) {
// 渲染数据操作
}
}
}
}
fetch(浏览器原生)
[fetch mdn][https://developer.mozilla.org/zh-CN/docs/Web/API/fetch]
fetch是xhr的升级版,采用的是Promise API。作用是和xhr一样的,但是使用起来更加友好。fetch原生JS就支持的一种Ajax请求
fetch()与XMLHttpRequest的主要差异
- 1、fetch使用Promise,不适用回调函数,因此写起来更简洁
- 2、fetch采用模块化设计,API分散在多个对象上:Response对象、Request对象、Headers对象,而XMLHttpRequest的API设计不是很好,输入、输出、状态都在同一个接口管理,比较混乱
- 3、fetch通过数据量(Stream对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景很有用。XMLHttpRequest对象不支持数据流,所有的数据必须放在缓存里,不支持分块读取,必须等全部拿到后,再一次性给出来
fetch(url, {method,headers,body,mode}).then(response=>{}).catch
Response对象:处理服务器返回的数据
- body: ReadableStream
- bodyUsed: false,只读属性包含一个布尔值,指示正文是否已被读取
- headers: Headers {},指向一个headers对象,对应HTTP回应的所有标头
- ok: true,返回一个布尔值,表示请求是否成功,true对应HTTP状态码200到299,false对应其他状态码
- redirected: false,属性返回一个布尔值,表示请求是否发生
- status: 200,属性返回一个数字,表示HTTP回应的状态码
- statusText: “OK”,属性返回一个字符串,表示HTTP回应的状态信息,成功就返回OK
- type: “cors”,属性返回请求的类型
- url: “http://localhost:3000/api/user”,属性返回请求的URL
-
Response.body
返回数据格式
ArrayBuffer
ArrayBufferView
(Uint8Array 等)Blob
/File- string
URLSearchParams
FormData
格式化解析
Response.json()使用较多的是将服务器返回的数据转为JSON格式
-
Response.type
返回请求的类型
basic
:普通请求,即同源请求。cors
:跨域请求。error
:网络错误,主要用于 Service Worker。opaque
:如果fetch()
请求的type
属性设为no-cors
,就会返回这个值,详见请求部分。表示发出的是简单的跨域请求,类似<form>
表单的那种跨域请求。opaqueredirect
:如果fetch()
请求的redirect
属性设为manual
,就会返回这个值,详见请求部分。
-
示例
fetch('http://localhost:3000/api/user',{ method: 'get' }).then(res=>{ console.log('res1===', res); return res.json() }).then(res=>{ console.log('res2===', res);
}).catch(e=>{
console.log(“出错了”, e)
})
```js
// 调用fetch发送请求来完成登录
const username = '孙悟空'
const password = '123456'
fetch("http://localhost:3000/api/login", {
method: "POST",
headers: {
"Content-type": "application/json"
},
body: JSON.stringify({ username, password })
}).then(res=>{
return res.json()
}).then(res=>{
localStorage.setItem('testToken', res.data.token)
console.log('res2===', res);
}).catch(e=>{
console.log("出错了", e)
})
// 传token获取用户信息
const token = localStorage.getItem('testToken')
fetch('http://localhost:3000/api/user',{
headers:{
// "Bearer xxxxxx"
"Authorization":`Bearer ${token}`
}
}).then(res=>{
return res.json()
}).then(res=>{
console.log(res);
}).catch(e=>{
console.log("出错了", e)
})
app.get('/api/user', (req, res) => {
const token = req.headers.authorization.split(' ').pop()
try {
const tokenUser = jwt.verify(token, jwtSecrent)
console.log('我收到了user请求', tokenUser);
res.send({
code: 200,
data: userLists
})
} catch (e) {
// 解码错误,token无效
res.send({
code: 501,
data: 'token失效,请重新登录'
})
}
})
注意点:node后端要 解析json格式请求体的中间件,否则请求不到参数
app.use(express.json())
-
取消接口请求
AbortController
AbortController
接口表示一个控制器对象,允许你根据需要中止一个或多个 Web 请求
const controller = new AbortController()
fetch('http://localhost:3000/api/user',{
method: 'get',
signal: controller.signal
}).then(res=>{
console.log('res1===', res);
return res.json()
}).then(res=>{
console.log('res2===', res);
}).catch(res=>{})
// 调用
setTimeout(()=>{
//终止请求
controller.abort()
}, 3000)
- 使用async/await调用fetch
async () => {
// fetch("http://localhost:3000/test").then()...
// 注意:将promise改写为await时,一定要写try-catch,否则出错了捕获不了
try {
const res = await fetch("http://localhost:3000/api/user")
const data = await res.json()
console.log(data)
} catch (e) {
console.log("出错了", e)
}
}
axios
文档:https://axios-http.com/zh/docs/intro
- Axios是什么?
Axios是一个基于promise网络请求库,在node.js和浏览器中都可以运行。在服务端它使用node.js http
模块,而在客户端(浏览器端)则使用XMLHttpRequests
-
特性
- 从浏览器创建XMLHttpRequests
- 从node.js创建http请求
- 支持PromiseAPI
- 拦截请求和响应
- 转换请求和响应数据
- 取消请求
- 自动转换JSON数据
- 客户端支持防御XSRF
-
与fetch区别
- 不用手动配置headers的application/json,axios会自动设置application/json
- 读取结果时,会自动转换数据格式,不用手动调用 res.json()
- 默认只有返回状态码2xx才会进入then,其他都进入catch,不用手动try catch
-
请求配置对象
url:请求地址,必须,这个可以理解为接口路由地址:
/user
baseURL:请求地址前缀,这个是接口的基础路径,url的公用部分:
http://localhost:3000/api
method:请求方法,默认为get
headers:指定请求头,默认{‘Content-type’:‘application/json’}
data:是作为请求体被发送的数据,**仅适用 ‘PUT’, ‘POST’, 'DELETE 和 ‘PATCH’ 请求方法 **
- data: {name: '张三', age: 16}
params:与请求一起发送的 URL 参数,url?name=“”&age=16
timeout:请求的过期时间,如果请求时间超过
timeout
的值,则请求会被中断,在多少时间后过期,不设置就表示一直在等待响应 默认值是0
(永不超时)transformRequest:用来处理请求数据,数组作为参数,数组可以接收多个参数,请求发送时多个函数按照顺序执行。函数执行时会接收两个参数data和headers,可以在函数中对data和headers进行修改
transformRequest: [function (data, headers) { // 对发送的 data 进行任意转换处理 return data; }]
ETE 和 ‘PATCH’ 请求方法 **
- data: {name: '张三', age: 16}
params:与请求一起发送的 URL 参数,url?name=“”&age=16
timeout:请求的过期时间,如果请求时间超过
timeout
的值,则请求会被中断,在多少时间后过期,不设置就表示一直在等待响应 默认值是0
(永不超时)transformRequest:用来处理请求数据,数组作为参数,数组可以接收多个参数,请求发送时多个函数按照顺序执行。函数执行时会接收两个参数data和headers,可以在函数中对data和headers进行修改
transformRequest: [function (data, headers) { // 对发送的 data 进行任意转换处理 return data; }]
- 响应结构