前端新人别慌:手写原生AJAX请求,彻底搞懂它到底在干啥(附避坑指南)
- 前端新人别慌:手写原生AJAX请求,彻底搞懂它到底在干啥(附避坑指南)
- AJAX这名字到底啥意思?跟后端接口啥关系?
- XMLHttpRequest:上古神器还是真香工具?
- 四行代码搞定一个请求?先别急着抄
- open、send、onload 到底谁先谁后?
- 同步请求:千万别在主线程玩这个!
- readyState的五个阶段:你的请求卡在哪了?
- status 200就万事大吉?太天真了!
- responseText vs response:别用混了
- JSON.parse()翻车现场:后端返回的不一定是JSON
- 跨域:前端根本解决不了,但你可以这样调试
- 封装一个能用的AJAX函数:带超时、自动解析、错误处理
- 取消请求:AbortController真香警告
- 请求发不出去?自查清单别急着甩锅后端
- 几个骚但实用的技巧
- 最后说句掏心窝子的
前端新人别慌:手写原生AJAX请求,彻底搞懂它到底在干啥(附避坑指南)
说实话,我刚入行那会儿,AJAX这个词听着就挺唬人的。什么"Asynchronous JavaScript and XML",念都念不顺溜,感觉像是某种黑魔法。那时候我天天用jQuery的$.ajax,几行代码数据就来了,爽得很。直到有一天面试官问我:"如果不用任何库,你怎么发请求?“我当场就懵了,脑子里一片空白,支支吾吾半天憋出一句"用fetch?”——然后就没有然后了。
所以如果你现在也是这个状态,别慌,太正常了。这篇文章咱们就彻底扒开AJAX的底裤,看看它到底在搞什么飞机。不用axios,不用fetch,就拿最原始的XMLHttpRequest,从零手写一遍。等你搞懂这些,再去用那些封装好的库,你会发现自己看问题的角度完全不一样了。
AJAX这名字到底啥意思?跟后端接口啥关系?
先说说这个拗口的名字。AJAX全称是"Asynchronous JavaScript and XML",翻译过来就是"异步的JavaScript和XML"。这名字现在看其实有点尴尬,因为:
- 异步这部分是对的,AJAX确实是异步操作
- XML这部分就扯淡了,现在谁还用XML传数据啊,不都是JSON吗
所以你可以这么理解:AJAX就是一种在不刷新整个页面的情况下,跟服务器偷偷摸摸交换数据的技术。注意这个"偷偷摸摸",用户是无感知的,页面不会白屏一下,数据就悄悄来了。
那它跟后端接口啥关系?简单说,AJAX是前端手里的"电话",后端接口是"电话号码"。你拿着电话(AJAX)拨号(发请求),对方(后端)接电话返回数据。没有电话你打不了,没有号码你也不知道打给谁。
这里有个误区要纠正:很多人以为AJAX是某个具体的函数或者库,其实不是。AJAX是一种技术方案,不是某个具体的工具。你用XMLHttpRequest实现叫AJAX,用fetch实现也叫AJAX,甚至早期用iframe偷偷加载数据也算AJAX。所以下次面试官问"什么是AJAX",别背定义,说"一种异步通信方案"就到位了。
XMLHttpRequest:上古神器还是真香工具?
现在提到XMLHttpRequest(以下简称XHR),很多新人一脸嫌弃:"都2026年了,还用这老古董?"确实,fetch API更现代、Promise-based、代码更简洁。但你要是因为这个就不学XHR,那真的是丢了西瓜捡芝麻。
为啥?因为所有现代HTTP库底层都是XHR。axios、superagent、甚至fetch的polyfill,底层全是XHR。你不理解XHR,就像开车不懂发动机原理——平时没事,一旦抛锚你就傻眼。
而且XHR有些特性是fetch目前还不太好替代的:
- 进度监控(上传下载进度条)
- 同步请求(虽然不建议用,但某些场景需要)
- 请求取消(fetch直到最近才支持AbortController)
所以咱们今天就拿XHR开刀,看看这"老古董"到底香不香。
四行代码搞定一个请求?先别急着抄
网上很多教程说"四行代码搞定AJAX",确实能跑,但那种写法生产环境用就是找死。咱们先看看最简版本长啥样,然后再慢慢加料。
// 最简版本——看看就行,别真这么写
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');
xhr.onload = () => console.log(xhr.responseText);
xhr.send();
这四行确实能发请求,但问题太多了:
- 出错怎么办?没处理
- 状态码判断呢?没有
- 请求头设置呢?也没有
- 超时控制?想多了
所以咱们得把这四行扩展成能用在项目里的版本。别急,一步一步来。
open、send、onload 到底谁先谁后?
XHR的工作流程其实挺像点外卖的:
- new XMLHttpRequest() = 拿出手机
- open() = 打开外卖APP,选好商家(配置请求参数)
- setRequestHeader() = 备注"不要香菜"(设置请求头)
- send() = 点击下单(真正发送请求)
- onload/onerror = 外卖到了/骑手迷路了(回调处理)
顺序绝对不能乱!我见过新手写成这样:
// 错误示范!别这么写
const xhr = new XMLHttpRequest();
xhr.send(); // 还没open就send?报错!
xhr.open('GET', 'https://api.example.com/data');
正确的顺序必须是:open → 设置请求头 → send → 等待回调。
const xhr = new XMLHttpRequest();
// 第一步:open,初始化请求
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);
// 第二步:设置请求头(如果需要)
xhr.setRequestHeader('Content-Type', 'application/json');
// 第三步:send,发送请求
// GET请求没有body,传null;POST请求这里传数据
xhr.send(null);
// 第四步:监听状态变化
xhr.onload = function() {
if (xhr.status === 200) {
console.log('成功:', JSON.parse(xhr.responseText));
} else {
console.error('服务器返回错误:', xhr.statusText);
}
};
xhr.onerror = function() {
console.error('网络请求失败,检查网络连接');
};
看到那个open的第三个参数true了吗?那是异步开关。如果写成false,就变成同步请求了。千万别这么干!下面咱们专门说说同步请求为啥是洪水猛兽。
同步请求:千万别在主线程玩这个!
有些教程为了"简化代码",会教你把open的第三个参数设为false,变成同步请求。代码看起来确实简单了,不用回调,直接拿到结果:
// 危险!同步请求,会阻塞页面
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', false); // false=同步
xhr.send();
console.log(xhr.responseText); // 直接拿到结果
看起来爽是吧?但代价是整个浏览器标签页卡死,直到请求回来。如果网络不好,用户就会看到一个 frozen 的页面,鼠标点不动,滚动条拉不了,甚至连关闭按钮都没反应。
现代浏览器已经强烈不推荐同步请求了,Chrome还计划在未来的版本里彻底禁掉主线程的同步XHR。所以记住:永远传true,或者干脆不传(默认就是true)。
那同步请求就完全没用吗?也不是。在Web Worker里面可以用,因为Worker没有UI,阻塞了也不影响页面。但那是进阶话题了,新手先别碰。
readyState的五个阶段:你的请求卡在哪了?
XHR有个属性叫readyState,表示请求的当前阶段。很多新手搞不懂这个和status的区别。简单说:
- readyState = 请求进行到哪个步骤了(过程)
- status = 服务器返回的HTTP状态码(结果)
readyState有五个值,从0到4:
const xhr = new XMLHttpRequest();
console.log(xhr.readyState); // 0,刚创建,还没open
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);
console.log(xhr.readyState); // 1,open调用了
xhr.onreadystatechange = function() {
// 这个回调会在每次readyState变化时触发
console.log('当前状态:', xhr.readyState);
if (xhr.readyState === 2) {
console.log('已经send,服务器收到请求了');
}
if (xhr.readyState === 3) {
console.log('正在下载响应体,可能只收到一部分数据');
}
if (xhr.readyState === 4) {
console.log('全部搞定,数据下完了');
// 这时候才能安全地访问responseText
}
};
xhr.send();
具体对应关系:
- 0 (UNSENT):刚
new出来,啥也没干 - 1 (OPENED):
open()调用了 - 2 (HEADERS_RECEIVED):
send()调用了,服务器返回响应头 - 3 (LOADING):正在下载响应体(大数据量时可能停留很久)
- 4 (DONE):全部完成,数据到手
实际开发中,你大部分时候只需要关心readyState === 4,因为这时候才能确定数据完整了。不过如果你想做个进度条,那readyState === 3就有用了,可以配合onprogress事件一起用。
status 200就万事大吉?太天真了!
很多人判断请求成功就写if (xhr.status === 200),这在简单场景没问题,但生产环境太粗糙了。HTTP状态码有一大家子,你得知道怎么处理:
xhr.onload = function() {
// 别只判断200,2xx都算成功
if (xhr.status >= 200 && xhr.status < 300) {
console.log('成功:', xhr.response);
}
// 400系列是客户端错误(你的锅)
else if (xhr.status >= 400 && xhr.status < 500) {
if (xhr.status === 401) {
console.error('没登录,去登录页吧');
window.location.href = '/login';
} else if (xhr.status === 403) {
console.error('权限不够,联系管理员');
} else if (xhr.status === 404) {
console.error('接口不存在,检查URL');
} else {
console.error('客户端错误:', xhr.status);
}
}
// 500系列是服务器错误(后端的锅)
else if (xhr.status >= 500) {
console.error('服务器挂了,找后端算账:', xhr.status);
}
};
常见状态码速查:
- 200 OK:万事大吉
- 201 Created:创建成功(POST新建资源)
- 204 No Content:成功但无返回体(DELETE常用)
- 301/302:重定向(XHR会自动跟随,一般不用管)
- 400 Bad Request:参数传错了,检查你的body
- 401 Unauthorized:没登录或token过期
- 403 Forbidden:登录了但权限不够
- 404 Not Found:URL写错了
- 500 Internal Server Error:后端代码报错了
- 502 Bad Gateway:网关挂了,可能是Nginx配置问题
- 503 Service Unavailable:服务维护中
还有个点要注意:status为0的情况。这通常表示请求根本没发出去,可能是跨域被拦了、请求被取消了、或者网络断开。别当成200处理,也别当成500,单独处理:
if (xhr.status === 0) {
console.error('请求未发送,检查跨域或网络');
}
responseText vs response:别用混了
XHR有两个拿数据的属性,新手经常搞混:
- responseText:返回纯文本字符串
- response:根据
responseType自动解析后的数据
默认情况下responseType是空字符串,这时候response和responseText是一样的,都是字符串。但你可以手动设置responseType,让XHR自动帮你解析:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);
// 关键:告诉XHR我要JSON,你帮我自动解析
xhr.responseType = 'json';
xhr.onload = function() {
if (xhr.status === 200) {
// 因为设置了responseType='json',这里直接拿到对象
console.log(xhr.response); // {userId: 1, id: 1, title: "...", body: "..."}
console.log(xhr.response.title); // 直接点属性,不用JSON.parse
// 如果没设置responseType,就得手动解析
// const data = JSON.parse(xhr.responseText);
}
};
xhr.send();
支持的responseType有:
''(默认):字符串'text':文本字符串(跟默认一样)'json':自动解析为JSON对象'blob':二进制大对象(下载文件用)'arraybuffer':原始二进制数据'document':XML文档(现在很少用了)
建议:现代浏览器都支持responseType='json',能省掉一次JSON.parse(),还能避免解析错误。但记得做兼容处理,IE11不支持这个。
JSON.parse()翻车现场:后端返回的不一定是JSON
说到JSON解析,这简直是新手噩梦现场。你信心满满地JSON.parse(xhr.responseText),结果报错了,一看后端返回的数据:
// 你以为后端返回的:
{"code": 200, "data": {"name": "张三"}}
// 实际后端返回的:
{code: 200, data: {name: "张三"}} // 没引号,不是标准JSON
// 或者:
console.log("debug");{"code": 200, "data": {}} // 多了个console.log
// 或者:
{"code": 200, "data": null} // 标准JSON,但你的代码没做null判断
所以解析前一定要try-catch,并且做数据校验:
xhr.onload = function() {
if (xhr.status === 200) {
let data;
try {
data = JSON.parse(xhr.responseText);
} catch (e) {
console.error('JSON解析失败,后端返回的不是标准JSON:', xhr.responseText);
return;
}
// 即使解析成功,也要检查数据结构
if (!data || typeof data !== 'object') {
console.error('数据格式不对:', data);
return;
}
// 检查业务状态码(有些接口HTTP 200但业务报错)
if (data.code !== 200) {
console.error('业务错误:', data.message);
return;
}
console.log('真正可用的数据:', data.data);
}
};
看到最后那个data.data了吗?很多接口设计是包裹两层的:外层是HTTP协议层,内层是业务状态层。即使HTTP状态是200,业务可能还是失败的,得看里面的code字段。
跨域:前端根本解决不了,但你可以这样调试
“Blocked by CORS policy”——这个报错每个前端都见过,而且第一反应都是:"我代码写错了?"其实跨域错误99%不是前端的问题,是后端没配置CORS(跨域资源共享)。
简单说,浏览器有个安全策略叫同源策略(Same-Origin Policy),要求协议、域名、端口三者完全一致。你前端跑在localhost:3000,后端在localhost:8080,端口不同,就是跨域,浏览器默认会拦住响应。
但注意:跨域请求其实是发出去了,后端也收到了,也返回了数据,只是浏览器把响应拦下来了,不让你看。你可以在Network面板看到请求,但代码里拿不到数据。
前端能做什么?几乎啥也做不了。这是浏览器的安全机制,你改不了。你能做的只有:
- 开发环境配置代理(webpack devServer的proxy)
- 让后端加CORS头(Access-Control-Allow-Origin: *)
- JSONP(只支持GET,老项目可能还在用)
如果你急着调试,可以用浏览器插件临时禁用CORS(比如Chrome的"CORS Unblock"),但千万别用在生产环境。
调试跨域问题的小技巧:先看Network面板的请求有没有发出去,再看Response有没有内容。如果有请求没响应,是后端没收到;如果有请求有响应但代码报错,才是CORS问题。
封装一个能用的AJAX函数:带超时、自动解析、错误处理
好了,基础概念都过了一遍,现在咱们封装一个能在项目里用的AJAX函数。需求:
- 支持Promise(告别回调地狱)
- 自动JSON解析
- 超时控制(默认10秒)
- 统一错误处理
- 支持取消请求
function request(options) {
return new Promise((resolve, reject) => {
const {
method = 'GET',
url,
data = null,
headers = {},
responseType = 'json',
timeout = 10000 // 默认10秒超时
} = options;
const xhr = new XMLHttpRequest();
// 超时处理
xhr.timeout = timeout;
xhr.ontimeout = () => {
reject(new Error(`请求超时: ${url}`));
};
// 网络错误处理
xhr.onerror = () => {
reject(new Error(`网络请求失败: ${url}`));
};
// 状态处理
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return;
// 请求完成,清除超时计时器(如果有的话)
if (xhr.status >= 200 && xhr.status < 300) {
// 成功,但还要检查数据有效性
let result = xhr.response;
// 如果没设置responseType或浏览器不支持,手动解析
if (responseType === 'json' && typeof result === 'string') {
try {
result = JSON.parse(result);
} catch (e) {
reject(new Error('JSON解析失败'));
return;
}
}
resolve(result);
} else {
// 根据状态码给不同错误信息
let message = `HTTP ${xhr.status}`;
if (xhr.status === 0) message = '网络错误或未跨域';
else if (xhr.status === 401) message = '未授权';
else if (xhr.status === 403) message = '禁止访问';
else if (xhr.status === 404) message = '接口不存在';
else if (xhr.status >= 500) message = '服务器错误';
reject(new Error(message));
}
};
// 打开连接
xhr.open(method, url, true);
// 设置responseType
xhr.responseType = responseType;
// 设置请求头
Object.keys(headers).forEach(key => {
xhr.setRequestHeader(key, headers[key]);
});
// 如果是POST/PUT,设置默认Content-Type
if (data && !headers['Content-Type'] && !(data instanceof FormData)) {
xhr.setRequestHeader('Content-Type', 'application/json');
}
// 发送数据
// 如果是对象且不是FormData,转成JSON字符串
let body = data;
if (data && typeof data === 'object' && !(data instanceof FormData)) {
body = JSON.stringify(data);
}
xhr.send(body);
});
}
// 使用示例
request({
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/posts/1'
}).then(data => {
console.log('拿到数据:', data);
}).catch(err => {
console.error('出错了:', err.message);
});
// POST示例
request({
method: 'POST',
url: 'https://jsonplaceholder.typicode.com/posts',
data: {
title: 'foo',
body: 'bar',
userId: 1
}
}).then(data => {
console.log('创建成功:', data);
});
这个封装已经能满足大部分场景了。但还缺个重要功能:取消请求。
取消请求:AbortController真香警告
有时候用户点了查询按钮,等不及又点了一次,或者页面跳转时还有请求没完成。这时候需要取消之前的请求,避免资源浪费或数据混乱。
XHR原生支持abort()方法,但配合Promise需要额外处理:
function requestWithCancel(options) {
const xhr = new XMLHttpRequest();
// 把xhr实例暴露出去,方便外部调用abort
const promise = new Promise((resolve, reject) => {
// ... 前面的代码一样
xhr.onabort = () => {
reject(new Error('请求被取消'));
};
// ... 后面的代码一样
});
// 给promise加个abort方法
promise.abort = () => {
xhr.abort();
};
return promise;
}
// 使用
const req = requestWithCancel({
url: 'https://api.example.com/slow-endpoint'
});
// 5秒后还没响应就取消
setTimeout(() => {
req.abort();
}, 5000);
req.then(data => {
console.log(data);
}).catch(err => {
if (err.message === '请求被取消') {
console.log('用户取消了请求');
}
});
如果你用AbortController(现代浏览器支持),代码更优雅:
function requestWithAbortController(options) {
const controller = new AbortController();
const signal = controller.signal;
const promise = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// 监听abort信号
signal.addEventListener('abort', () => {
xhr.abort();
});
xhr.onabort = () => {
reject(new Error('Aborted'));
};
// ... 其他代码不变
xhr.send();
});
// 返回promise和controller
return {
promise,
abort: () => controller.abort()
};
}
// 使用
const { promise, abort } = requestWithAbortController({
url: 'https://api.example.com/data'
});
// 随时调用abort取消
document.getElementById('cancelBtn').addEventListener('click', abort);
promise.then(data => {
console.log(data);
}).catch(err => {
if (err.name === 'AbortError' || err.message === 'Aborted') {
console.log('请求已取消');
}
});
请求发不出去?自查清单别急着甩锅后端
遇到请求没响应,很多前端第一反应是"后端又挂了"。但先别急着甩锅,按这个清单自查一遍:
1. URL写对了吗?
// 错误:少了协议
xhr.open('GET', 'api.example.com/data');
// 错误:多了空格
xhr.open('GET', ' https://api.example.com/data');
// 正确
xhr.open('GET', 'https://api.example.com/data');
// 本地开发注意:localhost vs 127.0.0.1
// 虽然都能访问,但浏览器认为是不同域名,cookie可能不共享
2. 请求头Content-Type和body格式对不上?
// 错误:发送JSON但Content-Type没设置
xhr.send(JSON.stringify({name: 'test'}));
// 后端收到的可能是空数据,因为默认Content-Type是text/plain
// 正确
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({name: 'test'}));
3. 后端返回的数据不干净?
有些后端同学喜欢在接口里加console.log调试,或者返回了BOM头(UTF-8 BOM),导致前端JSON解析失败。让后端检查返回体是否纯JSON,前面不能有任何其他字符。
4. 浏览器缓存搞事情?
GET请求容易被浏览器缓存,特别是IE。解决办法是加随机数:
// 加时间戳防止缓存
const url = `https://api.example.com/data?t=${Date.now()}`;
// 或者加随机数
const url = `https://api.example.com/data?r=${Math.random()}`;
5. HTTPS证书问题?
如果是自签名证书,浏览器会拦截,XHR直接失败。开发环境可以临时允许不安全内容,生产环境必须配正规证书。
几个骚但实用的技巧
用FormData上传文件,比你想象中简单
以前上传文件要用flash或者各种插件,现在XHR+FormData原生支持:
// HTML: <input type="file" id="fileInput" multiple>
document.getElementById('uploadBtn').addEventListener('click', () => {
const fileInput = document.getElementById('fileInput');
const files = fileInput.files;
if (files.length === 0) {
alert('先选文件');
return;
}
const formData = new FormData();
// 可以追加多个文件
for (let i = 0; i < files.length; i++) {
formData.append('files', files[i]);
}
// 还可以加其他字段
formData.append('description', '我的文件');
const xhr = new XMLHttpRequest();
// 上传进度监控
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
console.log(`上传进度: ${percent.toFixed(2)}%`);
// 更新进度条UI
document.getElementById('progress').style.width = percent + '%';
}
};
xhr.onload = () => {
if (xhr.status === 200) {
console.log('上传成功');
}
};
xhr.open('POST', 'https://api.example.com/upload', true);
// 注意:不要手动设置Content-Type,浏览器会自动设置并加上boundary
// xhr.setRequestHeader('Content-Type', 'multipart/form-data'); // 错误!
xhr.send(formData);
});
关键点:不要手动设置Content-Type,浏览器会自动生成multipart/form-data; boundary=----WebKitFormBoundary...,手动设置反而会导致boundary丢失,后端解析失败。
模拟进度条:下载也能监控
上传有xhr.upload.onprogress,下载也有xhr.onprogress:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.com/large-file.zip', true);
// 下载进度
xhr.onprogress = (e) => {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
console.log(`下载进度: ${percent.toFixed(2)}%`);
// 计算下载速度
const speed = e.loaded / ((Date.now() - startTime) / 1000);
console.log(`速度: ${(speed / 1024 / 1024).toFixed(2)} MB/s`);
} else {
// 如果服务器没返回Content-Length,只能知道下载了多少,不知道总共多少
console.log(`已下载: ${(e.loaded / 1024 / 1024).toFixed(2)} MB`);
}
};
const startTime = Date.now();
xhr.send();
多个请求串行还是并行?
有时候需要发多个请求,要看是串行(一个完了再发下一个)还是并行(同时发):
// 并行:用Promise.all
function request(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = () => resolve(xhr.response);
xhr.onerror = reject;
xhr.send();
});
}
// 同时发三个请求,等全部完成
Promise.all([
request('https://api.example.com/user'),
request('https://api.example.com/orders'),
request('https://api.example.com/messages')
]).then(([user, orders, messages]) => {
console.log('全部搞定:', user, orders, messages);
}).catch(err => {
console.error('有一个失败了:', err);
});
// 串行:用async/await
async function fetchSequentially() {
try {
const user = await request('https://api.example.com/user');
// 等user回来,再根据user.id查订单
const orders = await request(`https://api.example.com/orders?userId=${user.id}`);
// 再等orders回来
const details = await request(`https://api.example.com/orders/${orders[0].id}`);
console.log(details);
} catch (err) {
console.error(err);
}
}
别人用fetch你就焦虑?其实底层都一样
看到同事用fetch写代码很优雅,你是不是也焦虑过?其实fetch底层也是XHR(或者说和XHR同一级别的API),只是包装得更现代。理解了XHR,fetch上手就是5分钟的事:
// XHR写法
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.responseType = 'json';
xhr.onload = () => {
if (xhr.status === 200) {
console.log(xhr.response);
}
};
xhr.send();
// 等价的fetch写法
fetch('https://api.example.com/data')
.then(res => {
if (!res.ok) throw new Error(res.statusText);
return res.json();
})
.then(data => console.log(data))
.catch(err => console.error(err));
fetch的痛点(比如进度监控、同步请求、IE兼容),XHR都能解决。所以先学XHR不吃亏,反而是捷径。
最后说句掏心窝子的
我知道,现在让你手写AJAX,你可能会想:"有axios不用,有病吧?"确实,生产环境直接用axios、fetch或者更现代的@tanstack/react-query就行,没人让你天天造轮子。
但问题是,如果你不理解XHR,你连axios的拦截器都配不明白。为啥请求超时了没触发拦截器?为啥上传进度监听不到?为啥有时候返回的数据格式不对?这些问题的答案都在XHR里。
就像会开车不等于懂发动机,但万一路上抛锚,你会修啊!而且去面试的时候,你能从XHR讲到Promise讲到async/await,再讲到HTTP协议和浏览器事件循环,这不比背八股文强多了?
所以别嫌XHR老,把它当成前端网络请求的"操作系统",axios只是上面的"应用软件"。操作系统懂了,用什么软件都顺手。
好了,这篇文章够长了,代码也给了不少。建议你自己动手敲一遍,特别是那个封装的request函数,改改参数、故意制造点错误,看看报错信息长啥样。纸上得来终觉浅,绝知此事要躬行——这话虽然土,但真管用。
下次面试官再问你"不用框架怎么发请求",希望你能自信地说:“用XMLHttpRequest,先open配置参数,设置回调处理onload和onerror,然后send发送,注意要处理readyState和status的不同状态…”——然后你就看到他点头了。

&spm=1001.2101.3001.5002&articleId=157943890&d=1&t=3&u=7822ed4f972148199735b99dbb530ce1)
1万+

被折叠的 条评论
为什么被折叠?



