(声明:本笔记主要来自尚硅谷的原生AJAX课程)
原生AJAX(笔记01) - AJAX简介、特点
原生AJAX(笔记02) - HTTP协议简介
原生AJAX(笔记03) - Node.js 和 Express 的简介、安装
原生AJAX(笔记04) - GET请求
原生AJAX(笔记05) - POST请求
原生AJAX(笔记06) - 服务端响应JSON数据
原生AJAX(笔记07) - 请求超时、网络异常、取消请求和重复请求处理
原生AJAX(笔记08) - 总结
AJAX简介
AJAX 全称为 Asynchronous JavasScript And XML,就是异步的 JS 和 XML。通过 AJAX 可以在浏览器中向服务器发送异步请求,最大的优势:
无刷新获取数据
。
AJAX特点
优点:可以无刷新与服务器通信,根据用户事件更新部分内容(如:点击)。
不足:没有浏览历史,不能回退,存在跨域问题,SEO不友好。
应用场景:搜索提示,数据分页,数据增删改查,表单验证,懒加载等。
AJAX实现
就实现前后端通信这项能力而言,浏览器基础的 AJAX 是用 XHR 和 Fetch API,知名的 JQuery.ajax 和 Axios 是对浏览器原生 API 的二次封装。
AJAX与HTTP
AJAX通过HTTP协议与服务端通信,客户端发送请求,服务端响应请求。
请求报文:包括四个部分:行、头、空行、体
行 POST /s?ie=utf-8 HTTP/1.1 ## 三部分:请求方式,参数,协议版本
头 Host:baidu.com
Cookie:name=jack
Content-type: application/x-www-form-urlencoded
User-Agent: chrome 83
空行
体 username=admin&password=admin
响应报文:也是四部分:行、头、空行、体
行 HTTP/1.1 200 OK
头 Content-Type:text/html;charset=utf-8
Content-length:2048
Content-encoding:gzip
空行
体 <html>
<head>
</head>
<body>
<h1>欢迎学习HTTP</h1>
</body>
</html>
AJAX与后端服务
响应前端AJAX请求的后端服务也有很多,基本各种语言都能实现(Java,php,python),基于原生的JS后端服务器,知名的是 Express 框架。
Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建 Web 服务器的,本质是一个 npm 上的第三方包,提供了快速创建 Web 服务器的方法。
服务端代码(以Express为例):
// 1. 引入express模块
const express = require('express')
// 2. 创建应用对象
const app = express()
// 3. 创建路由规则 get / post / all
// request 是对请求报文的封装;
// response 是对响应报文的封装;
app.get('/',(request,response)=>{
// 允许跨域
response.setHeader('Access-Control-Allow-Origin', '*')
// 设置响应
response.send('Hello Express!')
})
// 4. 监听端口,启动服务
app.listen(8000,()=>{
console.log('服务已经启动,端口8000监听中...');
})
根据前端请求方法、传参和数据格式等要求的不同,修改相应的后端代码;
比如:修改路由、设置CORS规则和JSON数据等;
AJAX的GET请求
需求:点击按钮将响应来的数据显示在DIV上;
后端代码:可以使用上面通用型WEB服务器的代码;
前端JS代码:
// 获取button按钮
const btn = document.getElementsByTagName('button')[0]
// 获取div容器
const result = document.getElementById('result')
// 绑定事件
btn.onclick = function () {
// 1. 创建对象
const xhr = new XMLHttpRequest
// 2. 初始化,设置 请求方法 和 URL(写全)
xhr.open('GET', 'http://127.0.0.1:8000/server')
// 3. 发送
xhr.send()
// 4. 事件绑定,处理服务端返回的结果
xhr.onreadystatechange = function () {
// 判断 readyState 状态,只有4表示返回所有结果
if (xhr.readyState === 4) {
// 判断响应码 200,400,500 等;
// 2XX的响应码都表示成功
if (xhr.status >= 200 && xhr.status < 300) {
// 结果包括4个部分:行,头,空行,体
console.log(xhr.status); // 响应行状态码
console.log(xhr.statusText); // 响应行状态码字符串
console.log(xhr.getAllResponseHeaders()) // 所有响应头
console.log(xhr.response); // 响应体
// 响应体显示在容器里
result.innerHTML = xhr.response
}
}
}
}
传递参数:写在URL之后;
xhr.open('GET', 'http://127.0.0.1:8000/server?a=1&b=2&c=3')
IE浏览器会有缓存问题,解决办法是URL后加个时间戳;
xhr.open('GET', 'http://127.0.0.1:8000/server?a=1&b=2&c=3&t='+Date.now())
AJAX的POST请求
需求:鼠标移入 DIV 时发送POST请求,将响应数据显示在DIV里;
后端代码:这次改为 POST路由
const express = require('express')
const app = express()
// post 路由
app.post('/server', (request, response) => {
response.setHeader('Access-Control-Allow-Origin', '*')
response.send('Hello Ajax post!')
})
app.listen('8000', () => {
console.log('Web服务已经启动,端口8000监听中... ...');
})
前端代码:
// 获取对象
let result = document.getElementById('result')
// 绑定事件
result.addEventListener('mouseover',function(){
// 1. 创建对象
const xhr = new XMLHttpRequest
// 2. 初始化 设置类型与URL
xhr.open('POST', 'http://127.0.0.1:8000/server')
// 3. 发送请求
xhr.send()
// 4. 事件绑定
xhr.onreadystatechange = function () {
// 判断
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.response);
// 返回结果
result.innerHTML = xhr.response
}
}
}
})
POST 传参:写在send() 方法中;
格式一:
格式二:
POST设置请求头:必须写在 xhr.open() 和 xhr.send() 方法之间。
// 预定义请求头
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
// 自定义请求头
xhr.setRequestHeader('name','Jacky')
设置自定义请求头会触发 OPTIONS预检请求,需要后端做处理。
AJAX请求中的问题
同源策略
如果两个 URL 的 协议、域名 和 端口 都相同时,我们就称这两个 URL 同源。同源策略是浏览器出于安全考虑会阻止不同源间的JS脚本交互,目的是保护用户信息安全,防止恶意的网站窃取、身份伪造等隐患。
跨域和CORS机制
两个不同源的资源之间非要进行交互称为跨域(或跨源)。跨域本质就是要绕过同源策略的严格限制,浏览器给出了 CORS机制,即跨域资源共享的概念,可以通过正确设置 CORS 头信息的方式允许服务端响应客户端的请求。
注意
:很多人以为同源策略是浏览器不让请求发出去、或者后端拒绝返回数据。实际情况是,请求能正常发出,后端接口正常响应,只是
数据到了浏览器后被丢弃了(或被拦截了)
。
比如,设置 Origin ,允许任何域来的请求。
response.setHeader('Access-Control-Allow-Origin', '*')
OPTIONS预检请求
当浏览器即跨域了,又发送了自定义请求头时,会触发 OPTIONS预检请求,服务器要给予正确的响应,不然就报错了。
比如:设置 Header,允许自定义请求头。
response.setHeader('Access-Control-Allow-Headers', '*')
比如:设置服务端路由,接收 GET / POST 之外的请求方法。
app.all('/server', (request, response) => {
// ...
})
比如:把预检请求的响应缓存起来,在有效期内只需一次预检。
response.setHeader('Access-Control-Max-Age', '600')
AJAX请求服务端响应JSON数据
需求:点击按钮,把服务端的JSON格式数据显示在DIV中。
服务端代码:
const express = require('express')
const app = express()
app.get('/json-server', (request, response) => {
response.setHeader('Access-Control-Allow-Origin', '*')
// 对象数据
let data = {name:"jacky"}
// 转化JSON格式,对象变JSON字符串
let str = JSON.stringify(data)
response.send(str)
})
app.listen('8000', () => {
console.log('Web服务已经启动,端口8000监听中... ...');
})
前端代码:
接收JSON对象数据方法一
前端手动转化JSON数据: JSON.parse() 方法
let btn = document.getElementsByTagName('button')[0]
let res = document.getElementById('result')
btn.addEventListener('click', function () {
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:8000/json-server')
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 前端手动转化JSON数据,JSON字符串变对象
let data = JSON.parse(xhr.response)
console.log(data);
res.innerHTML = data.name
}
}
}
})
接收JSON对象数据方法二
设置响应体数据类型:设置 xhr.responseType 属性值为 json。
let btn = document.getElementsByTagName('button')[0]
let res = document.getElementById('result')
btn.addEventListener('click', function () {
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:8000/json-server')
xhr.send()
// 设置响应体数据的类型
xhr.responseType = 'json'
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.response);
res.innerHTML = xhr.response.name
}
}
}
})
AJAX的其他应用
开发的产品上线后,难免会遇到请求超时或网络异常的情况,在客户端设置超时处理和异常提醒,可以提升用户体验。手动取消请求和取消重复发送请求,可以节省服务端资源;
先模拟后端延迟响应数据:延时3秒响应。
const express = require('express')
const app = express()
app.get('/delay', (request, response) => {
response.setHeader('Access-Control-Allow-Origin', '*')
setTimeout(() => {
response.send('Hello Ajax!')
}, 3000)
})
app.listen('8000', () => {
console.log('Web服务已经启动,端口8000监听中... ...');
})
请求超时设置: xhr.timeout
let btn = document.getElementsByTagName('button')[0]
let res = document.getElementById('result')
btn.onclick = function(){
let xhr = new XMLHttpRequest();
// 超时设置2秒
xhr.timeout = 2000
xhr.open("GET","http://127.0.0.1:8000/delay")
xhr.send()
// ... 代码省略
}
提示:只需设置超时时长,超时后自动取消请求;
请求超时回调:xhr.ontimeout
let btn = document.getElementsByTagName('button')[0]
let res = document.getElementById('result')
btn.onclick = function(){
let xhr = new XMLHttpRequest();
// 超时设置2秒
xhr.timeout = 2000
// 超时设置回调
xhr.ontimeout = function(){
console.log('请求超时了');
}
// .... 代码省略
}
提示:超时后会取消请求,并触发超时回调函数,在控制台输出提示信息;
网络异常回调:xhr.onerror
let btn = document.getElementsByTagName('button')[0]
let res = document.getElementById('result')
btn.onclick = function(){
let xhr = new XMLHttpRequest();
// 网络异常回调
xhr.onerror = function(){
console.log("请检查网络...");
}
// .... 代码省略
}
开发者工具可以模拟断线的情况:异常后触发 onerror 回调。
手动取消请求:xhr.abort() 方法
// 获取元素对象
let btns = document.getElementsByTagName('button')
let res = document.getElementById('result')
let xhr = null
// 发起请求按钮
btns[0].onclick = function () {
xhr = new XMLHttpRequest();
xhr.open("GET", "http://127.0.0.1:8000/delay")
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
res.innerHTML = xhr.response
}
}
}
}
// 取消请求按钮
btns[1].onclick = function(){
xhr.abort()
}
设置两个按钮,一个发送请求,一个取消请求。
注意:这里的 xhr 放在外层,让两个按钮都能用的到。
取消重复发送请求:设置开关 isSending
let btn = document.getElementsByTagName('button')[0]
let res = document.getElementById('result')
let xhr = null
// 设置开关,代表是否正在发送请求
let isSending = false
btn.onclick = function () {
// 判断开关
if(isSending) xhr.abort()
xhr = new XMLHttpRequest();
// 发送请求前,打开开关
isSending = true
xhr.open("GET", "http://127.0.0.1:8000/delay")
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 关闭开关
isSending = false
if (xhr.status >= 200 && xhr.status < 300) {
res.innerHTML = xhr.response
}
}
}
}
提示:设置开关的作用是每当点击按钮就都要先判断一下当前是不是处在请求中。如果是,就 abort 掉,并开启一个新的请求;如果不是,就开启一个新的请求,并打开开关,直到响应状态完成后再关闭开关。
扩展阅读:
NPM 简介、安装、配置、常用方法
Express 简介、安装、使用和案例
Nodemon 简介、安装、使用和配置
HTTP协议简介、工作原理、请求方法、请求/响应步骤、示例
HTTP访问控制(CORS)、同源策略、跨域和预检请求等问题
HTTP 的 OPTIONS 预检请求简介、特点、触发和优化
HTTP 头信息解读