
html 文件输入框的 `files` 属性是只读的 filelist 对象,无法直接修改;需通过维护独立的文件数组(如 `selectedfiles`)来实现添加、删除与上传控制。
在前端多文件上传场景中,常见的需求是:用户选择多个文件后,页面展示预览列表,并支持逐个删除某项——但直接操作 的 files 属性(如 splice() 或 delete)无效,因为 input.files 是只读的 FileList 对象,任何对其的修改都不会反映到 dom 或后续上传逻辑中。
✅ 正确做法是:绕过直接修改 input.files,转而用 javaScript 维护一个可变的 File[] 数组(例如 selectedFiles = []),所有 ui 渲染、删除、上传均基于该数组操作。 仅用于初始文件选取,之后不再依赖其 files 属性。
✅ 推荐实现步骤
- 监听 change 事件,将新选文件追加至 selectedFiles 数组(避免覆盖,支持多次选择);
- 构建文件预览列表(含删除按钮),每个按钮绑定对应索引的 removeFile(index);
- removeFile(index) 从 selectedFiles 中移除指定项,并重新渲染列表;
- 上传时遍历 selectedFiles 构造 FormData,而非读取 input.files。
? 完整示例代码
暂无文件
let selectedFiles = []; document.getElementById('fileInput').addEventListener('change', function (e) { if (e.target.files.length === 0) return; // 将新选文件合并进数组(避免重复引用问题) selectedFiles = [...selectedFiles, ...Array.from(e.target.files)]; displayFiles(); document.getElementById('uploadBtn').disabled = false; }); function displayFiles() { const listEl = document.getElementById('fileList'); if (selectedFiles.length === 0) { listEl.innerhtml = '暂无文件
'; return; } listEl.innerHTML = selectedFiles.map((file, idx) => ` ? ${file.name} (${(file.size / 1024).toFixed(1)} KB) `).join(''); } function removeFile(index) { selectedFiles.splice(index, 1); displayFiles(); // 若删空,则禁用上传按钮 if (selectedFiles.length === 0) { document.getElementById('uploadBtn').disabled = true; } } document.getElementById('uploadBtn').addEventListener('click', async function () { if (selectedFiles.length === 0) return; const formData = new FormData(); selectedFiles.forEach(file => { formData.append('files[]', file, file.name); // 第三个参数确保原始文件名 }); try { const res = await fetch('/upload', { method: 'POST', body: formData }); alert(`上传成功!共 ${selectedFiles.length} 个文件。`); selectedFiles = []; // 重置 displayFiles(); } catch (err) { console.error('上传失败:', err); alert('上传出错,请检查网络或服务器状态。'); } });
⚠️ 注意事项
- ❌ 不要尝试 input.files.splice() 或 delete input.files[i] —— 这些操作静默失败,FileList 不可变;
- ✅ 使用 Array.from(input.files) 仅用于一次性读取快照,后续所有操作必须基于你自己的数组;
- ? 上传时若需保留原始文件名,务必在 formData.append() 中显式传入第三个参数(file.name),否则服务端可能收到临时名;
- ? 如需支持拖拽上传、校验(大小/类型)、进度条等,可在 selectedFiles 管理基础上扩展,保持逻辑解耦。
通过这种「数据驱动 UI」的方式,你不仅能可靠地增删文件,还能轻松集成校验、排序、分组等高级功能,是现代文件上传交互的最佳实践。