XMLHttpRequest
?
XMLHttpRequest
(簡稱?XHR)是瀏覽器提供的一個 JavaScript 對象,用于在客戶端和服務器之間發送 HTTP 請求。它是實現?AJAX(Asynchronous JavaScript and XML)?技術的核心工具,允許網頁在不重新加載的情況下與服務器交換數據并更新部分頁面內容。。
1. 基本用法
創建 XHR 對象
const xhr = new XMLHttpRequest();
配置請求
使用?open()
?方法初始化請求:
xhr.open(method, url, async);
-
method
:HTTP 方法(如?GET
、POST
)。 -
url
:請求的目標 URL。 -
async
:是否異步(默認為?true
)。
示例
xhr.open('GET', 'https://api.example.com/data', true);
發送請求
使用?send()
?方法發送請求:
xhr.send();
-
對于?
POST
?請求,可以在?send()
?中傳遞請求體數據:xhr.send(JSON.stringify({ key: 'value' }));
處理響應
通過?onreadystatechange
?事件監聽請求狀態變化:
xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {console.log('響應數據:', xhr.responseText);}
};
2. XHR 對象的屬性和方法
屬性
屬性 | 說明 |
---|---|
readyState | 請求狀態: - 0:未初始化 - 1:已打開 - 2:已發送 - 3:接收中 - 4:完成 |
status | HTTP 狀態碼(如 200、404、500)。 |
statusText | HTTP 狀態文本(如?OK 、Not Found )。 |
responseText | 服務器返回的文本數據(字符串形式)。 |
responseXML | 服務器返回的 XML 數據(如果響應內容是?text/xml )。 |
responseType | 設置響應類型(如?json 、text 、blob )。 |
response | 根據?responseType ?返回的響應數據。 |
方法
方法 | 說明 |
---|---|
open(method, url) | 初始化請求。 |
send(body) | 發送請求,body ?是可選的請求體數據。 |
setRequestHeader(header, value) | 設置請求頭(如?Content-Type )。 |
abort() | 取消請求。 |
3. 完整示例
GET 請求
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {console.log('響應數據:', xhr.responseText);}
};
xhr.send();
POST 請求
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/submit', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {console.log('響應數據:', xhr.responseText);}
};
xhr.send(JSON.stringify({ username: 'john', password: '123456' }));
4. 事件監聽
除了?onreadystatechange
,XHR 還支持以下事件:
事件 | 說明 |
---|---|
onload | 請求成功完成時觸發。 |
onerror | 請求失敗時觸發。 |
onprogress | 請求正在處理中時觸發(用于監控上傳/下載進度)。 |
示例
xhr.onload = function() {if (xhr.status === 200) {console.log('響應數據:', xhr.responseText);}
};
xhr.onerror = function() {console.error('請求失敗');
};
xhr.onprogress = function(event) {if (event.lengthComputable) {console.log(`已接收 ${event.loaded} / ${event.total} 字節`);}
};
5. 跨域請求
XHR 默認受?同源策略?限制,無法直接訪問不同源的資源。可以通過以下方式解決跨域問題:
-
CORS(跨域資源共享):
服務器設置?Access-Control-Allow-Origin
?響應頭。 -
JSONP:
通過?<script>
?標簽加載跨域數據(僅支持 GET 請求)。 -
代理服務器:
通過同源服務器轉發請求。
6. 與 Fetch API 的對比
特性 | XMLHttpRequest | Fetch API |
---|---|---|
語法 | 基于回調,代碼冗長 | 基于 Promise,語法簡潔 |
流式數據處理 | 不支持 | 支持 |
請求取消 | 支持(abort() ) | 支持(AbortController ) |
兼容性 | 支持所有瀏覽器(包括 IE) | 不支持 IE |
錯誤處理 | 需手動檢查狀態碼 | 自動處理網絡錯誤 |
7.查詢參數
-
什么是查詢參數:攜帶額外信息給服務器,返回匹配想要的數據
-
查詢參數原理要攜帶的位置和語法:http://xxxx.com/xxx/xxx?參數名1=值1&參數名2=值2
-
所以,原生 XHR 需要自己在 url 后面攜帶查詢參數字符串,沒有 axios 幫助我們把 params 參數拼接到 url 字符串后面了
特性 | XMLHttpRequest | Axios |
---|---|---|
語法 | 需要手動拼接 URL 或使用?URLSearchParams | 提供?params ?配置項,自動處理查詢參數 |
代碼復雜度 | 代碼冗長,需手動處理細節 | 代碼簡潔,封裝了底層細節 |
錯誤處理 | 需手動檢查狀態碼和錯誤 | 基于 Promise,支持?catch ?捕獲錯誤 |
兼容性 | 支持所有瀏覽器(包括 IE) | 不支持 IE |
功能擴展 | 功能有限,需自行封裝 | 提供攔截器、請求取消等高級功能 |
Promise
1. 什么是 Promise?
Promise
是 JavaScript 提供的一種用于 處理異步操作 的對象。它用于解決 回調地獄 問題,使異步代碼更易讀、可維護。
Promise 對象表示一個 尚未完成但預計會在未來完成的操作,它可以是:
- 進行中(pending):初始狀態,既沒有成功也沒有失敗。
- 已完成(fulfilled):操作成功,返回一個值(由
resolve()
處理)。 - 已拒絕(rejected):操作失敗,返回一個錯誤(由
reject()
處理)。
2. Promise 語法
const myPromise = new Promise((resolve, reject) => {setTimeout(() => {let success = true; // 模擬成功或失敗if (success) {resolve("操作成功!");} else {reject("操作失敗!");}}, 1000);
});// 處理 Promise 結果
myPromise.then((result) => console.log(result)) // 操作成功!.catch((error) => console.error(error)) // 操作失敗!.finally(() => console.log("操作完成")); // 無論成功或失敗都會執行
3. Promise 鏈式調用
支持多個 .then()
串聯執行,避免回調地獄
new Promise((resolve) => resolve(1)).then((num) => num * 2).then((num) => num * 3).then(console.log); // 6
4. 常用 Promise 方法
方法 | 作用 |
---|---|
Promise.all() | 同時執行多個 Promise,全部成功返回數組,否則返回第一個失敗的 |
Promise.race() | 返回第一個完成的 Promise(無論成功或失敗) |
Promise.allSettled() | 等待所有 Promise 結束,無論成功或失敗,返回所有結果 |
Promise.any() | 返回第一個成功的 Promise,如果全部失敗,則返回 AggregateError |
Promise.all([Promise.resolve("A"),Promise.resolve("B"),
]).then(console.log); // ["A", "B"]
5. async/await
(Promise 的語法糖)
async function fetchData() {try {let data = await new Promise((resolve) => setTimeout(() => resolve("數據返回"), 1000));console.log(data); // 數據返回} catch (error) {console.error(error);}
}fetchData();
6.myAxios制作
基于 Promise 和 XHR 封裝 myAxios 函數
核心語法:
function myAxios(config) {return new Promise((resolve, reject) => {// XHR 請求// 調用成功/失敗的處理程序})
}myAxios({url: '目標資源地址'
}).then(result => {}).catch(error => {})
步驟:
?
-
定義 myAxios 函數,接收配置對象,返回 Promise 對象
-
發起 XHR 請求,默認請求方法為 GET
-
調用成功/失敗的處理程序
-
使用 myAxios 函數,獲取省份列表展示
整體代碼:
function myAxios({ url, method = "GET", data = null, headers = {}, timeout = 5000 }) {return new Promise((resolve, reject) => {const xhr = new XMLHttpRequest();xhr.open(method.toUpperCase(), url, true);xhr.timeout = timeout; // 設置超時時間// 設置請求頭for (let key in headers) {xhr.setRequestHeader(key, headers[key]);}// 監聽請求完成事件xhr.onreadystatechange = function () {if (xhr.readyState === 4) {if (xhr.status >= 200 && xhr.status < 300) {try {const responseData = JSON.parse(xhr.responseText); // 解析 JSON 數據resolve(responseData);} catch (error) {reject("JSON 解析失敗");}} else {reject(`請求失敗,狀態碼:${xhr.status}`);}}};// 監聽錯誤xhr.onerror = function () {reject("網絡錯誤");};// 監聽超時xhr.ontimeout = function () {reject("請求超時");};// 發送請求if (method.toUpperCase() === "GET" || data === null) {xhr.send();} else {xhr.setRequestHeader("Content-Type", "application/json");xhr.send(JSON.stringify(data));}});
}
同步代碼和異步代碼的區別
1. 同步代碼(Synchronous)
同步代碼按 順序執行,當前任務完成后才執行下一個任務。如果某個任務耗時較長(如文件讀取、網絡請求等),整個程序都會被 阻塞。
示例:
console.log("任務 1");
console.log("任務 2");
console.log("任務 3");
? 執行順序:
任務 1
任務 2
任務 3
遇到阻塞的情況
console.log("開始");
for (let i = 0; i < 1e9; i++) {} // 模擬耗時操作
console.log("結束");
如果 for
循環運行了 3 秒,那么程序會 卡 3 秒,直到循環結束才繼續執行后面的代碼。
2. 異步代碼(Asynchronous)
異步代碼不會阻塞程序,它 不會等待任務完成,而是繼續執行后續代碼,等任務完成后再 通知(回調、Promise、async/await)。
示例(setTimeout 異步執行):
console.log("任務 1");
setTimeout(() => console.log("任務 2(延遲 1 秒)"), 1000);
console.log("任務 3");
? 執行順序:
任務 1
任務 3
任務 2(延遲 1 秒后執行)
👉 異步任務(setTimeout)不會阻塞后續代碼的執行。
3. 常見的異步操作
- 定時器(setTimeout, setInterval)
- DOM 事件(click, input, scroll)
- 網絡請求(fetch, XMLHttpRequest, axios)
- 文件讀取(fs.readFile - Node.js)
- 數據庫操作(MongoDB, MySQL)
回調函數(Callback)
function fetchData(callback) {setTimeout(() => {callback("數據加載完成");}, 1000);
}console.log("開始請求數據");
fetchData((data) => console.log(data));
console.log("代碼執行完畢");
? 執行順序
開始請求數據
代碼執行完畢
數據加載完成(1 秒后)
4. 現代異步方案
(1)Promise
開始請求數據
代碼執行完畢
數據加載完成(1 秒后)
? 執行順序
開始請求數據
代碼執行完畢
數據加載完成(1 秒后)
(2)async/await
async function fetchData() {return new Promise((resolve) => {setTimeout(() => resolve("數據加載完成"), 1000);});
}async function main() {console.log("開始請求數據");let data = await fetchData();console.log(data);console.log("代碼執行完畢");
}main();
? 執行順序
開始請求數據
數據加載完成(1 秒后)
代碼執行完畢
👉 await
讓異步代碼看起來像同步代碼,增強可讀性。
5. 總結
對比項 | 同步代碼 | 異步代碼 |
---|---|---|
執行方式 | 按順序執行 | 先執行后續代碼,任務完成后再執行回調 |
是否阻塞 | 是(遇到耗時任務會卡住) | 否(不會影響后續代碼執行) |
使用場景 | 計算、變量賦值、DOM 操作等 | I/O 操作、網絡請求、定時任務等 |
實現方式 | 普通代碼 | 回調函數、Promise、async/await |
Promise 鏈式調用
Promise 的 .then()
方法會返回一個新的 Promise
,這樣就可以鏈式調用多個 .then()
,實現 異步流程控制,避免回調地獄。
1. 基本鏈式調用
每個 .then()
處理上一個 .then()
返回的值:
new Promise((resolve) => {setTimeout(() => resolve(1), 1000); // 1秒后返回 1
}).then((num) => {console.log(num); // 1return num * 2;}).then((num) => {console.log(num); // 2return num * 3;}).then((num) => {console.log(num); // 6return num * 4;}).then(console.log); // 24
?? 執行順序
1
2
6
24
👉 每個 .then()
2. 鏈式調用處理異步操作
如果 .then()
返回一個 Promise
,下一個 .then()
會等待這個 Promise
解析完成:
都返回一個新的值,供下一個 .then()
使用。
new Promise((resolve) => {setTimeout(() => resolve("數據 1"), 1000);
}).then((data) => {console.log(data);return new Promise((resolve) => setTimeout(() => resolve("數據 2"), 1000));}).then((data) => {console.log(data);return new Promise((resolve) => setTimeout(() => resolve("數據 3"), 1000));}).then(console.log);
? 執行順序(每步間隔 1 秒)
數據 1
數據 2
數據 3
👉 每個 .then()
返回一個 Promise
,下一個 .then()
需等待前一個 Promise
解析完成
3. 處理異常
鏈式調用中,catch()
捕獲前面所有 Promise
的錯誤:
new Promise((resolve, reject) => {setTimeout(() => reject("出錯了!"), 1000);
}).then((data) => {console.log("不會執行", data);return "繼續";}).catch((error) => {console.error("捕獲錯誤:", error);return "錯誤已處理"; // catch() 可以返回新值}).then(console.log); // "錯誤已處理"
? 執行順序
捕獲錯誤: 出錯了!
錯誤已處理
👉 catch()
捕獲錯誤后,鏈式調用不會中斷,后續 .then()
仍會執行。
4. finally()
無論 Promise
成功或失敗,finally()
都會執行:
new Promise((resolve, reject) => {setTimeout(() => reject("失敗了"), 1000);
}).then(console.log).catch(console.error).finally(() => console.log("操作完成"));
?? 執行順序
失敗了
操作完成
👉 finally()
適合做清理操作,比如關閉加載動畫等。
方法 | 作用 |
---|
.then() | 處理成功結果,可鏈式調用 |
.catch() | 處理 Promise 失敗(捕獲錯誤) |
.finally() | 無論成功或失敗都會執行 |
JavaScript 事件循環(Event Loop)
1. 什么是事件循環?
JavaScript 是 單線程 的,它使用 事件循環(Event Loop) 機制來執行同步和異步代碼,確保不會阻塞主線程。
- 同步任務:立即執行,放入 主線程(調用棧 Call Stack) 運行。
- 異步任務:放入 任務隊列(Task Queue),等主線程空閑后執行。
2. 事件循環執行流程
- 執行同步代碼(放在主線程)。
- 遇到異步任務(如
setTimeout
、Promise
、fetch
),將它交給 Web APIs 處理。 - 同步代碼執行完畢后,檢查任務隊列:
- 微任務隊列(Microtask Queue):執行
Promise.then()
、queueMicrotask()
、MutationObserver
。 - 宏任務隊列(Macrotask Queue):執行
setTimeout
、setInterval
、setImmediate
(Node.js)、I/O
任務等。
- 微任務隊列(Microtask Queue):執行
- 先執行所有微任務,再執行宏任務,然后進入下一個循環。
3. 示例解析
示例 1:基本事件循環
console.log("同步 1");setTimeout(() => console.log("setTimeout"), 0);Promise.resolve().then(() => console.log("Promise 1"));console.log("同步 2");
? 執行順序
同步 1
同步 2
Promise 1 (微任務)
setTimeout (宏任務)
📌 解釋
- 同步 1、同步 2:立即執行。
- Promise 是 微任務,優先執行。
- setTimeout 是 宏任務,等微任務執行完才運行。
示例 2:多個宏任務和微任務
console.log("同步 1");setTimeout(() => console.log("setTimeout 1"), 0);
setTimeout(() => console.log("setTimeout 2"), 0);Promise.resolve().then(() => {console.log("Promise 1");return Promise.resolve();
}).then(() => console.log("Promise 2"));console.log("同步 2");
? 執行順序
同步 1
同步 2
Promise 1 (微任務)
Promise 2 (微任務)
setTimeout 1 (宏任務)
setTimeout 2 (宏任務)
📌 解釋
- 同步代碼 先執行("同步 1" → "同步 2")。
- Promise 微任務 依次執行("Promise 1" → "Promise 2")。
- setTimeout 宏任務 最后執行("setTimeout 1" → "setTimeout 2")。
示例 3:復雜情況
console.log("A");setTimeout(() => {console.log("B");Promise.resolve().then(() => console.log("C"));
}, 0);Promise.resolve().then(() => console.log("D"));console.log("E");
? 執行順序
A
E
D (微任務)
B (宏任務)
C (B 里的微任務)
📌 解釋
- 執行同步代碼:"A"、"E"。
- Promise 微任務:"D" 先執行。
- setTimeout 宏任務:"B" 進入隊列,等同步 & 微任務執行完畢后運行。
- "B" 里面的 Promise(C) 是 微任務,執行優先級高于新的宏任務。
Promise.all() 靜態方法
Promise.all()
用于 并行執行多個異步任務,并且 等待所有 Promise 都成功,才會返回 所有結果。如果 任何一個 Promise 失敗,Promise.all()
立即 reject,不會等待其他任務完成。
1. 基本用法
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);Promise.all([p1, p2, p3]).then((results) => {console.log(results); // [1, 2, 3]
});
? 執行順序
[1, 2, 3]
📌 解釋
Promise.all()
需要 等待所有 Promise 解析,返回[1, 2, 3]
。
2. 異步任務示例
const p1 = new Promise((resolve) => setTimeout(() => resolve("數據 1"), 1000));
const p2 = new Promise((resolve) => setTimeout(() => resolve("數據 2"), 2000));
const p3 = new Promise((resolve) => setTimeout(() => resolve("數據 3"), 3000));console.log("開始請求");Promise.all([p1, p2, p3]).then((results) => {console.log("所有數據加載完成:", results);
});
? 執行順序
開始請求
(等待 3 秒)
所有數據加載完成: [ '數據 1', '數據 2', '數據 3' ]
📌 解釋
Promise.all()
等待所有任務完成(最大耗時任務 3 秒)。- 3 秒后,返回
["數據 1", "數據 2", "數據 3"]
。
3. 處理失敗(任何一個失敗都會觸發 reject)
const p1 = Promise.resolve("成功 1");
const p2 = Promise.reject("失敗 2");
const p3 = Promise.resolve("成功 3");Promise.all([p1, p2, p3]).then(console.log).catch((error) => console.error("發生錯誤:", error));
? 執行順序
發生錯誤: 失敗 2
📌 解釋
p2
失敗,Promise.all
立即reject
,不會等待p3
。
4. 結合 map()
批量請求
📌 批量請求 5 個 API
const urls = ["https://jsonplaceholder.typicode.com/todos/1","https://jsonplaceholder.typicode.com/todos/2","https://jsonplaceholder.typicode.com/todos/3",
];Promise.all(urls.map((url) => fetch(url).then((res) => res.json()))).then(console.log).catch(console.error);
? 執行結果
[{ "userId": 1, "id": 1, "title": "...", "completed": false },{ "userId": 1, "id": 2, "title": "...", "completed": false },{ "userId": 1, "id": 3, "title": "...", "completed": false }
]
📌 解釋
map()
遍歷 URL 數組,創建fetch()
請求。Promise.all()
并行執行所有請求,加速響應。
5. 解決 Promise.all()
失敗問題
如果希望即使某些 Promise 失敗,仍然獲得結果,可以使用 Promise.allSettled()
。
const p1 = Promise.resolve("成功 1");
const p2 = Promise.reject("失敗 2");
const p3 = Promise.resolve("成功 3");Promise.allSettled([p1, p2, p3]).then(console.log);
? 執行結果
[{ "status": "fulfilled", "value": "成功 1" },{ "status": "rejected", "reason": "失敗 2" },{ "status": "fulfilled", "value": "成功 3" }
]
📌 解釋
Promise.allSettled()
不會因為某個 Promise 失敗而中斷。- 返回 每個 Promise 的狀態(fulfilled / rejected)。
總結
方法 | 作用 |
---|---|
Promise.all() | 全部成功才返回數組,任意失敗立即 reject |
Promise.allSettled() | 所有任務完成后返回數組(包含成功和失敗) |
?
醫學、法律、商業、工程 都是崇高的追求,足以支撐人的一生。但詩歌、美麗、浪漫、愛情這些才是我們生活的意義。