Ajax&Axios

什么是ajax技术?

简单点说,就是使用AJAX 是异步的 JavaScript和XML(Asynchronous JavaScript And XML)。XMLHttpRequest 对象与服务器通信。

它可以使用 JSON,XML,HTML 和 text 文本等格式发送和接收数据。

AJAX 最吸引人的就是它的“异步”特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。

ajax是浏览器和服务器进行通信的技术

使用axios库

  1. 引入axios的库

    https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js

  2. 使用axios函数

    • 传入配置对象
    • 使用.then接收返回结果

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>axios</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<p class="my-p"></p>
<script>
// http://hmajax.itheima.net/api/province
axios({
url:"http://hmajax.itheima.net/api/province"
}).then((res) => {
document.querySelector(".my-p").innerHTML = res.data.list.join('<br>')
})
</script>
</body>
</html>

查询参数

在配置对象里添加一个params参数,配置对应的参数名和参数的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>axios</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<p class="my-p"></p>
<script>
// http://hmajax.itheima.net/api/province
axios({
url:"http://hmajax.itheima.net/api/city",
params:{
pname:'江西省'
}
}).then((res) => {
document.querySelector(".my-p").innerHTML = res.data.list.join('<br>')
})
</script>
</body>
</html>

常用的请求方式

请求方式 使用场景
post 提交数据到服务器
delete 删除服务器数据
put 修改服务器数据(全部)
get 获取服务器数据
patch 修改服务器数据(部分)

axios请求配置

配置项 配置项含义
url 请求的url网址
method 请求方式
params 拼接到url上的查询参数
data 提交数据

axios的错误处理

什么情况下会走catch的逻辑?

  • .then的逻辑报错
  • 网络请求的状态码是异常的(默认是200-300之间走then,自定义状态码的情况需要具体分析)

在axios发请求代码后面添加.catch,编写错误处理的逻辑

请求报文的格式

  1. 请求行

    • 请求方法
    • url
    • 请求协议
  2. 请求头

    • 以键值对格式携带的附加信息
  3. 请求空行

    • 分割请求头,空行之后是发给服务器的资源
  4. 请求体

    • 发送的资源

响应报文的格式

  1. 响应行
    • 协议
    • Http响应状态码
    • 状态信息
  2. 响应头
    • 以键值对格式携带的附加信息
  3. 空行
    • 分割响应头,空行后的是服务器响应的资源
  4. 响应体
    • 响应的资源

图片上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>上传文件</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js">
</script>
</head>
<body>
<input type="file" class="upload">
<img class="img" src="#" alt="">
<script>
document.querySelector(".upload").addEventListener("change", (e) => {
// 通过e.target.files获取到上传文件,转换成FormData格式
const fd = new FormData()
fd.append("img", e.target.files[0])
axios({
url: 'http://hmajax.itheima.net/api/uploadimg',
method: 'post',
data: fd
}).then((result) => {
console.log(result.data.data.url)
document.querySelector(".img").src = result.data.data.url
})
})
</script>
</body>
</html>

ajax的底层原理

axios底层使用的也是XMLHttpRequest

使用步骤

  1. 创建XMLHttpRequest对象
  2. 配置请求方法和请求地址
  3. 监听loadend事件,接受响应结果
  4. 发起请求

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>xhr</title>
</head>
<body>
<div id="app">

</div>
<script>
var xhr = new XMLHttpRequest();
xhr.open("get", "http://hmajax.itheima.net/api/province")
xhr.addEventListener("loadend", () => {
console.log(xhr.response)
const data = JSON.parse(xhr.response)
document.querySelector("#app").innerHTML = data.list.join("<br>")
})
xhr.send()
</script>
</body>
</html>

添加查询参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>xhr</title>
</head>
<body>
<div id="app">

</div>
<script>
var xhr = new XMLHttpRequest();
xhr.open("get", "http://hmajax.itheima.net/api/city?pname=江西省")
xhr.addEventListener("loadend", () => {
console.log(xhr.response)
const data = JSON.parse(xhr.response)
document.querySelector("#app").innerHTML = data.list.join("<br>")
})
xhr.send()
</script>
</body>
</html>

提交数据

核心操作是需要设置content-type请求头,标注上传的数据类型

发起请求时携带数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>xhr</title>
</head>
<body>
<div id="app">

</div>
<script>
var xhr = new XMLHttpRequest();
xhr.open("post", "http://hmajax.itheima.net/api/register")
xhr.setRequestHeader("Content-Type","application/json")
xhr.addEventListener("loadend", () => {
console.log(xhr.response)
})
const user = {
username:'test00122',
password:'1234522'
}
let json = JSON.stringify(user)
xhr.send(json)
</script>
</body>
</html>

axios常见问题

如何配置接口的基地址?

1
axios.defaults.baseURL = '地址'

axios请求拦截器

发起请求之前,触发的配置函数,可以在函数内编写公共操作

1
2
3
4
5
6
7
8
9
10
// 添加请求拦截器
axios.interceptors.request.use((config) => {
// 在发送请求之前做些什么
let token = localStorage.getItem('token')
token && (config.headers.Authorization = `Bearer ${token}`)
return config
}, (error) => {
// 请求失败做些什么
return Promise.reject(error)
})

axios响应拦截器

响应回到.then,.catch之前,触发的配置函数,对响应结果统一处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 添加响应拦截器
axios.interceptors.response.use((response) => {
// 2xx的状态码都会触发该函数
// 对响应数据做些什么
return response
}, (error) => {
// 超出2xx范围的状态码都会触发该函数
// 对响应错误做些什么
if(error?.response?.status === 401){
alert("登录状态过期,请重新登录")
localStorage.clear()
location.href = '../login/index.html'
}
return Promise.reject(error)
})

初识Promise

Promise对象用于表示一个异步操作的最终完成(失败)及其结果值

使用Promise函数的好处

  • 让代码逻辑更加清晰
  • 解决回调函数调用层级过多的问题

Promise函数的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1.创建一个promise对象
const promise = new Promise((resolve, reject) => {
// 2.执行异步逻辑并传递执行结果
// resolve(result)触发.then
// reject(result)触发.catch
})

promise.then((result) => {
// 处理成功的逻辑
console.log(result)
}).catch((result) => {
// 处理失败的逻辑
console.log(result)
})

promise的三种状态

image-20240826145233057

  • 待定状态:初始状态,既没有兑现,也没有拒绝
  • 已兑现:意味着操作成功完成
  • 已拒绝:意味着操作失败

promise一但敲定了是兑现或者拒绝状态,状态无法再次改变。状态从待定改成其他状态后,调用对应的处理函数

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>promise</title>
</head>
<body>

<div id="app">

</div>

<script>
// 1.创建一个promise对象
const promise = new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open('get','http://hmajax.itheima.net/api/province')
xhr.addEventListener("loadend",() => {
var json = JSON.parse(xhr.response);
if(xhr.status >= 200 && xhr.status < 300){
resolve(json)
} else {
reject(json)
}
})
xhr.send()
})


promise.then((result) => {
// 处理成功的逻辑
let app = document.querySelector("#app")
app.innerHTML = result.list.join('<br>')
}).catch((result) => {
// 处理失败的逻辑
console.log(result.message)
})
</script>
</body>
</html>

封装一个简易版的myAxios

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>myAxios</title>
</head>
<body>
<script>
function myAxios(config) {
return new Promise((resolve, reject) => {
// 创建XMLHttpRequest对象
let xhr = new XMLHttpRequest();
// 配置请求方法和请求路径
let url = config.url
let method = config.method ? config.method : 'get'
let params = config.params
if(params){
let paramStr = new URLSearchParams(params).toString()
if(url.indexOf('?') !== -1){
url += `&${paramStr}`
} else {
url += `?${paramStr}`
}
}
xhr.open(method,url)
// 添加loadend事件逻辑
xhr.addEventListener('loadend',()=>{
if(xhr.status >= 200 && xhr.status < 300){
resolve(xhr.response)
} else {
reject(new Error(xhr.response))
}
})
let data = config.data
// 发送请求
if(data){
xhr.setRequestHeader("Content-Type",'application/json')
let dataStr = JSON.stringify(data)
xhr.send(dataStr)
} else {
xhr.send()
}
})
}

myAxios({
url:'http://hmajax.itheima.net/api/register',
method:'post',
data:{
username:'200000123123',
password:'123123123123123123'
}
}).then(result => {
console.log(result)
})
</script>
</body>
</html>

同步代码和异步代码

同步代码:逐行执行,需要原地等待处理结果,才能继续向下执行

异步代码:调用后耗时,不阻塞代码继续执行,在将来完成后触发一个回调函数

回调函数地狱

在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱

存在代码可读性差,耦合性严重,异常无法捕获的问题。

异常无法捕获的现象是,外层的catch无法捕获到内层函数抛出的异常

Promise链式调用

依靠.then方法会返回一个新的Promise对象的特性,继续串联下一环任务,直到任务结束

image-20240826165506513

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>链式调用</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js">
</script>
</head>
<body>
<script>
let province = ''
axios({
url: 'http://hmajax.itheima.net/api/province',
method: 'get'
// then会产生一个新的Promise对象
}).then(result => {
province = result.data.list[2]
// return的结果会传递给新的Promise对象
return axios({
url: 'http://hmajax.itheima.net/api/city',
method: 'get',
params: {
pname: province
}
})
}).then(result => {
return axios({
url: 'http://hmajax.itheima.net/api/area',
method: 'get',
params: {
pname: province,
cname: result.data.list[2]
}
})
}).then(result => {
console.log("result",result)
}).catch(error => {
console.log("这里处理异常")
})
</script>
</body>
</html>

aysnc & await

async 函数是使用 async 关键字声明的函数。async 函数是 AsyncFunction构造函数的实例,并且其中允许使用 await 关键字。

async 和 await 关键字让我们可以用一种更简洁的方式写出基于 promise 的异步行为,而无需刻意地链式调用 promise。

用async和await解决回调地狱的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>async&await</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js">
</script>
</head>
<body>
<script>
// 定义async修饰函数
async function getData() {
// 等待promise执行,得到返回结果
const provinces = await axios({
url: 'http://hmajax.itheima.net/api/province',
method: 'get'
})

const cities = await axios({
url: 'http://hmajax.itheima.net/api/city',
method: 'get',
params: {
pname: provinces.data.list[2]
}
})

const areas = await axios({
url: 'http://hmajax.itheima.net/api/area',
method: 'get',
params: {
pname: provinces.data.list[2],
cname: cities.data.list[2]
}
})

console.log(areas)
}

getData()
</script>
</body>
</html>

捕获异常处理

使用try…catch捕获。

1
2
3
4
5
6
try {
// 要执行的代码
} catch(error){
// error接收错误信息
// 处理异常的逻辑
}

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
async function getData() {
// 等待promise执行,得到返回结果
try {
const provinces = await axios({
url: 'http://hmajax.itheima.net/api/province',
method: 'get'
})

const cities = await axios({
url: 'http://hmajax.itheima.net/api/city',
method: 'get',
params: {
pname: provinces.data.list[2]
}
})

const areas = await axios({
url: 'http://hmajax.itheima.net/api/area1',
method: 'get',
params: {
pname: provinces.data.list[2],
cname: cities.data.list[2]
}
})

console.log(areas)
} catch (e) {
console.log(e)
}
}

打印异常:

image-20240826214228827

事件循环(EventLoop)

JavaScript 有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。

这个模型与其它语言中的模型截然不同,比如 C和 Java。

为什么JS要这样设计呢?

JavaScript 单线程(某一刻只能执行一行代码),为了让耗时代码不阻塞其他代码运行,设计了事件循环模型

事件循环执行过程

执行代码和收集异步任务的模型,在调用栈空闲,反复调用任务队列里回调函数的执行机制,就叫事件循环

image-20240826214906162

  1. 将console.log(1)推入到调用栈,执行完出栈
  2. 将0秒的定时器推入调用栈,发现是异步任务,交给宿主环境,又因为是0秒后执行,将任务交给任务队列
  3. 将console.log(3)推入到调用栈,执行完出栈
  4. 将2秒的定时器推入调用栈,发现是异步任务,交给宿主环境,开始倒计时
  5. 将console.log(5)推入到调用栈,执行完出栈
  6. 代码执行完毕,调用栈空闲,尝试从任务队列取出任务到调用栈,此时0秒的任务被被推入调用栈,执行完毕出栈
  7. 2秒的倒计时完毕,任务进入任务队列
  8. 代码执行完毕,调用栈空闲,尝试从任务队列取出任务到调用栈,此时2秒的任务被被推入调用栈,执行完毕出栈

总结:Js的代码如何执行

  1. 执行同步代码,遇到异步代码交给宿主环境执行
  2. 异步有了结果之后,将回调函数交给任务队列等待排队
  3. 当调用栈空闲后,反复调用任务队列的回调函数

宏任务和微任务

宏任务和微任务的定义

  • 宏任务:交给浏览器执行的异步代码
  • 微任务:交给JS引擎执行的异步代码

image-20240826220557855

宏任务和微任务的执行顺序

image-20240826220935429

  1. 将console.log(1)推入到调用栈,执行完出栈
  2. 将0秒的定时器推入调用栈,发现是异步任务,交给宿主环境,又因为是0秒后执行,将任务交给宏任务队列
  3. 将创建promise对象的代码推入调用栈,因为promise本身是同步的,将3打印
  4. 将p.then代码推入调用栈,发现是异步任务,又因为Promise函数的then,catch是属于微任务,将这个回调函数交给微任务队列
  5. 将console.log(5)推入到调用栈,执行完出栈
  6. 调用栈空闲,优先从微任务队列取出任务执行,打印4
  7. 宏任务队列取出任务执行,打印2

Promise.all()静态方法

合并多个Promise对象的逻辑,等待所有同时成功完成之后(或某一个失败),做后续逻辑

image-20240826224121700

案例:同时请求“北京”、“上海”、“广州”、“深圳”的天气并在网页尽可能同时显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>天气</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js">
</script>
</head>
<body>
<p class="my-p">

</p>
<script>
// 四个城市的编码
// 北京-110100 上海-310100 广州-440100 深圳-440300
const arr = ['110100', '310100', '440100', '440300']
// 定义Promise数组
const promises = []
arr.forEach((item) => {
promises.push(
axios({
url: 'http://hmajax.itheima.net/api/weather',
params: {
city: item
}
})
)
})
// 使用Promise.all合并
let p = Promise.all(promises)
p.then(result => {
let ele = result.map(item => {
return `<li>${item.data.data.area}---${item.data.data.weather}</li>`
}).join('<br>')
document.querySelector(".my-p").innerHTML = ele
}).catch(error => {
console.dir(error)
})
</script>
</body>
</html>