<input type="file">是唯一标准文件选择控件,仅触发选择不自动上传;必须配合name属性、JavaScript(fetch+FormData)或enctype="multipart/form-data"表单提交才能上传,且需注意accept、multiple、隐藏方式及后端字段名匹配。

HTML 中的 <input type="file"> 是唯一标准起点
浏览器原生不支持直接拖拽或点击上传到服务器,<input type="file"> 是必须的触发控件。它本身只负责选文件,不发请求,后续必须用 JavaScript 配合 FormData 和 fetch 或 XMLHttpRequest 才能真正上传。
常见错误是只写 <input type="file"> 就以为“能上传了”,结果点击没反应或表单提交时丢失文件 —— 因为默认提交会走传统 form 表单刷新,而文件字段在没有 enctype="multipart/form-data" 时根本不会被发送。
- 务必加
accept="image/*"限制类型(如需支持 WebP 还得显式写accept="image/png,image/jpeg,image/webp") - 加
multiple属性才允许多选;不加则每次只能选一张 - 不要用
display: none隐藏原生 input 后用按钮模拟点击 —— 某些 iOS 版本会拒绝触发文件选择器
用 fetch + FormData 上传图片最简洁可靠
相比 XMLHttpRequest,fetch 更现代、语法更干净,且天然支持 FormData。关键点在于:必须把文件对象(File)append 到 FormData,不能直接传 FileList 或字符串路径。
示例逻辑:
立即学习“前端免费学习笔记(深入)”;
const input = document.querySelector('input[type="file"]');
input.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file || !file.type.startsWith('image/')) return;
<p>const formData = new FormData();
formData.append('image', file); // 注意:'image' 是后端接收的字段名</p><p>try {
const res = await fetch('/api/upload', {
method: 'POST',
body: formData // 不要设 Content-Type!浏览器会自动设为 multipart/form-data 并带上 boundary
});
console.log(await res.json());
} catch (err) {
console.error(err);
}
});容易踩的坑:
- 手动设置
headers: { 'Content-Type': 'multipart/form-data' }→ 导致上传失败,因为 boundary 丢失 - 传
e.target.files整个对象而不是e.target.files[0]→ 后端收不到文件(除非后端明确支持数组) - 没检查
file.size就上传 → 大图可能超服务端限制,建议前端先if (file.size > 5 * 1024 * 1024) alert('图片不能超过 5MB')
预览图片用 URL.createObjectURL,别读取 base64
用户选完图片后想立刻看到缩略图?用 URL.createObjectURL(file) 设置 <img src> 最快,内存开销小,也不触发编码延迟。比 FileReader.readAsDataURL 转 base64 更轻量,尤其对大图明显。
注意:createObjectURL 返回的是临时 URL,用完记得释放,否则内存泄漏:
let currentPreviewUrl = null;
<p>function updatePreview(file) {
if (currentPreviewUrl) URL.revokeObjectURL(currentPreviewUrl);
currentPreviewUrl = URL.createObjectURL(file);
document.getElementById('preview').src = currentPreviewUrl;
}- 不调
revokeObjectURL→ 页面长期持有引用,浏览器无法回收文件内存 - 在
change事件里反复调用没清理 → 预览图可能错乱,或旧预览残留 - 想压缩再上传?那是另一层逻辑,
createObjectURL只管预览,和上传内容无关
后端接收名称和字段必须严格匹配
前端 formData.append('avatar', file),后端就必须按 avatar 这个字段名去解析。Node.js 的 multer 要写 upload.single('avatar');Python Flask 用 request.files.get('avatar');PHP 是 $_FILES['avatar']。名字错一个字符,后端就收不到。
另一个常被忽略的点:input 的 name 属性不影响 FormData.append,它只在传统表单提交时起作用。所以如果你混合使用(比如表单 submit + JS 上传),两个地方的字段名最好保持一致,避免混淆。
如果后端返回 400 或空文件,第一反应不是改前端代码,而是抓包看请求体里有没有 Content-Disposition: form-data; name="xxx" 这行,以及 name 值是否和后端期待的一致。



















