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() | 所有任务完成后返回数组(包含成功和失败) |
医学、法律、商业、工程 都是崇高的追求,足以支撑人的一生。但诗歌、美丽、浪漫、爱情这些才是我们生活的意义。