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,手动添加键值对:
javascript
// 创建空 FormData
const formData = new FormData();
// 添加普通键值对(字符串/数字)
formData.append('username', 'zhangsan');
formData.append('age', 25);
// 添加布尔值(会自动转为字符串 "true"/"false")
formData.append('isVip', true);
方式2:从 DOM 表单初始化
直接基于已有的 <form> 元素创建,自动封装所有表单字段:
html
<!-- HTML 表单 -->
<form id="myForm">
<input type="text" name="username" value="lisi">
<input type="number" name="age" value="30">
<input type="file" name="avatar"> <!-- 文件上传字段 -->
<input type="checkbox" name="hobby" value="coding" checked>
<input type="radio" name="gender" value="male" checked>
</form>
javascript
// 从表单元素初始化 FormData
const form = document.getElementById('myForm');
const formData = new FormData(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:会覆盖指定键的所有已有值,最终仅保留最新值。
示例:
javascript
const formData = new FormData();
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 是可迭代对象,支持多种遍历方式:
javascript
const formData = new FormData();
formData.append('name', '张三');
formData.append('age', 20);
// 方式1:for...of 遍历 entries
for (const [key, value] of formData) {
console.log(`${key}: ${value}`);
}
// 方式2:forEach
formData.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// 方式3:遍历 keys/values
for (const key of formData.keys()) {
console.log('键:', key);
}
for (const value of formData.values()) {
console.log('值:', value);
}
三、文件上传(核心场景)
FormData 最核心的用途是文件上传,支持单文件、多文件、大文件分片上传。
1. 单文件上传
步骤1:HTML 布局
html
<input type="file" id="fileInput" accept="image/*">
<button id="uploadBtn">上传文件</button>
步骤2:JS 处理上传
javascript
const fileInput = document.getElementById('fileInput');
const uploadBtn = document.getElementById('uploadBtn');
uploadBtn.addEventListener('click', async () => {
// 获取选中的文件
const file = fileInput.files[0];
if (!file) {
alert('请选择文件');
return;
}
// 创建 FormData 并添加文件
const formData = new FormData();
formData.append('file', file); // 键名 "file" 需与后端接口约定
formData.append('desc', '用户头像'); // 可同时添加其他参数
try {
// 发送请求(Fetch API)
const response = await fetch('/api/upload', {
method: 'POST',
body: formData, // FormData 作为 body,自动设置 Content-Type: multipart/form-data
// 无需手动设置 Content-Type,浏览器会自动添加边界符(boundary)
});
const result = await response.json();
console.log('上传成功:', result);
} catch (error) {
console.error('上传失败:', error);
}
});
2. 多文件上传
只需将 input 的 multiple 属性设为 true,然后遍历 files 数组添加:
html
<input type="file" id="multiFileInput" multiple accept="image/*">
<button id="multiUploadBtn">批量上传</button>
javascript
const multiFileInput = document.getElementById('multiFileInput');
const multiUploadBtn = document.getElementById('multiUploadBtn');
multiUploadBtn.addEventListener('click', async () => {
const files = multiFileInput.files;
if (files.length === 0) {
alert('请选择文件');
return;
}
const formData = new FormData();
// 遍历多文件,添加到同一个键(后端接收数组)
for (let i = 0; i < files.length; i++) {
formData.append('files', files[i]); // 键名统一为 "files"
}
// 发送请求
const response = await fetch('/api/multi-upload', {
method: 'POST',
body: formData,
});
const result = await response.json();
console.log('批量上传成功:', result);
});
3. 上传进度监控
通过 XMLHttpRequest 可监控文件上传进度(Fetch API 需结合 ReadableStream,较复杂):
javascript
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', () => {
const file = fileInput.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/upload');
// 监控上传进度
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const progress = (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:
javascript
import axios from 'axios';
// 单文件上传
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
formData.append('name', '测试文件');
try {
const response = await axios.post('/api/upload', formData, {
// 监控上传进度
onUploadProgress: (e) => {
const progress = (e.loaded / e.total) * 100;
console.log(`进度:${progress}%`);
},
headers: {
// 无需设置 Content-Type,Axios 会自动添加 boundary
// "Content-Type": "multipart/form-data"
}
});
return response.data;
} catch (error) {
console.error('上传失败:', error);
}
}
2. React/Vue 中使用 FormData
以 React 为例(Vue 逻辑一致):
jsx
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) 只能看到空对象,无法直接查看内容。
解决方案:
javascript
// 方式1:遍历打印
formData.forEach((v, k) => console.log(k, v));
// 方式2:转为对象(仅适用于非文件类型,文件会显示 [object File])
const formDataObj = Object.fromEntries(formData.entries());
console.log(formDataObj);
六、进阶技巧
1. 分片上传大文件
核心思路:将文件按固定大小切分,分批上传,后端合并:
javascript
async function sliceUpload(file) {
const chunkSize = 1024 * 1024; // 1MB 每片
const totalChunks = Math.ceil(file.size / chunkSize);
const fileHash = Date.now() + '-' + file.name; // 唯一标识文件
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
// 切分文件
const start = chunkIndex * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
// 创建 FormData
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', chunkIndex);
formData.append('totalChunks', totalChunks);
formData.append('fileHash', fileHash);
formData.append('fileName', file.name);
// 上传分片
await fetch('/api/upload-chunk', {
method: 'POST',
body: formData,
});
}
// 所有分片上传完成,通知后端合并
await fetch('/api/merge-chunk', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fileHash, fileName, totalChunks }),
});
}
2. 自定义 Blob 上传
除了文件,还可以上传自定义 Blob(如生成的文本、图片):
javascript
// 上传自定义文本 Blob
const textBlob = new Blob(['Hello FormData'], { type: 'text/plain' });
const formData = new FormData();
formData.append('textFile', textBlob, 'custom.txt'); // 第三个参数为文件名
fetch('/api/upload', {
method: 'POST',
body: formData,
});
七、后端接收示例(Node.js/Express)
以 Express 为例,使用 multer 处理 FormData 上传的文件:
javascript
const express = require('express');
const multer = require('multer');
const app = express();
// 配置文件存储路径
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, './uploads'); // 上传文件保存目录
},
filename: (req, file, cb) => {
cb(null, Date.now() + '-' + file.originalname); // 重命名文件
}
});
const upload = 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)。