JavaScript无法直接读写本地文件,需通过获取用户选择的File对象,用FormData封装后通过fetch或XMLHttpRequest上传;注意兼容性、服务端配置及错误处理。
JavaScript 本身不能直接读写本地文件系统,但可以通过浏览器提供的标准 API 实现用户主动选择并上传文件。核心是 File、FileList、FormData 和 XMLHttpRequest 或 fetch 的组合使用。
获取用户选中的文件这是最常见、最可控的入口。用户点击后触发 change 事件,从 event.target.files 中拿到 FileList 对象。
注意:files 是只读的类数组对象,不是真正的 Array,不能直接用 map 等方法 —— 需转成数组再操作。
multiple 属性决定是否允许多选()
accept 属性可限制类型(如 accept="image/*,.pdf"),但只是提示,不阻止用户绕过files[0] 是第一个 File 实例,它继承自 Blob,有 name、size、type、lastModified 等属性const input = document.querySelector('input[type="file"]');
input.addEventListener('change', (e) => {
const files = Array.from(e.target.files); // 转为数组便于处理
files.forEach(file => {
console.log(file.name, file.size, file.type);
});
});FormData 构建上传数据FormData 是专为表单提交设计的接口,能自动处理文件字段的 Content-Type(包括 boundary),比手动拼接更可靠。它支持追加字符串或 Blob/File。
formData.append('file', file)
formData.append('files', file1)、formData.append('files', file2)
formData.append('userId', '123')
Content-Type 头 —— fetch 或 XHR 发送 FormData 时会自动设置为 multipart/form-data 并生成正确 boundaryconst formData = new FormData();
formData.append('file', input.files[0]);
formData.append('desc', '用户头像');
fetch('/upload', {
method: 'POST',
body: formData // 不要加 headers: { 'Content-Type': ... }
});
fetch 还是 XMLHttpRequest?关键区别在哪两者都能发 FormData,但行为细节不同:
fetch 更简洁,原生支持 Promise,但默认不带 cookie(需显式加 credentials: 'include')XMLHttpRequest 提供更细粒度的上传控制,比如监听 upload.onprogress(fetch 目前无原生进度事件)fetch 不会 reject,需手动检查 response.ok;XHR 的 onerror 只在网络失败时触发,状态码错误仍走 onload
XMLHttpRequest,方便加进度条和断点续传逻辑const xhr = new XMLHttpRequest();
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
console.log(`${(e.loaded / e.total * 100).toFixed(1)}%`);
}
};
xhr.open('POST', '/upload');
xhr.send(formData);很多上传失败不是代码逻辑问题,而是环境或配置疏漏:
multipart/form-data,比如 Express 缺少 multer,Koa 缺少 @koa/multer
client_max_body_size 或 LimitRequestBody
Access-Control-Allow-Origin,且若带 cookie,还需 Access-Control-Allow-Credentials: true 和前端 credentials: 'include'
input[type="file"] 的 click() 触发有限制(需用户手势上下文),不能在异步回调里直接调用 input.click()
fetch,需用 XMLHttpRequest 或引入 polyfill真正难的从来不是“怎么传”,而是服务端能否稳定接收、校验、存储,并在出错时给前端明确反馈。前端上传逻辑越简单,越依赖后端接口设计的健壮性。