Ajax 筆記:
Ajax 筆記(一)—— Ajax 入門(mén)
Ajax 筆記(二)—— Ajax 案例
Ajax 筆記(三)—— Ajax 原理
Ajax 筆記(四)—— Ajax 進(jìn)階
Ajax 筆記接口文檔:https://apifox.com/apidoc/shared-fa9274ac-362e-4905-806b-6135df6aa90e/doc-842135
4. Ajax 進(jìn)階
4.1 同步代碼和異步代碼
JavaScript 是單線程的語(yǔ)言,意味著它只有一個(gè)主線程用來(lái)執(zhí)行代碼。這個(gè)特點(diǎn)決定了 JavaScript 在同一時(shí)間只能處理一個(gè)任務(wù),防止了多線程帶來(lái)的競(jìng)態(tài)條件和死鎖等問(wèn)題。隨著 HTML5 到來(lái)也支持了多線程webWorker,但也是不允許操作 DOM 。
-
同步代碼:逐行執(zhí)行,需原地等待結(jié)果后,才繼續(xù)向下執(zhí)行
-
異步代碼:調(diào)用后耗時(shí),不阻塞代碼繼續(xù)執(zhí)行(不必原地等待),在將來(lái)完成后觸發(fā)回調(diào)函數(shù)傳遞結(jié)果,比如:setTimeout / setInterval,事件,Ajax
4.2 回調(diào)函數(shù)地獄
- 概念:在回調(diào)函數(shù)中嵌套回調(diào)函數(shù),一直嵌套下去就形成了回調(diào)函數(shù)地獄
- 缺點(diǎn):可讀性差,異常無(wú)法捕獲,耦合性嚴(yán)重
代碼示例:
// 需求:展示默認(rèn)第一個(gè)省,第一個(gè)城市,第一個(gè)地區(qū)在下拉菜單中
axios({ url: 'http://hmajax.itheima.net/api/province' }).then(result => {
const pname = result.data.list[0]
document.querySelector('.province').innerHTML = pname
// 獲取第一個(gè)省份默認(rèn)下屬的第一個(gè)城市名字
axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } }).then(result => {
const cname = result.data.list[0]
document.querySelector('.city').innerHTML = cname
// 獲取第一個(gè)城市默認(rèn)下屬第一個(gè)地區(qū)名字
axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } }).then(result => {
document.querySelector('.area').innerHTML = result.data.list[0]
})
})
})
4.2.1 解決方法一:Promise 鏈?zhǔn)秸{(diào)用
- 依靠
then()
方法會(huì)返回一個(gè)新生成的 Promise 對(duì)象特性,繼串聯(lián)下一環(huán)任務(wù),直到結(jié)束。then()
回調(diào)函數(shù)中的返回值,會(huì)影響新生成的 Promise 對(duì)象最終狀態(tài)和結(jié)果。
代碼示例:
/**
* 目標(biāo):掌握Promise的鏈?zhǔn)秸{(diào)用
* 需求:把省市的嵌套結(jié)構(gòu),改成鏈?zhǔn)秸{(diào)用的線性結(jié)構(gòu)
*/
// 1. 創(chuàng)建Promise對(duì)象-模擬請(qǐng)求省份名字
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('北京市')
}, 2000)
})
// 2. 獲取省份名字
const p2 = p.then(result => {
console.log(result)
// 3. 創(chuàng)建Promise對(duì)象-模擬請(qǐng)求城市名字
// return Promise對(duì)象最終狀態(tài)和結(jié)果,影響到新的Promise對(duì)象
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result + '--- 北京')
}, 2000)
})
})
// 4. 獲取城市名字
p2.then(result => {
console.log(result)
})
// then()原地的結(jié)果是一個(gè)新的Promise對(duì)象
console.log(p2 === p)
- 利用
Promise
鏈?zhǔn)秸{(diào)用解決回調(diào)地獄
/**
* 目標(biāo):把回調(diào)函數(shù)嵌套代碼,改成Promise鏈?zhǔn)秸{(diào)用結(jié)構(gòu)
* 需求:獲取默認(rèn)第一個(gè)省,第一個(gè)市,第一個(gè)地區(qū)并展示在下拉菜單中
*/
let pname = ''
// 1. 得到-獲取省份Promise對(duì)象
axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => {
pname = result.data.list[0]
document.querySelector('.province').innerHTML = pname
// 2. 得到-獲取城市Promise對(duì)象
return axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
}).then(result => {
const cname = result.data.list[0]
document.querySelector('.city').innerHTML = cname
// 3. 得到-獲取地區(qū)Promise對(duì)象
return axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
}).then(result => {
console.log(result)
const areaName = result.data.list[0]
document.querySelector('.area').innerHTML = areaName
})
4.2.2 解決方法二:async 函數(shù)和 await
在 async 函數(shù)內(nèi),使用 await 關(guān)鍵字取代 then 函數(shù),等待獲取 Promise 對(duì)象成功狀態(tài)的結(jié)果值
- 利用async 函數(shù)和 await 解決回調(diào)地獄
/**
* 目標(biāo):掌握async和await語(yǔ)法,解決回調(diào)函數(shù)地獄
* 概念:在async函數(shù)內(nèi),使用await關(guān)鍵字,獲取Promise對(duì)象"成功狀態(tài)"結(jié)果值
* 注意:await必須用在async修飾的函數(shù)內(nèi)(await會(huì)阻止"異步函數(shù)內(nèi)"代碼繼續(xù)執(zhí)行,原地等待結(jié)果)
*/
// 1. 定義async修飾函數(shù)
async function getData() {
// 2. await等待Promise對(duì)象成功的結(jié)果
const pObj = await axios({url: 'http://hmajax.itheima.net/api/province'})
const pname = pObj.data.list[0]
const cObj = await axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
const cname = cObj.data.list[0]
const aObj = await axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
const areaName = aObj.data.list[0]
document.querySelector('.province').innerHTML = pname
document.querySelector('.city').innerHTML = cname
document.querySelector('.area').innerHTML = areaName
}
getData()
- async 函數(shù)和 await 捕獲錯(cuò)誤
try {
// 要執(zhí)行的代碼
} catch (error) {
// error 接收的是,錯(cuò)誤消息
// try 里代碼,如果有錯(cuò)誤,直接進(jìn)入這里執(zhí)行
}
改進(jìn)上述代碼:
/**
* 目標(biāo):async和await_錯(cuò)誤捕獲
*/
async function getData() {
// 1. try包裹可能產(chǎn)生錯(cuò)誤的代碼
try {
const pObj = await axios({ url: 'http://hmajax.itheima.net/api/province' })
const pname = pObj.data.list[0]
const cObj = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } })
const cname = cObj.data.list[0]
const aObj = await axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } })
const areaName = aObj.data.list[0]
document.querySelector('.province').innerHTML = pname
document.querySelector('.city').innerHTML = cname
document.querySelector('.area').innerHTML = areaName
} catch (error) {
// 2. 接著調(diào)用catch塊,接收錯(cuò)誤信息
// 如果try里某行代碼報(bào)錯(cuò)后,try中剩余的代碼不會(huì)執(zhí)行了
console.dir(error)
}
}
getData()
4.3 Promise.all 靜態(tài)方法
合并多個(gè) Promise 對(duì)象并等待所有同時(shí)成功的結(jié)果,如果有一個(gè)報(bào)錯(cuò)就會(huì)最終為失敗狀態(tài),當(dāng)需要同時(shí)渲染多個(gè)接口數(shù)據(jù)同時(shí)到網(wǎng)頁(yè)上時(shí)使用
const p = Promise.all([Promise對(duì)象, Promise對(duì)象, ...])
p.then(result => {
// result 結(jié)果: [Promise對(duì)象成功結(jié)果, Promise對(duì)象成功結(jié)果, ...]
}).catch(error => {
// 第一個(gè)失敗的 Promise 對(duì)象,拋出的異常對(duì)象
})
實(shí)例代碼:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-641660.html
<!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>Promise的all方法</title>
</head>
<body>
<ul class="my-ul"></ul>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目標(biāo):掌握Promise的all方法作用,和使用場(chǎng)景
* 業(yè)務(wù):當(dāng)我需要同一時(shí)間顯示多個(gè)請(qǐng)求的結(jié)果時(shí),就要把多請(qǐng)求合并
* 例如:默認(rèn)顯示"北京", "上海", "廣州", "深圳"的天氣在首頁(yè)查看
* code:
* 北京-110100
* 上海-310100
* 廣州-440100
* 深圳-440300
*/
// 1. 請(qǐng)求城市天氣,得到Promise對(duì)象
const bjPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '110100' } })
const shPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '310100' } })
const gzPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440100' } })
const szPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440300' } })
// 2. 使用Promise.all,合并多個(gè)Promise對(duì)象
const p = Promise.all([bjPromise, shPromise, gzPromise, szPromise])
p.then(result => {
// 注意:結(jié)果數(shù)組順序和合并時(shí)順序是一致
console.log(result)
const htmlStr = result.map(item => {
return `<li>${item.data.data.area} --- ${item.data.data.weather}</li>`
}).join('')
document.querySelector('.my-ul').innerHTML = htmlStr
}).catch(error => {
console.dir(error)
})
</script>
</body>
</html>
4.4 事件循環(huán)
4.4.1 事件循環(huán)
- 作用:事件循環(huán)負(fù)責(zé)執(zhí)行代碼,收集和處理事件以及執(zhí)行隊(duì)列中的子任務(wù)
- 原因:JavaScript 單線程(某一刻只能執(zhí)行一行代碼),為了讓耗時(shí)代碼不阻塞其他代碼運(yùn)行,設(shè)計(jì)了事件循環(huán)模型
- 概念:執(zhí)行代碼和收集異步任務(wù)的模型,在調(diào)用??臻e,循環(huán)不斷地調(diào)用任務(wù)隊(duì)列里回調(diào)函數(shù)的執(zhí)行機(jī)制,就叫事件循環(huán)
舉例說(shuō)明:
/**
* 目標(biāo):閱讀并回答執(zhí)行的順序結(jié)果
*/
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
function myFn() {
console.log(3)
}
function ajaxFn() {
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://hmajax.itheima.net/api/province')
xhr.addEventListener('loadend', () => {
console.log(4)
})
xhr.send()
}
for (let i = 0; i < 1; i++) {
console.log(5)
}
ajaxFn()
document.addEventListener('click', () => {
console.log(6)
})
myFn()
// 結(jié)果:1 5 3 2 4 點(diǎn)擊一次document就會(huì)執(zhí)行一次打印6
代碼分析:
-
console.log(1)
進(jìn)入調(diào)用棧,然后在控制臺(tái)打印1
-
setTimeout
是異步代碼,交給瀏覽器,console.log(2)
在 0 s 過(guò)后進(jìn)入任務(wù)隊(duì)列 - 然后兩個(gè)
function
不調(diào)用,不執(zhí)行 -
for
循環(huán)放入調(diào)用棧執(zhí)行,打印5
- 執(zhí)行
ajaxFn
,loadend
事件異步,交給瀏覽器 - 用戶(hù)
click
異步點(diǎn)擊事件,交給瀏覽器,和loadend
一樣處于監(jiān)測(cè)狀態(tài) -
myFn
放入調(diào)用棧執(zhí)行,打印3
- 同步代碼執(zhí)行完畢,調(diào)用棧清空,然后反復(fù)查找任務(wù)隊(duì)列有無(wú)需要執(zhí)行的回調(diào)函數(shù)
- 執(zhí)行
setTimeout
,進(jìn)入調(diào)用棧,打印2
,出棧 - 瀏覽器中的
loadend
事件被放入任務(wù)隊(duì)列,然后放入調(diào)用棧,執(zhí)行,打印4
- 最后
click
事件,點(diǎn)擊后打印6
4.4.2 宏任務(wù)與微任務(wù)
- 異步任務(wù)劃分:
- 宏任務(wù):由瀏覽器環(huán)境執(zhí)行的異步代碼
- 微任務(wù):由 JS 引擎環(huán)境執(zhí)行的異步代碼
宏任務(wù)(Macrotasks):
- setTimeout 和 setInterval:用于創(chuàng)建定時(shí)任務(wù),會(huì)在指定的時(shí)間間隔之后執(zhí)行回調(diào)函數(shù)。
- DOM 事件:如點(diǎn)擊事件、輸入事件等。
- 網(wǎng)絡(luò)請(qǐng)求:如 Ajax 請(qǐng)求、fetch 等。
- I/O 操作:如文件讀寫(xiě)、讀取數(shù)據(jù)庫(kù)等。
- requestAnimationFrame:用于在每次瀏覽器重繪之前執(zhí)行的回調(diào)。
- MessageChannel:一種更底層的異步任務(wù)創(chuàng)建方式。
- UI 渲染:瀏覽器會(huì)在宏任務(wù)執(zhí)行完成后進(jìn)行 UI 渲染。
微任務(wù)(Microtasks):
- Promise 的 resolve 和 reject:當(dāng)一個(gè) Promise 被 resolved 或 rejected 時(shí),相關(guān)的回調(diào)會(huì)被放入微任務(wù)隊(duì)列。
- process.nextTick(Node.js 環(huán)境下):類(lèi)似于 Promise 的微任務(wù),但在 Node.js 環(huán)境中使用。
- Object.observe 和 MutationObserver:用于監(jiān)聽(tīng)對(duì)象的變化,在變化后會(huì)觸發(fā)回調(diào)。
- Vue 的 nextTick:Vue 中用于延遲執(zhí)行 DOM 更新之后的回調(diào)。
- await 表達(dá)式的后續(xù)操作:在 async 函數(shù)中,await 表達(dá)式后的操作會(huì)被放入微任務(wù)隊(duì)列。
JavaScript 執(zhí)行順序如下:
-
執(zhí)行同步代碼:從上到下逐行執(zhí)行當(dāng)前代碼文件中的同步代碼,將函數(shù)調(diào)用壓入執(zhí)行棧,遇到異步任務(wù)會(huì)將其注冊(cè),但不會(huì)立即執(zhí)行。
-
執(zhí)行微任務(wù):在每次事件循環(huán)中,首先會(huì)執(zhí)行微任務(wù)隊(duì)列中的所有微任務(wù),直到微任務(wù)隊(duì)列為空。這保證了微任務(wù)比宏任務(wù)更早執(zhí)行。
-
執(zhí)行宏任務(wù):然后從宏任務(wù)隊(duì)列中取出一個(gè)宏任務(wù)(通常是先進(jìn)先出),執(zhí)行它,可能觸發(fā)異步操作。
-
可能進(jìn)行 UI 渲染:在宏任務(wù)執(zhí)行完成后,瀏覽器有機(jī)會(huì)進(jìn)行 UI 渲染(如果有需要的話)。
-
重復(fù)以上步驟:然后事件循環(huán)繼續(xù),重復(fù)執(zhí)行微任務(wù)、宏任務(wù)、可能的 UI 渲染等步驟。
實(shí)例代碼:
/**
* 目標(biāo):閱讀并回答打印的執(zhí)行順序
*/
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
const p = new Promise((resolve, reject) => {
resolve(3)
})
p.then(res => {
console.log(res)
})
console.log(4)
// 1 3 5 4 2
代碼分析:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-641660.html
-
console.log(1)
進(jìn)入調(diào)用棧,打印1
-
setTiemout
宏任務(wù),交給瀏覽器,0s
后console.log(2)
放入宏任務(wù)隊(duì)列排隊(duì) -
new Promise
放入調(diào)用棧,執(zhí)行console.log(3)
,打印3
,用4
標(biāo)記成功,然后出棧 -
p
調(diào)用過(guò)resolve
,處于成功狀態(tài),p.then()
里面的回調(diào)函數(shù)是微任務(wù),放入微任務(wù)隊(duì)列排隊(duì) -
console.log(5)
,進(jìn)入調(diào)用棧打印5
,出棧 - 調(diào)用棧清空,先調(diào)度微任務(wù)隊(duì)列的回調(diào)函數(shù)
- 執(zhí)行微任務(wù)
console.log(result)
,打印4
- 接著調(diào)度宏任務(wù)
console.log(2)
,打印2
4.5 案例
4.5.1 案例一-商品分類(lèi)
/**
* 目標(biāo):把所有商品分類(lèi)“同時(shí)”渲染到頁(yè)面上
* 1. 獲取所有一級(jí)分類(lèi)數(shù)據(jù)
* 2. 遍歷id,創(chuàng)建獲取二級(jí)分類(lèi)請(qǐng)求
* 3. 合并所有二級(jí)分類(lèi)Promise對(duì)象
* 4. 等待同時(shí)成功后,渲染頁(yè)面
*/
// 1. 獲取所有一級(jí)分類(lèi)數(shù)據(jù)
axios({
url: 'http://hmajax.itheima.net/api/category/top'
}).then(result => {
console.log(result)
// 2. 遍歷id,創(chuàng)建獲取二級(jí)分類(lèi)請(qǐng)求
const secPromiseList = result.data.data.map(item => {
return axios({
url: 'http://hmajax.itheima.net/api/category/sub',
params: {
id: item.id // 一級(jí)分類(lèi)id
}
})
})
console.log(secPromiseList) // [二級(jí)分類(lèi)請(qǐng)求Promise對(duì)象,二級(jí)分類(lèi)請(qǐng)求Promise對(duì)象,...]
// 3. 合并所有二級(jí)分類(lèi)Promise對(duì)象
const p = Promise.all(secPromiseList)
p.then(result => {
console.log(result)
// 4. 等待同時(shí)成功后,渲染頁(yè)面
const htmlStr = result.map(item => {
const dataObj = item.data.data // 取出關(guān)鍵數(shù)據(jù)對(duì)象
return `<div class="item">
<h3>${dataObj.name}</h3>
<ul>
${dataObj.children.map(item => {
return `<li>
<a href="javascript:;">
<img src="${item.picture}">
<p>${item.name}</p>
</a>
</li>`
}).join('')}
</ul>
</div>`
}).join('')
console.log(htmlStr)
document.querySelector('.sub-list').innerHTML = htmlStr
})
})
4.5.2 案例二-學(xué)習(xí)反饋
/**
* 目標(biāo)1:完成省市區(qū)下拉列表切換
* 1.1 設(shè)置省份下拉菜單數(shù)據(jù)
* 1.2 切換省份,設(shè)置城市下拉菜單數(shù)據(jù),清空地區(qū)下拉菜單
* 1.3 切換城市,設(shè)置地區(qū)下拉菜單數(shù)據(jù)
*/
// 1.1 設(shè)置省份下拉菜單數(shù)據(jù)
axios({
url: 'http://hmajax.itheima.net/api/province'
}).then(result => {
const optionStr = result.data.list.map(pname => `<option value="${pname}">${pname}</option>`).join('')
document.querySelector('.province').innerHTML = `<option value="">省份</option>` + optionStr
})
// 1.2 切換省份,設(shè)置城市下拉菜單數(shù)據(jù),清空地區(qū)下拉菜單
document.querySelector('.province').addEventListener('change', async e => {
// 獲取用戶(hù)選擇省份名字
// console.log(e.target.value)
const result = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname: e.target.value } })
const optionStr = result.data.list.map(cname => `<option value="${cname}">${cname}</option>`).join('')
// 把默認(rèn)城市選項(xiàng)+下屬城市數(shù)據(jù)插入select中
document.querySelector('.city').innerHTML = `<option value="">城市</option>` + optionStr
// 清空地區(qū)數(shù)據(jù)
document.querySelector('.area').innerHTML = `<option value="">地區(qū)</option>`
})
// 1.3 切換城市,設(shè)置地區(qū)下拉菜單數(shù)據(jù)
document.querySelector('.city').addEventListener('change', async e => {
console.log(e.target.value)
const result = await axios({url: 'http://hmajax.itheima.net/api/area', params: {
pname: document.querySelector('.province').value,
cname: e.target.value
}})
console.log(result)
const optionStr = result.data.list.map(aname => `<option value="${aname}">${aname}</option>`).join('')
console.log(optionStr)
document.querySelector('.area').innerHTML = `<option value="">地區(qū)</option>` + optionStr
})
/**
* 目標(biāo)2:收集數(shù)據(jù)提交保存
* 2.1 監(jiān)聽(tīng)提交的點(diǎn)擊事件
* 2.2 依靠插件收集表單數(shù)據(jù)
* 2.3 基于axios提交保存,顯示結(jié)果
*/
// 2.1 監(jiān)聽(tīng)提交的點(diǎn)擊事件
document.querySelector('.submit').addEventListener('click', async () => {
// 2.2 依靠插件收集表單數(shù)據(jù)
const form = document.querySelector('.info-form')
const data = serialize(form, { hash: true, empty: true })
console.log(data)
// 2.3 基于axios提交保存,顯示結(jié)果
try {
const result = await axios({
url: 'http://hmajax.itheima.net/api/feedback',
method: 'POST',
data
})
console.log(result)
alert(result.data.message)
} catch (error) {
console.dir(error)
alert(error.response.data.message)
}
})
到了這里,關(guān)于Ajax 筆記(四)—— Ajax 進(jìn)階的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!