一. 前端数据请求方式
1. 前后端分离的优势
- 早期的网页都是通过后端渲染来完成的:服务器端渲染(
SSR,server side render
)- 客户端发出请求 => 服务端接收请求并返回相应
HTML
文档 => 页面刷新,客户端加载新的HTML
文档
- 客户端发出请求 => 服务端接收请求并返回相应
- 服务器端渲染的缺点:
- 当用户点击页面中的某个按钮向服务器发送请求时,页面本质上只是一些数据发生了变化
- 而此时服务器却要将重绘的整个页面再返回给浏览器加载,这显然有悖于程序员的"
DRY( Don‘t repeat yourself )
"原则 - 而且明明只是一些数据的变化却迫使服务器要返回整个
HTML
文档,这本身也会给网络带宽带来不必要的开销
- 有没有办法在页面数据变动时,只向服务器请求新的数据,并且在阻止页面刷新的情况下,动态的替换页面中展示的数据呢?
- 答案正是
AJAX
- 答案正是
AJAX
是Asynchronous JavaScript And XML
的缩写(异步的JavaScript
和XML
)- 是一种实现无页面刷新获取服务器数据的技术
AJAX
最吸引人的就是它的"异步"特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面
- 你可以使用
AJAX
最主要的两个特性做下列事:- 在不重新加载页面的情况下发送请求给服务器
- 接受并使用从服务器发来的数据
2. 网页的渲染过程 – 服务器端渲染

3. 网页的渲染过程 – 前后端分离

二. HTTP 协议的解析
1. 什么是 http?
什么是
HTTP
呢?我们来看一下维基百科的解释:- 超文本传输协议(英语:
HyperText Transfer Protocol
,缩写:HTTP
) - 是一种用于分布式、协作式和超媒体信息系统的应用层协议
HTTP
是万维网的数据通信的基础,设计HTTP
最初的目的是为了提供一种发布和接收HTML
页面的方法- 通过
HTTP
或者HTTPS
协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI
)来标识
- 超文本传输协议(英语:
HTTP
是一个客户端(用户)和服务端(网站)之间请求和响应的标准- 通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个
HTTP
请求到服务器上指定端口(默认端口为80
)- 我们称这个客户端为用户代理程序(
user agent
)
- 我们称这个客户端为用户代理程序(
- 响应的服务器上存储着一些资源,比如
HTML
文件和图像- 我们称这个响应服务器为源服务器(
origin server
)
- 我们称这个响应服务器为源服务器(
- 通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个
2. 网页中资源的获取
我们网页中的资源通常是被放在
Web
资源服务器中,由浏览器自动发送HTTP
请求来获取、解析、展示的目前我们页面中很多数据是动态展示的:
- 比如页面中的数据展示、搜索数据、表单验证等等,也是通过在
js
中发送HTTP
请求获取的
- 比如页面中的数据展示、搜索数据、表单验证等等,也是通过在
3. http 的组成
一次
HTTP
请求主要包括:请求(Request
)和响应(Response
)
4. http 的版本
HTTP/0.9
- 发布于1991年
- 只支持
GET
请求方法获取文本数据,当时主要是为了获取HTML
页面内容
HTTP/1.0
- 发布于1996年
- 支持
POST
、HEAD
等请求方法,支持请求头、响应头等,支持更多种数据类型(不再局限于文本数据) - 但是浏览器的每次请求都需要与服务器建立一个
TCP
连接,请求处理完成后立即断开TCP
连接,每次建立连接增加了性能损耗
HTTP/1.1
(目前使用最广泛的版本)- 发布于1997年
- 增加了
PUT
、DELETE
等请求方法 - 采用持久连接 (
Connection: keep-alive
),多个请求可以共用同一个TCP
连接
- 2015年,
HTTP/2.0
- 2018年,
HTTP/3.0
5. http 的请求方式
- 在
RFC
中定义了一组请求方式,来表示要对给定资源执行的操作:GET
:GET
方法请求一个指定资源的表示形式,使用GET
的请求应该只被用于获取数据HEAD
:HEAD
方法请求一个与GET
请求的响应相同的响应,但没有响应体- 比如在准备下载一个文件前,先获取文件的大小,再决定是否进行下载
POST
:POST
用于将实体提交到指定的资源PUT
:PUT
方法用请求有效载荷(payload
)替换目标资源的所有当前表示DELETE
:DELETE
方法删除指定的资源PATCH
:PATCH
方法用于对资源应部分修改CONNECT
:CONNECT
方法建立一个到目标资源标识的服务器的隧道,通常用在代理服务器,网页开发很少用到TRACE
:TRACE
方法沿着到目标资源的路径执行一个消息环回测试
- 在开发中使用最多的是
GET
、POST
请求- 在后续的后台管理项目中,我们也会使用
PATCH
、DELETE
请求
- 在后续的后台管理项目中,我们也会使用
6. http Request Header
在
request
对象的header
中也包含很多有用的信息,客户端会默认传递过来一些信息:content-type
是这次请求携带的数据的类型:application/x-www-form-urlencoded
:表示数据被编码成以&
分隔的键值对,同时以=
分隔键和值application/json
:表示是一个json
类型text/plain
:表示是文本类型application/xml
:表示是xml
类型multipart/form-data
:表示是上传文件
content-length
:文件的大小长度keep-alive
:http
是基于TCP
协议的,但是通常在进行一次请求和响应结束后会立刻中断- 在
http1.0
中,如果想要继续保持连接:- 浏览器需要在请求头中添加
connection
:keep-alive
- 服务器需要在响应头中添加
connection
:keey-alive
- 当客户端再次放请求时,就会使用同一个连接,直接一方中断连接
- 浏览器需要在请求头中添加
- 在
http1.1
中,所有连接默认是connection
:keep-alive
的- 不同的
Web
服务器会有不同的保持keep-alive
的时间 Node
中默认是5s
- 不同的
accept-encoding
:告知服务器,客户端支持的文件压缩格式,比如js
文件可以使用gzip
编码,对应.gz
文件accept
:告知服务器,客户端可接受文件的格式类型user-agent
:客户端相关的信息
7. http Response 响应状态码
Http
状态码(Http Status Code
)是用来表示Http
响应状态的数字代码:Http
状态码非常多,可以根据不同的情况,给客户端返回不同的状态码MDN
响应码解析地址:https://developer.mozilla.org/zh-CN/docs/web/http/status
常见 HTTP 状态码 状态描述 信息说明 200 OK 客户端请求成功 201 Created POST请求,创建新的资源 301 Moved Permanently 请求资源的URL已经修改,响应中会给出新的URL 400 Bad Request 客户端的错误,服务器无法或不进行处理 401 Unauthorized 未授权的错误,必须携带请求的身份信息 403 Forbidden 客户端没有权限访问,被拒接 404 Not Found 服务器找不到请求的资源 500 Internal Server Error 服务器遇到了不知道如何处理的情况 503 Service Unavailable 服务器不可用,可能处于维护或重载状态,暂时无法访问
8. http Request Header
响应的
header
中包括一些服务器给客户端的信息:
9. Chrome 安装插件 - FeHelper
为了之后查看数据更加的便捷、优雅,我们安装一个
Chrome
插件:- 方式一:可以直接通过
Chrome
的扩展商店安装 - 方式二:手动安装
- 方式一:可以直接通过
三. XHR 的基本用法
1. ajax 发送请求
AJAX
是异步的JavaScript
和XML
(Asynchronous JavaScript And XML
)- 它可以使用
JSON
,XML
,HTML
和text
文本等格式发送和接收数据
- 它可以使用
如何来完成
AJAX
请求呢?- 第一步:创建网络请求的
AJAX
对象(使用XMLHttpRequest
) - 第二步:监听
XMLHttpRequest
对象状态的变化,或者监听onload
事件(请求完成时触发) - 第三步:配置网络请求(通过
open
方法) - 第四步:调用
send
方法发送网络请求
js// 1.创建XMLHttpRequest对象 const xhr = new XMLHttpRequest() // 2.监听状态的改变(宏任务) xhr.onreadystatechange = function() { // console.log(xhr.response) if (xhr.readyState !== XMLHttpRequest.DONE) return // 将字符串转成JSON对象(js对象) const resJSON = JSON.parse(xhr.response) const banners = resJSON.data.banner.list console.log(banners) } // 3.配置请求open // method: 请求的方式(get/post/delete/put/patch...) // url: 请求的地址 xhr.open("get", "http://123.207.32.32:8000/home/multidata") // 4.发送请求(浏览器帮助发送对应请求) xhr.send()
- 第一步:创建网络请求的
2. XMLHttpRequest 的 state(状态)
事实上,我们在一次网络请求中看到状态发生了很多次变化,这是因为对于一次请求来说包括如下的状态:
值 状态 描述 0 UNSENT 代理被创建,但尚未调用 open() 方法 1 OPENED open() 方法已被调用 2 HEADERS_RECEIVED send() 方法已被调用,且头部和状态已经获得 3 LOADING 下载站,responseText 属性已经包含部分数据 4 DONE 下载操作已完成 注意:这个状态并非是
HTTP
的响应状态,而是记录的XMLHttpRequest
对象的状态变化http
响应状态通过status
获取
js// 1.创建XMLHttpRequest对象 const xhr = new XMLHttpRequest() // 2.监听状态的改变(宏任务) // 监听四种状态 xhr.onreadystatechange = function() { // 1.如果状态不是DONE状态, 直接返回 console.log(XMLHttpRequest.DONE) // 4 if (xhr.readyState !== XMLHttpRequest.DONE) return // 2.确定拿到了数据 console.log(xhr.response) } // 3.配置请求open xhr.open("get", "http://123.207.32.32:8000/home/multidata") // 4.发送请求(浏览器帮助发送对应请求) xhr.send()
发送同步请求:
- 将
open
的第三个参数设置为false
,默认为true
- 后续的代码将等到该请求完成之后才能执行
js// 1.创建XMLHttpRequest对象 const xhr = new XMLHttpRequest() // 2.监听状态的改变(宏任务) // 监听四种状态 // xhr.onreadystatechange = function() { // // 1.如果状态不是DONE状态, 直接返回 // if (xhr.readyState !== XMLHttpRequest.DONE) return // // 2.确定拿到了数据 // console.log(xhr.response) // } // 3.配置请求open // async: false // 实际开发中要使用异步请求, 异步请求不会阻塞js代码继续执行 xhr.open("get", "http://123.207.32.32:8000/home/multidata", false) // 4.发送请求(浏览器帮助发送对应请求) xhr.send() // 5.同步必须等到有结果后, 才会继续执行 console.log(xhr.response) console.log("------")
- 将
3. XMLHttpRequest 其他事件监听
除了
onreadystatechange
还有其他的事件可以监听loadstart
:请求开始progress
:一个响应数据包到达,此时整个response body
都在response
中abort
:调用xhr.abort()
取消了请求error
:发生连接错误,例如,域错误。不会发生诸如404
这类的HTTP
错误load
:请求成功完成(即资源请求下载完成)timeout
:由于请求超时而取消了该请求(仅发生在设置了timeout
的情况下)loadend
:在load
,error
,timeout
或abort
之后触发
我们也可以使用
load
来获取数据:jsconst xhr = new XMLHttpRequest() // onload监听数据加载完成 xhr.onload = function() { console.log("onload") } xhr.open("get", "http://123.207.32.32:8000/home/multidata") xhr.send()
4. 响应数据和响应类型
发送了请求后,我们需要获取对应的结果:
response
属性XMLHttpRequest response
属性返回响应的正文内容- 返回的类型取决于
responseType
的属性设置
通过
responseType
可以设置获取数据的类型- 如果将
responseType
的值设置为空字符串,则会使用text
作为默认值
- 如果将
和
responseText
、responseXML
的区别:- 早期通常服务器返回的数据是普通的文本和
XML
,所以我们通常会通过responseText
、responseXML
来获取响应结 - 之后将它们转化成
js
对象形式 - 目前服务器基本返回的都是
json
数据,直接设置为json
即可
jsconst xhr = new XMLHttpRequest() // 2.onload监听数据加载完成 xhr.onload = function() { // const resJSON = JSON.parse(xhr.response) console.log(xhr.response) // console.log(xhr.responseText) // console.log(xhr.responseXML) } // 3.告知xhr获取到的数据的类型 xhr.responseType = "json" // xhr.responseType = "xml" // 4.配置网络请求 // 4.1.json类型的接口 xhr.open("get", "http://123.207.32.32:8000/home/multidata") // 4.2.json类型的接口 // xhr.open("get", "http://123.207.32.32:1888/01_basic/hello_json") // 4.3.text类型的接口 // xhr.open("get", "http://123.207.32.32:1888/01_basic/hello_text") // 4.4.xml类型的接口 // xhr.open("get", "http://123.207.32.32:1888/01_basic/hello_xml") // 5.发送网络请求 xhr.send()
- 早期通常服务器返回的数据是普通的文本和
5. http响应的状态 status
XMLHttpRequest
的state
是用于记录xhr
对象本身的状态变化,并非针对于HTTP
的网络请求状态如果我们希望获取
HTTP
响应的网络状态,可以通过status
和statusText
来获取:常见 HTTP 状态码 状态描述 信息说明 200 OK 客户端请求成功 201 Created POST请求,创建新的资源 301 Moved Permanently 请求资源的URL已经修改,响应中会给出新的URL 400 Bad Request 客户端的错误,服务器无法或不进行处理 401 Unauthorized 未授权的错误,必须携带请求的身份信息 403 Forbidden 客户端没有权限访问,被拒接 404 Not Found 服务器找不到请求的资源 500 Internal Server Error 服务器遇到了不知道如何处理的情况 503 Service Unavailable 服务器不可用,可能处于维护或重载状态,暂时无法访问 js// 1.创建对象 const xhr = new XMLHttpRequest() // 2.监听结果 xhr.onload = function() { console.log(xhr.status, xhr.statusText) // 根据http的状态码判断是否请求成功 if (xhr.status >= 200 && xhr.status < 300) { console.log(xhr.response) } else { console.log(xhr.status, xhr.statusText) } } xhr.onerror = function() { console.log("onerror", xhr.status, xhr.statusText) } // 3.设置响应类型 xhr.responseType = "json" // 4.配置网络请求 // xhr.open("get", "http://123.207.32.32:8000/abc/cba/aaa") xhr.open("get", "http://123.207.32.32:8000/home/multidata") // 5.发送网络请求 xhr.send()
6. get/post 请求传递参数
在开发中,我们使用最多的是
GET
和POST
请求,在发送请求的过程中,我们也可以传递给服务器数据常见的传递给服务器数据的方式有如下几种:
方式一:
GET
请求的query
参数方式二:
POST
请求x-www-form-urlencoded
格式方式三:
POST
请求FormData
格式方式四:
POST
请求JSON
格式
<form class="info">
<input type="text" name="username">
<input type="password" name="password">
</form>
<button class="send">发送请求</button>
<script>
const formEl = document.querySelector(".info")
const sendBtn = document.querySelector(".send")
sendBtn.onclick = function() {
// 创建xhr对象
const xhr = new XMLHttpRequest()
// 监听数据响应
xhr.onload = function() {
console.log(xhr.response)
}
// 配置请求
xhr.responseType = "json"
// 1.传递参数方式一: get -> query
xhr.open("get", "http://123.207.32.32:1888/02_param/get?name=why&age=18&address=广州市")
// 2.传递参数方式二: post -> urlencoded
xhr.open("post", "http://123.207.32.32:1888/02_param/posturl")
// // 发送请求(请求体body)
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
xhr.send("name=why&age=18&address=广州市")
// 3.传递参数方式三: post -> formdata
// xhr.open("post", "http://123.207.32.32:1888/02_param/postform")
// // formElement对象转成FormData对象
// const formData = new FormData(formEl)
// formData.append('height', 1.88)
// xhr.send(formData)
// 4.传递参数方式四: post -> json
xhr.open("post", "http://123.207.32.32:1888/02_param/postjson")
xhr.setRequestHeader("Content-type", "application/json")
xhr.send(JSON.stringify({name: "why", age: 18, height: 1.88}))
}
</script>
四. XHR 的进阶和封装
1. ajax 网络请求封装
function hyajax({
url,
method = "get",
data = {},
headers = {}, // token
success,
failure
} = {}) {
// 1.创建对象
const xhr = new XMLHttpRequest()
// 2.监听数据
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
success && success(xhr.response)
} else {
failure && failure({ status: xhr.status, message: xhr.statusText })
}
}
// 3.设置类型
xhr.responseType = "json"
// 4.open方法
if (method.toUpperCase() === "GET") {
const queryStrings = []
for (const key in data) {
queryStrings.push(`${key}=${data[key]}`)
}
url = url + "?" + queryStrings.join("&")
xhr.open(method, url)
xhr.send()
} else {
xhr.open(method, url)
xhr.setRequestHeader("Content-type", "application/json")
xhr.send(JSON.stringify(data))
}
return xhr
}
// 调用者
hyajax({
url: "http://123.207.32.32:1888/02_param/get",
method: "GET",
data: {
name: "why",
age: 18
},
success: function(res) {
console.log("res:", res)
},
failure: function(err) {
// alert(err.message)
}
})
2. 延迟时间 timeout 和取消请求
在网络请求的过程中,为了避免过长的时间服务器无法返回数据,通常我们会为请求设置一个超时时间:
timeout
- 当达到超时时间后依然没有获取到数据,那么这个请求会自动被取消掉
- 默认值为
0
,表示没有设置超时时间
我们也可以通过
abort
方法强制取消请求jsconst xhr = new XMLHttpRequest() xhr.onload = function() { console.log(xhr.response) } xhr.onabort = function() { console.log("请求被取消掉了") } xhr.responseType = "json" // 1.超时时间的设置 xhr.ontimeout = function() { console.log("请求过期: timeout") } // timeout: 浏览器达到过期时间还没有获取到对应的结果时, 取消本次请求 xhr.timeout = 3000 xhr.open("get", "http://123.207.32.32:1888/01_basic/timeout") xhr.send() // 2.手动取消结果 const cancelBtn = document.querySelector("button") cancelBtn.onclick = function() { xhr.abort() }
五. Fetch 的使用详解
1. 认识 Fetch 和 Fetch API
Fetch
可以看做是早期的XMLHttpRequest
的替代方案,它提供了一种更加现代的处理方案:- 比如返回值是一个
Promise
,提供了一种更加优雅的处理结果方式- 在请求发送成功时,调用
resolve
回调then
- 在请求发送失败时,调用
reject
回调catch
- 在请求发送成功时,调用
- 比如不像
XMLHttpRequest
一样,所有的操作都在一个对象上
- 比如返回值是一个
fetch
函数的使用:jsPromise<Response> fetch(input[, init])
input
:定义要获取的资源地址,可以是一个URL
字符串,也可以使用一个Request
对象类型(实验性特性)init
:其他初始化参数method
:请求使用的方法,如GET
、POST
headers
:请求的头信息body
:请求的body
信息
2. Fetch 数据的响应(Response)
Fetch
的数据响应主要分为两个阶段:- 阶段一:当服务器返回了响应(
response
)fetch
返回的promise
就使用内建的Response class
对象来对响应头进行解析- 在这个阶段,我们可以通过检查响应头,来检查
HTTP
状态以确定请求是否成功 - 如果
fetch
无法建立一个HTTP
请求,例如网络问题,亦或是请求的网址不存在,那么promise
就会reject
- 异常的
HTTP
状态,例如404
或500
,不会导致出现error
- 我们可以在
response
的属性中看到HTTP
状态:status
:HTTP
状态码,例如200
ok
:布尔值,如果HTTP
状态码为200-299
,则为true
- 第二阶段,为了获取
response body
,我们需要使用一个其他的方法调用response.text()
—— 读取response
,并以文本形式返回response
response.json()
—— 将response
解析为JSON
3. Fetch 网络请求的演练
基于
Promise
的使用方案:js// 1.fetch发送get请求 1.1.未优化的代码 fetch("http://123.207.32.32:8000/home/multidata").then(res => { // 1.获取到response const response = res // 2.获取具体的结果 response.json().then(res => { console.log("res:", res) }) }).catch(err => { console.log("err:", err) }) // 1.2. 优化方式一: fetch("http://123.207.32.32:8000/home/multidata").then(res => { // 1.获取到response const response = res // 2.获取具体的结果 return response.json() }).then(res => { console.log("res:", res) }).catch(err => { console.log("err:", err) })
基于
async
、await
的使用方案:js// 1.3. 优化方式二: async function getData() { const response = await fetch("http://123.207.32.32:8000/home/multidata") const res = await response.json() // json()方法本身返回的是 console.log("res:", res) } getData()
4. Fetch POST请求
创建一个
POST
请求,或者其他方法的请求,我们需要使用fetch
选项:method
:HTTP
方法,例如POST
body
:request body
,其中之一:- 字符串(例如
JSON
编码的) FormData
对象,以multipart/form-data
形式发送数据
- 字符串(例如
js// 2.post请求并且有参数 async function getData() { // const response = await fetch("http://123.207.32.32:1888/02_param/postjson", { // method: "post", // // headers: { // // "Content-type": "application/json" // // }, // body: JSON.stringify({ // name: "why", // age: 18 // }) // }) const formData = new FormData() formData.append("name", "why") formData.append("age", 18) const response = await fetch("http://123.207.32.32:1888/02_param/postform", { method: "post", body: formData }) // 获取response状态 console.log(response.ok, response.status, response.statusText) const res = await response.json() console.log("res:", res) } getData()
六. 前端文件上传流程
1. XMLHttpRequest 文件上传
文件上传是开发中经常遇到的需求,比如头像上传、照片等
- 要想真正理解文件上传,必须了解服务器如何处理上传的文件信息
- 服务器那边操作:将文件保存在服务器硬盘上(某个位置) => 返回图片地址给前端
html<input class="file" type="file"> <button class="upload">上传文件</button> <script> const fileEl = document.querySelector(".file") const uploadBtn = document.querySelector(".upload") uploadBtn.onclick = function() { // 1.获取文件 const files = fileEl.files if (!files.length) { alert('请选择要上传的文件') return } const avatarFile = files[0] const formData = new FormData() formData.append("avatar", avatarFile) // 2.开始上传 const xhr = new XMLHttpRequest() xhr.onload = function() { console.log(xhr.response) } xhr.onprogress = function(event) { console.log(event, `上传进度${event.loaded/event.total}%`) // 文件小,一下就完事了,大文件需要用断点续传,还有一种切片上传的方式 } xhr.responseType = "json" xhr.open("post", "http://123.207.32.32:1888/02_param/upload") xhr.send(formData) } </script>
2. Fetch 文件上传
Fetch
也支持文件上传,但是Fetch
没办法监听进度jsconst uploadBtn = document.querySelector(".upload") uploadBtn.onclick = async function() { // 表单 const fileEl = document.querySelector(".file") const file = fileEl.files[0] const formData = new FormData() formData.append("avatar", file) // 发送fetch请求 const response = await fetch("http://123.207.32.32:1888/02_param/upload", { method: "post", body: formData }) const res = await response.json() console.log("res:", res) }