Ajax(進階)
01-同步代碼和異步代碼
什么是同步代碼?
- 同步代碼:逐行執(zhí)行,需要原地等待結果后,才繼續(xù)向下執(zhí)行。
什么是異步代碼?
- 調(diào)用后耗時,不阻塞代碼繼續(xù)執(zhí)行,(不必原地等待),在將來完成后 觸發(fā)一個回調(diào)函數(shù)。
代碼閱讀
目標:閱讀并回答代碼執(zhí)行和打印的順序
<script> const result = 0 + 1 console.log(result) setTimeout(() => { console.log(2) }, 2000) document.querySelector('.btn').addEventListener('click', () => { console.log(3) }) document.body.style.backgroundColor = 'pink' console.log(4) </script>
打印結果為: 1,4,2,點擊按鈕一次就打印一次3.
- 注意,異步代碼都有一個特性,那就是耗時的、事件的、通過回調(diào)函數(shù)返回值。都是異步代碼。
02-回調(diào)函數(shù)地域
概念
- 在回調(diào)函數(shù)中嵌套回調(diào)函數(shù),一直嵌套下去就形成了回調(diào)函數(shù)地域。
缺點
- 可讀性差
- 異常無法捕獲
- 耦合性嚴重,牽一發(fā)動全身。
代碼示例
axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => { const pname = result.data.list[0] document.querySelector('.province').innerHTML = pname // 獲取第一個省份的第一個城市 axios({url: 'http://hmajax.itheima.net/api/city',params: {pname}}).then(result => { const cname = result.data.list[0] document.querySelector('.city').innerHTML = cname // 獲取當前城市的第一個地區(qū)名字 axios({url: 'http://hmajax.itheima.net/api/area',params: {pname,cname}}).then(result => { const area = result.data.list[0] document.querySelector('.area').innerHTML = area }) }) }).catch(error => { console.log(error) }) // 這樣一來,就進入了回調(diào)地獄了,而且錯誤無法捕獲
- 回調(diào)地域主要就是在回調(diào)函數(shù)中,不斷的使用回到函數(shù)。
03-Promise鏈式調(diào)用
概念
- 依靠then()方法 會返回一個新 生成的Promise對象特性,繼續(xù)串聯(lián)下一環(huán)任務,知道結束。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CI9Ao6Nx-1691654693290)(D:\桌面\筆記\Ajax筆記\my-note\Ajax04\images\2023-08-07_22-29-32.png)]
細節(jié)
- then()方法里面的回調(diào)函數(shù)中的返回值,會影響新生成的Promise對象最終狀態(tài)和結果。
好處
- 通過鏈式通用,解決回調(diào)函數(shù)嵌套問題。
需求:把省市的嵌套結構,改成鏈式調(diào)用的線性結構
代碼示例
const p = new Promise((resolve, reject) => { setTimeout(() => { resolve('北京市') }, 2000) }) // 2、獲取省份的名字 const p2 = p.then(result => { console.log(result) // 3、創(chuàng)建Promise對象,獲取城市名字 return new Promise((resolve,reject) => { setTimeout(() => { resolve(result + '---北京') },2000) }) }) // 4、獲取城市名字 p2.then(result => { console.log(result) }) // then函數(shù)原地的結果是一個新的Promise對象 console.log(p2 === p) //地址不一樣 </script>
04-Promise鏈式應用
目標:使用Promise解決鏈式調(diào)用,解決回調(diào)函數(shù)地域的問題。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YV7efGq0-1691654693291)(D:\桌面\筆記\Ajax筆記\my-note\Ajax04\images\2023-08-07_23-02-04.png)]
實現(xiàn)方式:每個Promise對象中管理一個一步任務,用then返回Promise對象,串聯(lián)起來。
代碼示例
需求:獲取默認第一個省,第一個市,第一個地區(qū)并展示在下拉菜單中
let pname = '' // 1、得到 - 獲取省份Promise對象 axios({ url: 'http://hmajax.itheima.net/api/province' }).then(result => { pname = result.data.list[0] document.querySelector('.province').innerHTML = pname // 2.得到 - 獲取城市Promise對象 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對象 return axios({url: 'http://hmajax.itheima.net/api/area',params:{pname, cname}}) }).then(result => { const area = result.data.list[0] document.querySelector('.area').innerHTML = area }) </script>
05-async函數(shù)和await
##### 有什么用呢?
- async和await關鍵字讓我們可以用一種更簡潔的方式寫出基于 Promise的一異步行為,而無需刻意的鏈式調(diào)用Promise.
- 解決回調(diào)函數(shù)地獄
概念
- 在async函數(shù)內(nèi),使用await關鍵字去掉then函數(shù),等待獲取Promise對象成功狀態(tài)的結果值。
- await必須用在async修飾的函數(shù)內(nèi)(await會阻止"異步函數(shù)內(nèi)"代碼繼續(xù)執(zhí)行,原地等待結果)
- await === Promise對象返回的成功的結果值 (resolve的值)
代碼示例
// 1.定義一個async修飾的函數(shù) async function getData() { // Promise對象的返回成功的結果被await接收。 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 area = aObj.data.list[0] document.querySelector('.province').innerHTML = pname document.querySelector('.city').innerHTML = cname document.querySelector('.area').innerHTML = area } getData()
awwit會接受Promise對象返回成功的值。
06-async和await-捕獲錯誤
- 使用try catch語句標記需要嘗試的語句塊,并指定一個出現(xiàn)異常時拋出的響應。
- 如果try里某一行代碼報錯后,try中剩余的代碼不會執(zhí)行了。
- catch塊,接受錯誤信息,返回的形參可以查看詳細信息。
語法
try { //要執(zhí)行的代碼 } catch (error) { //error接收的是,錯誤信息 //try里的代碼,如果有錯誤,直接進入這里執(zhí)行 }
07-事件循環(huán)(EventLoop)
同步放在執(zhí)行棧,異步被宿主環(huán)境讀取,放在任務隊列中
由于JavaScript是單線程的,(某一刻只能 執(zhí)行一行代碼),為了讓耗時代碼不阻塞其他代碼運行,設計了事件循環(huán)模型。
什么是事件循環(huán)
- 在執(zhí)行代碼和收集異步任務時,在調(diào)用??臻e時候,反復調(diào)用任務隊列里的代碼執(zhí)行,就叫做事件循環(huán)。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JLX51V2x-1691654693291)(D:\桌面\筆記\Ajax筆記\my-note\Ajax04\images\2023-08-08_17-19-44.png)]
在JavaScript中,數(shù)據(jù)類型有分為簡單數(shù)據(jù)類型和引用數(shù)據(jù)類型,當我們js引擎識別到
- 簡單數(shù)據(jù)類型 放在??臻g存儲值(因為簡單數(shù)據(jù)類型沒有地址)
- 引用數(shù)據(jù)類型(有地址存儲在內(nèi)存中),所以,引用數(shù)據(jù)類型地址放在棧空間,值放在堆空間,我們程序員能支配棧空間,堆空間由系統(tǒng)來支配,所以深淺考拷貝的問題就在于地址和值的問題,對于此,我們程序員只能操作引用數(shù)據(jù)類型的地址,而值,不能直接去改變,會產(chǎn)生很多問題。
事件循環(huán):就是js執(zhí)行機制分配好存儲空間,來決定執(zhí)行代碼
- 當代碼識別為簡單數(shù)據(jù)類型:則將代碼放在執(zhí)行棧中
- 當代碼被識別為引用數(shù)據(jù)類型(復雜數(shù)據(jù)類型),則將代碼放在任務隊列中
- 當執(zhí)行棧中的代碼被執(zhí)行完畢后,那么js執(zhí)行機制會向任務隊列中讀取里面的任務代碼,根據(jù)所需要執(zhí)行的事件進行取出任務到執(zhí)行棧中運行代碼。執(zhí)行完畢后再次讀取任務隊列中是否還有任務需要執(zhí)行,如果還有任務,那么繼續(xù)取出來運行,多次重復的讀取操作,就被成為事件循環(huán)。
代碼體驗
// 體驗JS是單線程的,必須先遍歷完,才能改變顏色
document.querySelector('.time-btn').addEventListener('click', () => {
for (let i = 0; i < 300000; i++) {
console.log(i)
}
document.body.style.background = 'pink'
})
// 為了避免線程卡死,出現(xiàn)了異步代碼,把耗時操作放到異步中,先保證主線程執(zhí)行完所有同步代碼
document.querySelector('.time-btn').addEventListener('click', () => {
setTimeout(() => {
for (let i = 0; i < 300000; i++) {
console.log(i)
}
}, 0)
document.body.style.background = 'pink'
})
<script>
console.log(1)
setTimeout(() =>{
console.log(2)
},2000)
console.log(3)
//打印順序:1,3,2
console.log(1)
setTimeout(() => {
console.log(2)
},0)
console.log(3)
//打印順序還是: 1,3,2 (因為setTimeout是引用數(shù)據(jù)類型,放到任務隊列中等待執(zhí)行棧中的同步任務執(zhí)行完,在讀取任務隊列中的代碼執(zhí)行,并且一次只能執(zhí)行一個,多個的話,就重復,這樣就形成了事件循環(huán))
</script>
練習
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()
// 答1,5,3,2,4,6(不點擊事件不執(zhí)行)
08-宏任務與微任務
ES6之后引入了Promise對象,讓JS引擎也可以發(fā)起異步任務。
異步任務分為;
宏任務:由瀏覽器環(huán)境執(zhí)行的異步代碼。
微任務:由JS引擎環(huán)境執(zhí)行的代碼
執(zhí)行順序:執(zhí)行??臻e時,宏任務只有等到所有的微任務執(zhí)行完畢后才會到宏任務中讀取任務。
任務(代碼) | 執(zhí)行所在的環(huán)境 |
---|---|
JS腳本執(zhí)行事件(script) | 瀏覽器 |
setTimeout/setlnterval | 瀏覽器 |
Ajax請求完成事件 | 瀏覽器 |
用戶交互事件 | 瀏覽器 |
Promise本身是同步代碼,而then和catch回調(diào)函數(shù)是異步的。放在微任務執(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 4 3 2
console.log(1)
setTimeout(() => {
console.log(2)
},0)
const p = new Promise((resolve,reject) => {
console.log(3)
resolve(4)
})
p.then(result => {
console.log(result)
})
console.log(5)
- 執(zhí)行順序為: 1 3 5 4 2
09-經(jīng)典面試題(事件循環(huán))
// 目標:回答代碼執(zhí)行順序
console.log(1)
setTimeout(() => {
console.log(2)
const p = new Promise(resolve => resolve(3))
p.then(result => console.log(result))
}, 0)
const p = new Promise(resolve => {
setTimeout(() => {
console.log(4)
}, 0)
resolve(5)
})
p.then(result => console.log(result))
const p2 = new Promise(resolve => resolve(6))
p2.then(result => console.log(result))
console.log(7)
// 1 7 5 6 2 3 4
10-Promise.all靜態(tài)方法
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CpSvq3KJ-1691654693291)(D:\桌面\筆記\Ajax筆記\my-note\Ajax04\images\2023-08-10_09-11-37.png)]
需要同時結果的時候,就是用Promise.all即可。
概念: 合并多個Promise對象,等待所有同時成功完成(或某一個失?。龊罄m(xù)邏輯
語法
const p = Promise.all([Promise對象,Promise對象,....])
p.then(result => {
//result結果:[Promise對象成功的結果,Promise對象成功的結果,...]
}).catch(error => {
//第一個失敗的Promise對象,拋出的異常
})
代碼示例
// 1. 請求城市天氣,得到Promise對象
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,合并多個Promise對象
const p = Promise.all([bjPromise, shPromise, gzPromise, szPromise])
p.then(result => {
// 注意:結果數(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)
})
11-商品分類案例
核心步驟:
* 1. 獲取所有一級分類數(shù)據(jù)
* 2. 遍歷id,創(chuàng)建獲取二級分類請求
* 3. 合并所有二級分類Promise對象
* 4. 等待同時成功后,渲染頁面
// 1. 獲取所有一級分類數(shù)據(jù)
axios({ url: 'http://hmajax.itheima.net/api/category/top' }).then(result => {
console.log(result)
//2. 遍歷id,創(chuàng)建獲取二級分類請求
const list = result.data.data.map(item => {
return axios({
url: 'http://hmajax.itheima.net/api/category/sub',
params: {
id: item.id
}
})
})
console.log(list)
//3. 合并所有二級分類Promise對象 Promise 對象就是所有axios函數(shù)的返回值
const p = Promise.all(list)
p.then(result => {
console.log(result)
// 4. 等待同時成功后,渲染頁面
const htmlStr = result.map(item => {
// 取出關鍵的數(shù)據(jù)對象
const dataObj = item.data.data
return `<div class="item">
<h3>分類名字</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
})
})
12-學習反饋案例
* 目標1:完成省市區(qū)下拉列表聯(lián)動切換
* 1.1 設置省份下拉菜單數(shù)據(jù)
* 1.2 切換省份,設置城市下拉菜單數(shù)據(jù),清空地區(qū)下拉菜單
* 1.3 切換城市,設置地區(qū)下拉菜單數(shù)據(jù)
代碼示例
// 1.1 設置省份下拉菜單數(shù)據(jù)
axios({
url: 'http://hmajax.itheima.net/api/province'
}).then(result => {
console.log(result)
// 數(shù)組映射
const optionStr = result.data.list.map(pname => `<option value="${pname}">${pname}</option>`).join()
document.querySelector('.province').innerHTML = `<option value="">省份</option>` + optionStr
})
// 1.2 切換省份,設置城市下拉菜單數(shù)據(jù),清空地區(qū)下拉菜單
document.querySelector('.province').addEventListener('change', async e => {
// 立馬獲取到用戶選擇的省份名字
// 拿到省份數(shù)據(jù)
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('')
// 把默認的城市選項 + 下屬城市數(shù)據(jù)插入到select中
document.querySelector('.city').innerHTML = `<option value="">城市</option>` + optionStr
// 清空地區(qū)數(shù)據(jù)
document.querySelector('.area').innerHTML = `<option value="">地區(qū)</option>`
})
// 1.3 切換城市,設置地區(qū)下拉菜單數(shù)據(jù)
document.querySelector('.city').addEventListener('change', async e => {
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('')
document.querySelector('.area').innerHTML = `<option value="">地區(qū)</option>` + optionStr
})
// 省市區(qū)聯(lián)動效果。2023年8月10日15:36:15
提交表單
* 目標2: 收集數(shù)據(jù)提交保存
* 2.1 監(jiān)聽提交的點擊事件
* 2.2 依靠插件收集表單元素文章來源:http://www.zghlxwxcb.cn/news/detail-640053.html
* 2.3 基于axios提交緩存,顯示結果文章來源地址http://www.zghlxwxcb.cn/news/detail-640053.html
代碼示例
// 2.1 監(jiān)聽提交的點擊事件
document.querySelector('.submit').addEventListener('click', async () => {
// 2.2 依靠插件收集表單元素
const form = document.querySelector('.info-form')
const data = serialize(form, { hash: true, empty: true })
console.log(data)
// 2.3 基于axios提交緩存,顯示結果
try {
const result = await axios({
url: 'http://hmajax.itheima.net/api/feedback',
method: 'POST',
data//因為接口文檔參數(shù)名和返回的結果名相同,直接傳入data,data配置對象屬性名和屬性值相同,則簡寫。
})
console.log(result)
alert(result.data.message)
} catch(error) {
console.dir(error)
alert(error.response.data.message)
}
})
- serialize插件的使用獲取表單
- async和await修飾函數(shù)和獲取axios異步響應返回的結果。
- 使用try catch(error)來拋出異常,如果響應結果出現(xiàn)問題,那么就返回錯誤信息。
到了這里,關于Ajax_4(進階)同步異步+ 宏任務微任務 + Promise鏈 + async終極解決方案 +事件循環(huán)原理 + 綜合案例的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!