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