基于FormData的JS批量文件上传实现指南
本文详细解析如何利用JavaScript中的FormData对象实现前端批量文件上传功能。文章对比了三种常见实现方式:动态添加输入框、使用multiple属性多选以及隐藏Input配合数组管理。通过具体的HTML与JS代码示例,展示了从构造FormData到Ajax提交的全过程,并附带后端接收参数的关键代码,帮助开发者解决批量附件上传的技术难点。
近期项目中遇到了批量上传附件的需求,查阅相关资料后发现,网络上的教程往往只展示部分代码且解释不够详尽。为了记录实践过程中的经验教训及多种实现方案,避免日后遗忘,特此整理本文。
FormData对象基础
首先对FormData对象进行简要介绍,相关定义如下:
XMLHttpRequest Level 2规范引入了FormData接口。借助该接口,开发者可以使用JavaScript模拟一系列键值对来构建表单数据,并通过XMLHttpRequest的send()方法异步提交这些数据。相较于传统的Ajax请求,使用FormData的最大优势在于支持异步上传二进制文件。
在之前的自定义Input文件上传样式中,已实现了单文件上传及自定义样式的功能。需要注意的是,若通过传入表单JS对象来构造FormData,系统会自动注入表单内的值;若新建一个空对象并手动调用append方法添加类型为file的文件,务必确保传入的是File对象,而非FileList对象。
以下是大致的运行效果展示:

后端接口定义
后端Controller提供了两种接收方法,但前端三种提交方式均调用同一个接口地址。
/**
* 批量上传
*/
@PostMapping("upload")
public ResultModel<List<AttachmentVo>> upload(HttpServletRequest request, @RequestParam("applyId") String applyId){
List<MultipartFile> multipartFileList = ((MultipartHttpServletRequest) request).getFiles("attachment");
System.out.println(multipartFileList.size());
System.out.println(applyId);
return null;
}
/**
* 批量上传2 (推荐使用)
*/
@PostMapping("upload2")
public ResultModel<List<AttachmentVo>> upload2(MultipartFile[] attachment,@RequestParam("applyId") String applyId){
System.out.println(attachment.length);
System.out.println(applyId);
return null;
}
方式一:动态添加Input
第一种实现方式
点击Add按钮可追加一个input元素,点击Delete按钮或叉号可删除对应的input。这种方式要求用户为每个独立的input单独选择文件。
界面效果如下:

HTML结构代码
<form id="attachments" enctype="multipart/form-data" class="form-horizontal nice-validator n-yellow" novalidate="novalidate">
<div class='form-body'>
<div class='form-group'>
<label class="control-label col-md-1">附件管理:</label>
<div class="col-md-4">
<button id="attachmentAddBtn" type="button" class="btn btn-default">Add Attachment</button>
<button id="attachmentDeleteBtn" type="button" class="btn btn-default">Delete Attachment</button>
<button id="attachmentUploadBtn" type="button" class="btn btn-default">Upload</button>
</div>
</div>
<div class='form-group'>
<label class="control-label col-md-1">附件上传:</label>
<div id="attachmentInputs" class="col-md-3">
</div>
</div>
</div>
</form>
JavaScript逻辑代码
//attachment-remove
$("#attachmentInputs").on("click", ".attachment-remove", function (even) {
$(this).prev().remove();//删除上一个兄弟节点
$(this).remove();//删除自己
});
//add but
$("#attachmentAddBtn").click(function (even) {
//name值一样就可以
$("#attachmentInputs").append("<input name=\"attachment\" type=\"file\" class=\"form-control input-attachment\"/><i class=\"fa fa-times attachment-remove\"></i>");
});
//delete
$("#attachmentDeleteBtn").click(function (even) {
var files = $("#attachmentInputs input[type='file']");
files.each(function (index, element) {
//从最下面开始删除,至少保留一个
if (!(index === 0) && index === (files.length - 1)) {
$(element).next().remove();
$(element).remove();
}
});
});
//upload
$("#attachmentUploadBtn").click(function (even) {
//1、通过HTML表单创建FormData对象 自动注入
// var formData = new FormData($("#attachments")[0]);
//2、从零开始创建FormData对象 手动注入
var formData = new FormData();
//注入 name=file
var files = $("#attachmentInputs input[type='file']");
for (var i = 0; i < files.length; i++) {
//注意:这里append进去的是File对象,而不是FileList对象
formData.append("attachment", files[i].files[0]);
}
//注入name=text
formData.append("applyId", "123456");
console.log(formData.getAll("attachment"));
//执行上传
$.ajax({
url: ctx + "/attachment/upload2",
type: "post",
data: formData,
processData: false,
contentType: false,
success: function (data) {
},
error: function (e) {
}
});
});
//add one input
$("#attachmentAddBtn").click();
方式二:multiple属性多选
第二种实现方式
此方式仅包含一个input元素,利用multiple="multiple"属性允许用户在弹窗中一次性选择多个文件进行提交。若需进一步优化,可参考第三种方式,展示文件名并支持单独删除已选文件。
界面效果如下:


HTML结构代码
<form id="attachments2" enctype="multipart/form-data" class="form-horizontal" novalidate="novalidate">
<div class='form-body'>
<div class='form-group'>
<label class="control-label col-md-1">附件管理:</label>
<div class="col-md-4">
<button id="attachmentUploadBtn2" type="button" class="btn btn-default">Upload</button>
</div>
</div>
<div class='form-group'>
<label class="control-label col-md-1">附件上传:</label>
<div id="attachmentInputs2" class="col-md-3">
<input name="attachment" type="file" class="form-control input-attachment" multiple="multiple"/>
</div>
</div>
</div>
</form>
JavaScript逻辑代码
//upload2
$("#attachmentUploadBtn2").click(function (even) {
//1、通过HTML表单创建FormData对象 自动注入
// var formData = new FormData($("#attachments2")[0]);
//2、从零开始创建FormData对象 手动注入
var formData = new FormData();
//注入 name=file
var files = $("#attachmentInputs2 input[type='file']");
for (var i = 0; i < files[0].files.length; i++) {
formData.append("attachment", files[0].files[i]);
}
//注入name=text
formData.append("applyId", "123456");
console.log(formData.getAll("attachment"));
//执行上传
$.ajax({
url: ctx + "/attachment/upload2",
type: "post",
data: formData,
processData: false,
contentType: false,
success: function (data) {
},
error: function (e) {
}
});
});
方式三:隐藏Input与数组管理
第三种实现方式
定义一个隐藏的input,并将Select File按钮的click事件与input的click事件绑定,点击按钮即触发文件选择对话框。同时监听input的change事件,将选中的File对象push到全局数组变量attachmentArray中。点击Upload时,再遍历该数组并将文件注入到FormData对象中。
界面效果如下:

HTML结构代码
<form id="attachments3" enctype="multipart/form-data" class="form-horizontal" novalidate="novalidate">
<div class='form-body'>
<div class='form-group'>
<label class="control-label col-md-1">附件管理:</label>
<div class="col-md-4">
<button id="selectFile" type="button" class="btn btn-default">Select File</button>
<button id="attachmentUploadBtn3" type="button" class="btn btn-default">Upload</button>
</div>
</div>
<div class='form-group'>
<label class="control-label col-md-1">附件上传:</label>
<input id="attachmentInputs3" type="file" style="display: none;"/>
<div id="attachmentText3" class="col-md-3">
</div>
</div>
</div>
</form>
JavaScript逻辑代码
//存放file对象
var attachmentArray = [];
//attachment-remove
$("#attachmentText3").on("click", ".attachment-remove", function (even) {
//删除attachmentArray数据
attachmentArray.splice($(this).data("index"), 1);
//删除html对象
$(this).prev().prev().remove();
$(this).prev().remove();
$(this).remove();
});
//Select File
$("#selectFile").click(function (even) {
// 获取input
$("#attachmentInputs3").click();
});
//input change
$("#attachmentInputs3").change(function (even) {
// 获取input
var fileName = $(this).val();
var file = $(this)[0].files[0];
//是否选择了文件
if (fileName) {
attachmentArray.push(file);
$("#attachmentText3").append("<div><p class='attachment-text-p'>" + fileName + "</p><i data-index='" + (attachmentArray.length - 1) + "' class=\"fa fa-times attachment-remove\"></i></div>")
}
});
//upload3
$("#attachmentUploadBtn3").click(function (even) {
//这里只能手动注入
var formData = new FormData();
//遍历数据,手动注入formData
for (var i = 0; i < attachmentArray.length; i++) {
formData.append("attachment", attachmentArray[i]);
}
formData.append("applyId", "123456");
console.log(formData.getAll("attachment"));
//执行上传
$.ajax({
url: ctx + "/attachment/upload",
type: "post",
data: formData,
processData: false,
contentType: false,
success: function (data) {
},
error: function (e) {
}
});
});
数据与请求分析
最后,展示File数据内容、请求头信息以及后端成功接收参数的截图。
File数据详情

请求头信息

后端成功接收参数截图

以上内容为本文的全部总结,希望能为读者的学习提供有益帮助,也欢迎大家继续支持本站。
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18
