
AJAX上传文件的基础逻辑:从FormData到请求发送
其实AJAX上传文件的核心就一个“小工具”——FormData。你可以把它理解成一个“虚拟的表单”,既能装文件,也能装文字信息(比如文件名、分类),而且浏览器会自动帮你把这些内容转换成服务器能认的“multipart/form-data”格式——不用你自己拼复杂的字符串,省了超多麻烦。
我第一次用的时候,还傻兮兮地手动构造请求体:把文件转成Base64字符串,再拼到JSON里发出去,结果后端根本解析不了。后来查了MDN才知道,FormData是浏览器专门为“上传文件”设计的——它能完美模拟传统表单的提交方式,但又不会刷新页面。
具体怎么做呢?我拆成5步,你跟着走肯定没错:
标签,然后用JS监听它的change
事件——当用户选好文件,e.target.files[0]
就是你要的文件对象(比如头像、商品图)。 let formData = new FormData();
然后用formData.append('file', file)
把文件加进去——要是你还想传点其他信息(比如“这是商品图”),可以再加一句formData.append('type', 'product_img')
,特别方便。 let xhr = new XMLHttpRequest();
——这是AJAX的“核心引擎”,负责发请求和收响应。 xhr.open('POST', '/api/upload', true);
——这里要注意3点:①方法必须是POST
(GET
有长度限制,传不了大文件);②/api/upload
是你后端的上传接口地址;③第三个参数true
表示异步请求(不阻塞页面)。 xhr.send(formData);
之后要加两个事件监听:xhr.onload
(请求成功完成时触发)和xhr.onerror
(请求失败时触发)。比如: xhr.onload = function() {
FileReaderif (xhr.status === 200) {
alert('上传成功!');
// 这里可以更新页面,比如显示上传的图片
}
};
xhr.onerror = function() {
alert('上传失败,再试一次吧~');
};
为什么说这个逻辑“基础但关键”?因为我见过很多新手绕开FormData,用
读文件成Base64再传——不是不行,但Base64会比原文件大30%,传大文件的时候更卡;而且FormData能同时传文件和其他字段,比Base64灵活多了。比如我朋友的博客头像上传,一开始用Base64,1MB的头像传要2秒,换成FormData后只要1秒不到,还稳定。
xhr.uploadAJAX上传最容易踩的3个坑,我帮你踩过了
基础逻辑学会了,但实际做的时候,你肯定会遇到一些“奇奇怪怪”的问题——我帮不同客户做过5次AJAX上传功能,踩了3个最常见的坑,今天一次性告诉你怎么避。
坑1:进度条做不出来?其实是没监听对事件
我之前帮电商客户做商品图上传时,明明加了进度条代码,但进度条就是不动。查了3小时才发现:进度事件要绑在
上,不是
xhr本身!
正常的进度监听应该这么写:
js
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) { // 判断是否能获取进度
let percent = (e.loaded / e.total) 100; // 计算百分比
document.getElementById(‘progressBar’).style.width = percent + ‘%’; // 更新进度条
}
};
为什么?因为
xhr本身的
progress事件是监听“下载进度”(比如后端返回数据的进度),而
xhr.upload的
progress才是“上传进度”——我之前就是绑错了地方,所以进度条没反应。后来改绑到
upload上,进度条一下子就动了,客户还夸这个功能“很专业”。
坑2:大文件上传卡顿?试试分片上传
上个月帮做短视频的客户做上传功能,1GB的视频直接传,要么超时,要么传到一半断开。后来我想起之前学过的分片上传——把大文件切成一小块一小块的,分多次发给后端,后端再拼起来。
具体怎么做?核心是
File.slice()方法——它能把文件切成指定范围的“分片”。比如把1GB的文件切成100MB一块:
const chunkSize = 100 1024 1024; // 100MB每片,
const totalChunks = Math.ceil(file.size / chunkSize); // 总片数。
for循环遍历每一片,用
file.slice(i chunkSize, (i + 1) chunkSize)切出分片,然后把分片、分片序号、总片数一起发给后端:
js
for (let i = 0; i < totalChunks; i++) {
let chunk = file.slice(i
chunkSize, (i + 1) chunkSize);
let formData = new FormData();
formData.append(‘chunk’, chunk);
formData.append(‘chunkIndex’, i);
formData.append(‘totalChunks’, totalChunks);
formData.append(‘fileName’, file.name);
// 发送这个formData给后端
sendChunk(formData); // 这里需要写一个发送分片的函数
}
的顺序把分片拼起来,就能得到完整的文件。
这样做的好处是稳定——就算某一片传失败了,只需要重新传那一片,不用整个文件重传;而且后端处理小文件也更轻松。那个短视频客户用了分片上传后,1GB的视频上传成功率从50%升到了95%,老板还加了我微信说“下次有需求找你”。
坑3:跨域报错?不是AJAX的问题,是后端没配置CORS
我上个月做前端项目时,和后端联调上传功能,老是报“Access-Control-Allow-Origin”错误。我一开始以为是AJAX写得有问题,查了好几遍代码——FormData没错,XHR参数没错,请求地址也没错,后来才发现:跨域是浏览器的同源策略限制,和AJAX没关系,得让后端配置CORS。
什么是CORS?简单说就是后端告诉浏览器:“这个前端域名是安全的,你让它访问我吧”。具体要让后端加3个响应头:
(或者
表示允许所有域名,但不推荐,不安全);
(允许的请求方法,上传一般用POST);
Access-Control-Allow-Headers: Content-Type(允许的请求头,FormData会自动加
Content-Type: multipart/form-data,所以得允许这个头)。
要是你传了自定义头信息(比如
Authorizationtoken),后端还得加
Access-Control-Allow-Headers: Authorization。我当时和后端说清楚后,他5分钟就配置好了,跨域问题一下子就解决了——原来之前的报错,根本不是我的问题!
下面这个表格,是我整理的AJAX上传 vs 传统表单上传的区别,你一看就明白为什么AJAX更好用:
对比项 | 传统表单上传 | AJAX上传 |
---|---|---|
页面刷新 | 是(会丢失表单内容) | 否(无刷新体验) |
进度监控 | 无法实现 | 可以精准显示上传百分比 |
大文件支持 | 容易超时/卡顿 | 可分片上传,稳定性高 |
用户体验 | 差(刷新后得重新填内容) | 好(实时反馈上传状态) |
你要是按这些方法试了,欢迎回来告诉我效果!要是遇到什么奇怪的问题——比如进度条突然跳满,或者分片合并后文件损坏——评论区留个言,我帮你想想办法——毕竟这些坑我都踩过,说不定能帮你省点时间~
你是不是经常遇到要传好几个文件的情况?比如做商品详情页要传5-10张不同角度的图,或者传客户案例要传一组照片?其实AJAX完全能搞定多文件上传,步骤跟单文件差不多,就多两步小操作。首先得给文件输入框加个multiple
属性——把原来的改成
,这样用户选文件时就能像在电脑上选文件一样,按住Ctrl键点选多个,或者拖个框选一片,不用再点好几次“选择文件”。我之前帮朋友做婚纱摄影的案例上传功能,就这么改了一下,用户反馈说“终于不用传一张等一张了”。
选好多个文件后,JS处理也不复杂。之前单文件是拿e.target.files[0]
,多文件就是直接拿e.target.files
这个数组,里面每个元素都是选好的文件对象。接下来只要循环这个数组,把每个文件加到FormData里就行——比如用forEach
遍历,每遍历一个就调用formData.append('file', 当前文件)
。这里有个小技巧:FormData的append
方法允许同一个键名加多次,不用改成file1
、file2
这种,就用同一个file
键名,服务器端收到会自动当成列表处理。比如PHP里用$_FILES['file']
能拿到所有文件,Python Django用request.FILES.getlist('file')
也能拿到,后端不用改太多代码。我之前用这个方法传过8张1MB左右的商品图,不到2秒就传完了,页面没刷新,用户还能接着填商品名字和描述,体验比传统表单好太多。
FormData在老浏览器里能用吗?
FormData的兼容性很好,IE10及以上版本的浏览器都支持,Chrome、Firefox、Safari等现代浏览器也都兼容。如果你的网站不需要兼容IE9及以下,完全可以放心用;如果要兼容更老的浏览器,可能需要用Flash或其他替代方案,但现在大部分场景下FormData已经足够覆盖。
AJAX上传能同时传多个文件吗?
可以的。只需要给文件输入框加个multiple
属性(比如),用户选文件时就能选多个。然后在JS里循环e.target.files
数组,把每个文件都用formData.append('file', 文件对象)
加进去就行——服务器端只要按多个文件的逻辑接收就行。
为什么进度条没反应?
大概率是进度事件绑错了地方!进度事件要绑定在xhr.upload
对象上,而不是xhr
本身。因为xhr.upload
负责监听上传阶段的进度,xhr
本身的进度事件是监听下载阶段的(比如服务器返回数据的进度)。把onprogress
事件绑到xhr.upload
上,进度条就能正常显示了。
跨域报错是AJAX代码的问题吗?
不是的。跨域报错是浏览器的同源策略限制,和AJAX代码没关系,得让后端配置CORS(跨域资源共享)。具体来说,后端需要在响应头里加三个字段:Access-Control-Allow-Origin
(允许的前端域名)、Access-Control-Allow-Methods
(允许的请求方法,比如POST)、Access-Control-Allow-Headers
(允许的请求头,比如Content-Type)。配置好这些,跨域问题就能解决。
多大的文件需要用分片上传?
没有固定的数字标准,一般 100MB以上的文件用分片上传。因为大文件直接传容易超时、卡顿,或者因为网络波动导致上传失败——分片上传能把大文件切成小份,就算某一份传失败,只需要重传那一份,不用整个文件重新传,稳定性更高。如果你的文件大部分是几MB的小图,直接传就行,没必要分片。