FormData 是 HTML5 新增的内置对象,用于以键值对的形式封装表单数据,支持文件上传,可通过 XMLHttpRequest 或 Fetch API 异步提交,是前端处理表单数据(尤其是文件上传)的核心工具。本文从基础到进阶,全面讲解 FormData 的使用、原理和实战技巧。
一、FormData 核心特性
- 兼容性:支持所有现代浏览器(Chrome、Firefox、Edge、Safari 10+),IE10 及以上部分支持(需注意文件上传兼容性)。
- 核心能力:
- 模拟表单提交(
multipart/form-data编码); - 动态添加/删除键值对,无需手动拼接字符串;
- 支持文件/Blob 类型数据上传;
- 可与 XMLHttpRequest、Fetch、Axios 无缝配合。
- 模拟表单提交(
- 编码类型:默认采用
multipart/form-data(表单上传文件的标准编码),区别于application/x-www-form-urlencoded(普通表单默认编码,不支持文件)。
二、基础用法
1. 创建 FormData 对象
方式1:空对象初始化
创建空的 FormData,手动添加键值对:
// 创建空 FormDataconstformData=newFormData();// 添加普通键值对(字符串/数字)formData.append('username','zhangsan');formData.append('age',25);// 添加布尔值(会自动转为字符串 "true"/"false")formData.append('isVip',true);方式2:从 DOM 表单初始化
直接基于已有的<form>元素创建,自动封装所有表单字段:
<!-- HTML 表单 --><formid="myForm"><inputtype="text"name="username"value="lisi"><inputtype="number"name="age"value="30"><inputtype="file"name="avatar"><!-- 文件上传字段 --><inputtype="checkbox"name="hobby"value="coding"checked><inputtype="radio"name="gender"value="male"checked></form>// 从表单元素初始化 FormDataconstform=document.getElementById('myForm');constformData=newFormData(form);// 此时 formData 已包含所有表单字段的键值对2. 核心方法
FormData 提供了一套方法操作键值对,常用如下:
| 方法 | 作用 | 示例 |
|---|---|---|
append(key, value) | 添加键值对(支持重复键) | formData.append('hobby', 'reading') |
set(key, value) | 设置键值对(覆盖已有值) | formData.set('username', 'wangwu') |
get(key) | 获取指定键的第一个值 | formData.get('username')// “wangwu” |
getAll(key) | 获取指定键的所有值(数组) | formData.getAll('hobby')// [“coding”, “reading”] |
delete(key) | 删除指定键的所有值 | formData.delete('age') |
has(key) | 判断是否存在指定键 | formData.has('gender')// true |
entries() | 返回迭代器,遍历所有键值对 | for (let [k, v] of formData.entries()) {} |
keys() | 返回迭代器,遍历所有键 | for (let k of formData.keys()) {} |
values() | 返回迭代器,遍历所有值 | for (let v of formData.values()) {} |
关键区别:append vs set
append:允许同一个键对应多个值(如多选框),不会覆盖已有值;set:会覆盖指定键的所有已有值,最终仅保留最新值。
示例:
constformData=newFormData();formData.append('tag','js');formData.append('tag','html');console.log(formData.getAll('tag'));// ["js", "html"]formData.set('tag','css');console.log(formData.getAll('tag'));// ["css"]3. 遍历 FormData
FormData 是可迭代对象,支持多种遍历方式:
constformData=newFormData();formData.append('name','张三');formData.append('age',20);// 方式1:for...of 遍历 entriesfor(const[key,value]offormData){console.log(`${key}:${value}`);}// 方式2:forEachformData.forEach((value,key)=>{console.log(`${key}:${value}`);});// 方式3:遍历 keys/valuesfor(constkeyofformData.keys()){console.log('键:',key);}for(constvalueofformData.values()){console.log('值:',value);}三、文件上传(核心场景)
FormData 最核心的用途是文件上传,支持单文件、多文件、大文件分片上传。
1. 单文件上传
步骤1:HTML 布局
<inputtype="file"id="fileInput"accept="image/*"><buttonid="uploadBtn">上传文件</button>步骤2:JS 处理上传
constfileInput=document.getElementById('fileInput');constuploadBtn=document.getElementById('uploadBtn');uploadBtn.addEventListener('click',async()=>{// 获取选中的文件constfile=fileInput.files[0];if(!file){alert('请选择文件');return;}// 创建 FormData 并添加文件constformData=newFormData();formData.append('file',file);// 键名 "file" 需与后端接口约定formData.append('desc','用户头像');// 可同时添加其他参数try{// 发送请求(Fetch API)constresponse=awaitfetch('/api/upload',{method:'POST',body:formData,// FormData 作为 body,自动设置 Content-Type: multipart/form-data// 无需手动设置 Content-Type,浏览器会自动添加边界符(boundary)});constresult=awaitresponse.json();console.log('上传成功:',result);}catch(error){console.error('上传失败:',error);}});2. 多文件上传
只需将 input 的multiple属性设为 true,然后遍历 files 数组添加:
<inputtype="file"id="multiFileInput"multipleaccept="image/*"><buttonid="multiUploadBtn">批量上传</button>constmultiFileInput=document.getElementById('multiFileInput');constmultiUploadBtn=document.getElementById('multiUploadBtn');multiUploadBtn.addEventListener('click',async()=>{constfiles=multiFileInput.files;if(files.length===0){alert('请选择文件');return;}constformData=newFormData();// 遍历多文件,添加到同一个键(后端接收数组)for(leti=0;i<files.length;i++){formData.append('files',files[i]);// 键名统一为 "files"}// 发送请求constresponse=awaitfetch('/api/multi-upload',{method:'POST',body:formData,});constresult=awaitresponse.json();console.log('批量上传成功:',result);});3. 上传进度监控
通过 XMLHttpRequest 可监控文件上传进度(Fetch API 需结合 ReadableStream,较复杂):
constfileInput=document.getElementById('fileInput');fileInput.addEventListener('change',()=>{constfile=fileInput.files[0];if(!file)return;constformData=newFormData();formData.append('file',file);constxhr=newXMLHttpRequest();xhr.open('POST','/api/upload');// 监控上传进度xhr.upload.addEventListener('progress',(e)=>{if(e.lengthComputable){constprogress=(e.loaded/e.total)*100;console.log(`上传进度:${progress.toFixed(2)}%`);}});// 上传完成回调xhr.addEventListener('load',()=>{if(xhr.status>=200&&xhr.status<300){console.log('上传成功:',JSON.parse(xhr.responseText));}else{console.error('上传失败');}});// 发送请求xhr.send(formData);});四、与主流库配合使用
1. Axios 中使用 FormData
Axios 会自动识别 FormData,无需手动设置 Content-Type:
importaxiosfrom'axios';// 单文件上传asyncfunctionuploadFile(file){constformData=newFormData();formData.append('file',file);formData.append('name','测试文件');try{constresponse=awaitaxios.post('/api/upload',formData,{// 监控上传进度onUploadProgress:(e)=>{constprogress=(e.loaded/e.total)*100;console.log(`进度:${progress}%`);},headers:{// 无需设置 Content-Type,Axios 会自动添加 boundary// "Content-Type": "multipart/form-data"}});returnresponse.data;}catch(error){console.error('上传失败:',error);}}2. React/Vue 中使用 FormData
以 React 为例(Vue 逻辑一致):
import React, { useRef } from 'react'; import axios from 'axios'; function UploadComponent() { const fileInputRef = useRef(null); const handleUpload = async () => { const file = fileInputRef.current.files[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); await axios.post('/api/upload', formData); }; return ( <div> <input type="file" ref={fileInputRef} /> <button onClick={handleUpload}>上传</button> </div> ); }五、常见问题与解决方案
1. 手动设置 Content-Type 导致上传失败
问题:手动设置Content-Type: multipart/form-data后,浏览器不会自动添加边界符(boundary),后端无法解析。
解决方案:不手动设置 Content-Type,让浏览器/Axios 自动生成(包含 boundary)。
2. 文件大小限制
问题:大文件上传超时/失败。
解决方案:
- 前端:分片上传(将文件切分成多个 Blob,分批上传);
- 后端:配置文件大小限制(如 Node.js/Express 需设置
express-fileupload的limits)。
3. 跨域上传文件
问题:跨域时请求被拦截。
解决方案:
- 后端配置 CORS:允许
Content-Type: multipart/form-data,并允许OPTIONS预检请求; - 前端请求时无需额外配置(Fetch/Axios 自动处理)。
4. FormData 无法打印/调试
问题:console.log(formData)只能看到空对象,无法直接查看内容。
解决方案:
// 方式1:遍历打印formData.forEach((v,k)=>console.log(k,v));// 方式2:转为对象(仅适用于非文件类型,文件会显示 [object File])constformDataObj=Object.fromEntries(formData.entries());console.log(formDataObj);六、进阶技巧
1. 分片上传大文件
核心思路:将文件按固定大小切分,分批上传,后端合并:
asyncfunctionsliceUpload(file){constchunkSize=1024*1024;// 1MB 每片consttotalChunks=Math.ceil(file.size/chunkSize);constfileHash=Date.now()+'-'+file.name;// 唯一标识文件for(letchunkIndex=0;chunkIndex<totalChunks;chunkIndex++){// 切分文件conststart=chunkIndex*chunkSize;constend=Math.min(start+chunkSize,file.size);constchunk=file.slice(start,end);// 创建 FormDataconstformData=newFormData();formData.append('chunk',chunk);formData.append('chunkIndex',chunkIndex);formData.append('totalChunks',totalChunks);formData.append('fileHash',fileHash);formData.append('fileName',file.name);// 上传分片awaitfetch('/api/upload-chunk',{method:'POST',body:formData,});}// 所有分片上传完成,通知后端合并awaitfetch('/api/merge-chunk',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({fileHash,fileName,totalChunks}),});}2. 自定义 Blob 上传
除了文件,还可以上传自定义 Blob(如生成的文本、图片):
// 上传自定义文本 BlobconsttextBlob=newBlob(['Hello FormData'],{type:'text/plain'});constformData=newFormData();formData.append('textFile',textBlob,'custom.txt');// 第三个参数为文件名fetch('/api/upload',{method:'POST',body:formData,});七、后端接收示例(Node.js/Express)
以 Express 为例,使用multer处理 FormData 上传的文件:
constexpress=require('express');constmulter=require('multer');constapp=express();// 配置文件存储路径conststorage=multer.diskStorage({destination:(req,file,cb)=>{cb(null,'./uploads');// 上传文件保存目录},filename:(req,file,cb)=>{cb(null,Date.now()+'-'+file.originalname);// 重命名文件}});constupload=multer({storage});// 单文件上传接口app.post('/api/upload',upload.single('file'),(req,res)=>{// req.file 是上传的文件信息// req.body 是 FormData 中的其他参数(如 desc)res.json({code:200,msg:'上传成功',file:req.file,body:req.body});});// 多文件上传接口app.post('/api/multi-upload',upload.array('files',5),(req,res)=>{// req.files 是多文件数组res.json({code:200,msg:'批量上传成功',files:req.files});});app.listen(3000,()=>{console.log('服务器运行在 http://localhost:3000');});八、总结
FormData 是前端处理表单数据(尤其是文件上传)的核心工具,核心要点:
- 优先使用
append添加数据,set用于覆盖; - 上传文件时无需手动设置
Content-Type,避免丢失 boundary; - 结合 XMLHttpRequest 监控上传进度,结合 Axios 简化请求;
- 大文件上传推荐分片策略,解决超时/失败问题;
- 后端需对应配置文件解析(如 Express + multer)。