跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端

前端文件上传核心原理与实战详解

前端文件上传全流程。涵盖 HTML input 标签基础、File 对象属性、多文件及文件夹选择、文件类型大小校验、图片视频本地预览(URL.createObjectURL)、FormData 封装与 AJAX 请求(XHR/Axios)、以及大文件分片上传方案。包含原生实现与主流框架用法,解决跨域、进度监控等常见问题,适合前端开发者掌握核心知识点。

FlinkHero发布于 2026/4/5更新于 2026/7/2857 浏览

前言

文件上传是前端开发中高频且核心的业务能力,几乎所有中后台系统、用户中台、内容平台都离不开该功能,如头像上传、Excel 导入、附件提交、视频/图片发布等。前端文件上传并非简单的表单提交,而是涉及 HTML 基础语法、JavaScript 核心 API、浏览器兼容性、大文件分片、断点续传、进度监控、文件校验、跨域处理、文件预览等多维度的综合知识点。

本文将从基础原理到高级实战,从原生实现到主流方案,详细讲解前端文件上传的核心知识点。

一、文件上传的核心前置知识:HTML 核心上传标签

前端文件上传的根基是 HTML 提供的两个核心标签/属性,所有上传逻辑都基于它们实现,重中之重,必须先掌握:

1. 核心标签:

这是前端文件上传的唯一入口,专门用于让用户选择本地文件,是实现文件上传的核心标签,无它不可。

2. 核心属性:files

当用户通过 选择文件后,这个 DOM 元素会自动生成一个只读属性 files,它的本质是:一个【FileList 文件列表对象】。

  • FileList 是一个类数组对象,里面每一个成员都是一个 File 对象
  • 每一个 File 对象,就代表用户选择的一个本地文件,包含了文件的所有信息(名称、大小、类型、最后修改时间等)
3. File 对象 核心属性(开发高频必用)

每一个选中的文件,都是一个 File 实例,自带以下只读属性,可直接获取,无兼容性问题:

file.name; // 文件名(如:头像.png),包含扩展名
file.size; // 文件大小,单位 字节 (Byte) 1MB = 1024KB = 1024*1024 Byte
file.type; // 文件的 MIME 类型(如:image/png、application/pdf、text/plain)
file.lastModified; // 文件最后修改的时间戳

二、核心属性:multiple 实现「多文件上传」、webkitdirectory 开启文件夹选择(H5 新特性)

multiple:

默认只能选择一个文件,想要实现「按住 Ctrl/Shift 选择多个文件」的多文件上传,只需要加一个属性即可:

<!-- 单文件上传(默认) -->
<input type="file">
<!-- 多文件上传(核心:加 multiple 属性) -->
<input type="file" multiple>

关键特性:

  • 加了 multiple 后,input.files 里会包含所有选中的文件,长度 input.files.length 就是选中的文件数量
  • 不加 multiple,input.files.length 永远是 1(选中一个文件时)
webkitdirectory:

作用:支持用户直接选择整个文件夹,浏览器会自动解析文件夹内的所有文件(包含子文件夹的文件),适配「批量上传大量文件」的业务场景,兼容 Chrome、Edge、Safari 等现代浏览器:

<!-- 选择整个文件夹 -->
<input type="file" webkitdirectory />

三、文件上传的核心 JS 逻辑:监听文件选择事件 change

1. 核心逻辑

用户「选择文件」这个动作,会触发 的 change 事件,我们只需要监听这个事件,就能在事件回调中拿到用户选中的所有文件(input.files),这是所有文件上传的入口逻辑。

2. 基础示例:获取选中的文件信息(单文件)
<input type="file">
<script>
const fileInput = document.querySelector('#fileInput');
// 监听文件选择事件
fileInput.addEventListener('change', function() {
    // 1. 判断是否选中了文件
    if (this.files.length === 0) {
        console.log('未选择任何文件');
        return;
    }
    // 2. 获取选中的文件(单文件:取 files[0])
    const file = this.files[0];
    // 3. 打印文件的所有核心信息
    console.log('文件名:', file.name);
    console.log('文件大小:', file.size, '字节');
    console.log('文件类型:', file.type);
    console.log('最后修改时间:', new Date(file.lastModified));
})
</script>
3. 多文件选择:获取所有选中的文件
<input type="file" multiple>
<script>
const fileInput = document.querySelector('#fileInput');
fileInput.addEventListener('change', function() {
    const fileList = this.files;
    console.log('选中的文件总数:', fileList.length);
    // 遍历所有选中的文件
    for(let i = 0; i < fileList.length; i++) {
        const file = fileList[i];
        console.log(`第${i+1}个文件:`, file.name, file.size);
    }
})
</script>

四、开发高频需求:文件类型 / 大小 限制

实际开发中,一定会限制文件的类型和大小(比如:只能传图片、图片不能超过 2MB;只能传 PDF,不能超过 10MB),这个功能是前端文件上传的标配,核心实现逻辑就是:通过 File 对象的属性做判断,不符合则提示用户并阻止后续操作。

案例 1:限制「只能上传图片文件」+「文件大小≤2MB」
<input type="file" accept="image/*">
<script>
const fileInput = document.querySelector('#fileInput');
fileInput.addEventListener('change', function() {
    if (!this.files.length) return;
    const file = this.files[0];
    const maxSize = 2 * 1024 * 1024; // 2MB 的字节数
    // 1. 判断文件类型:是否为图片
    if (!file.type.startsWith('image/')) {
        alert('⚠️ 只能上传图片格式的文件!');
        this.value = ''; // 清空选中的文件,重要!
        return;
    }
    // 2. 判断文件大小:是否超过 2MB
    if (file.size > maxSize) {
        alert('⚠️ 文件大小不能超过 2MB!');
        this.value = ''; // 清空选中的文件,重要!
        return;
    }
    // 验证通过,后续执行上传逻辑
    console.log('文件验证通过,可上传:', file.name);
})
</script>
锦上添花:accept 属性(前端筛选,提升体验)

上面代码中用到了 ,这个属性的作用是:打开文件选择框时,默认只展示指定类型的文件,提升用户体验。

  • 只显示图片:accept="image/*"
  • 只显示 PDF:accept=".pdf"
  • 只显示 Word/Excel:accept=".doc,.docx,.xls,.xlsx"
  • 只显示视频:accept="video/*"

❗ 注意:accept 只是前端筛选,用户可以手动选择「所有文件」,所以必须配合 JS 的 File.type 判断,双重验证才安全!

案例 2:多文件上传 - 批量验证
<input type="file" multiple accept="image/*">
<script>
const fileInput = document.querySelector('#fileInput');
fileInput.addEventListener('change', function() {
    const fileList = this.files;
    if (!fileList.length) return;
    const maxSize = 2 * 1024 * 1024;
    let isValid = true;
    for(let file of fileList) {
        if (!file.type.startsWith('image/')) {
            alert(`⚠️ 文件【${file.name}】不是图片格式!`);
            isValid = false;
            break;
        }
        if (file.size > maxSize) {
            alert(`⚠️ 文件【${file.name}】大小超过 2MB!`);
            isValid = false;
            break;
        }
    }
    if (isValid) {
        console.log('所有文件验证通过,可批量上传');
    } else {
        this.value = ''; // 清空所有选中的文件
    }
})
</script>

五、开发高频需求:文件预览(图片 / 视频)

用户选择文件后,在「点击上传」之前,预览选中的文件(比如:图片预览、视频预览)是非常常见的需求,能极大提升用户体验。前端实现文件预览的核心是:URL.createObjectURL(file) 这个 API,也是浏览器原生提供的 BOM 方法。

1. 核心 API 说明:URL.createObjectURL(file)
  1. 作用:接收一个 File 文件对象,返回一个临时的、唯一的 blob 本地预览地址
  2. 特点:这个地址只在当前页面有效,页面刷新 / 关闭后自动失效,不会占用服务器资源
  3. 配套:用完之后可以调用 URL.revokeObjectURL(url) 释放内存(非必须,但推荐)
2. 案例 1:图片文件预览(最常用)
<!-- 选择文件 -->
<input type="file" accept="image/*">
<!-- 预览容器 -->
<div>
    <img id="previewImg" alt="图片预览">
</div>
<script>
const fileInput = document.querySelector('#fileInput');
const previewImg = document.querySelector('#previewImg');
let oldUrl = '';
fileInput.addEventListener('change', function() {
    if (!this.files.length) {
        previewImg.style.display = 'none';
        return;
    }
    const file = this.files[0];
    if (!file.type.startsWith('image/')) {
        alert('只能上传图片!');
        return;
    }
    // 释放上一次的预览地址,节省内存
    if (oldUrl) {
        URL.revokeObjectURL(oldUrl);
    }
    // 生成预览地址
    const previewUrl = URL.createObjectURL(file);
    oldUrl = previewUrl;
    // 赋值给 img 标签展示
    previewImg.src = previewUrl;
    previewImg.style.display = 'block';
})
</script>
3. 案例 2:视频文件预览

和图片预览逻辑完全一致,只是把 文章配图 换成 即可:

<input type="file" accept="video/*">
<div>
    <video id="previewVideo" controls></video>
</div>
<script>
const fileInput = document.querySelector('#fileInput');
const previewVideo = document.querySelector('#previewVideo');
let oldUrl = '';
fileInput.addEventListener('change', function() {
    if (!this.files.length) return;
    const file = this.files[0];
    if(oldUrl) URL.revokeObjectURL(oldUrl);
    const previewUrl = URL.createObjectURL(file);
    previewVideo.src = previewUrl;
    previewVideo.style.display = 'block';
})
</script>

六、文件上传的核心:通过 AJAX 把文件传给后端(重中之重)

前面所有的操作都是「前端本地操作」,真正的文件上传是把本地文件通过网络发送到后端服务器,这个过程必须通过 AJAX 请求实现(原生 XHR /axios 都可以)。

1. 核心注意点:文件上传的请求格式

文件属于二进制数据,不能用普通的 JSON 格式传参!前端上传文件时,必须把请求体的格式设置为:multipart/form-data,这是 HTTP 协议规定的「文件上传专用格式」。

2. 关键对象:FormData

浏览器原生提供的 FormData 对象,是实现文件上传的「核心载体」,它的作用是:

  1. 可以像「表单」一样,存放普通键值对参数 + 二进制文件对象
  2. 自动将请求的 Content-Type 设置为 multipart/form-data,无需手动设置
  3. 兼容所有浏览器,无兼容性问题
FormData 核心方法
const fd = new FormData();
fd.append('key', value); // 追加参数/文件,核心方法!
// 示例:
fd.append('username', '张三'); // 普通字符串参数
fd.append('avatar', file); // 文件参数(key 是后端约定的字段名,value 是 File 对象)

七、两种主流上传实现方案

方案一:原生 XMLHttpRequest (XHR) 上传文件(无依赖,推荐原生)

无任何第三方库,纯原生 JS 实现,兼容性最好,面试 / 开发都常用,必学!

<input type="file" accept="image/*">
<button>点击上传</button>
<script>
const fileInput = document.querySelector('#fileInput');
const uploadBtn = document.querySelector('#uploadBtn');
let file = null;

// 1. 先拿到选中的文件
fileInput.addEventListener('change', function() {
    if (this.files.length) {
        file = this.files[0];
        uploadBtn.disabled = false;
    } else {
        file = null;
        uploadBtn.disabled = true;
    }
})

// 2. 点击上传按钮,发送请求
uploadBtn.addEventListener('click', function() {
    if (!file) return;
    // 1. 创建 FormData 对象,封装参数和文件
    const fd = new FormData();
    fd.append('avatar', file); // 后端接收的文件字段名:avatar
    fd.append('userId', 1001); // 可追加任意普通参数
    
    // 2. 创建 XHR 对象
    const xhr = new XMLHttpRequest();
    
    // 3. 配置请求:POST + 后端上传接口地址
    xhr.open('POST', 'http://localhost:3000/api/upload', true);
    
    // 4. 监听上传进度(可选,超实用!)
    xhr.upload.onprogress = function(e) {
        if (e.lengthComputable) {
            // 计算上传进度百分比
            const percent = (e.loaded / e.total) * 100;
            console.log(`上传进度:${percent.toFixed(2)}%`);
        }
    }
    
    // 5. 监听请求完成
    xhr.onload = function() {
        if (xhr.status >= 200 && xhr.status < 300) {
            const res = JSON.parse(xhr.responseText);
            console.log('上传成功!', res);
            alert('文件上传成功~');
        } else {
            console.error('上传失败!');
            alert('文件上传失败,请重试~');
        }
    }
    
    // 6. 发送请求(核心:传入 FormData 对象)
    xhr.send(fd);
})
</script>
方案二:axios 上传文件(Vue/React 项目主流,推荐)

在 Vue/React 等工程化项目中,我们都用 axios 发送请求,axios 上传文件的逻辑更简洁,底层还是基于 XHR,核心依然是 FormData,开发中 90% 用这个!

前提:已引入 axios 库(CDN /npm 安装均可)

<input type="file" accept="image/*">
<button>axios 上传</button>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
const fileInput = document.querySelector('#fileInput');
const uploadBtn = document.querySelector('#uploadBtn');
let file = null;

fileInput.addEventListener('change', () => file = fileInput.files[0]);

uploadBtn.addEventListener('click', async () => {
    if (!file) return;
    const fd = new FormData();
    fd.append('avatar', file);
    fd.append('userId', 1001);
    try {
        // axios 直接传 FormData 即可,自动处理请求头!
        const res = await axios.post('http://localhost:3000/api/upload', fd, {
            // 可选:监听上传进度
            onUploadProgress: (e) => {
                const percent = (e.loaded / e.total) * 100;
                console.log(`axios 上传进度:${percent.toFixed(2)}%`);
            }
        });
        console.log('上传成功', res.data);
        alert('上传成功');
    } catch (err) {
        console.error('上传失败', err);
        alert('上传失败');
    }
})
</script>

八、文件上传的高阶功能:大文件分片上传

1. 什么是大文件分片上传?

当上传几百 MB / 几 GB 的大文件(比如:视频、压缩包)时,直接上传会有很多问题:

  1. 上传速度慢,容易超时失败
  2. 断网后需要重新上传整个文件,体验极差
  3. 后端服务器可能限制「单次请求的文件大小」,直接拒绝大文件

分片上传的核心思想:把一个 大文件切割成多个小文件(切片),比如:把 1GB 的视频切成 100 个 10MB 的切片,然后并发 / 串行上传所有切片,后端接收完所有切片后,再合并成一个完整的文件。

2. 核心 API:File.slice(start, end)

实现分片的核心是 File 对象的 slice 方法,它的作用是:对文件进行切割,返回一个新的 Blob 对象(和 File 对象兼容)

  • 参数 1 start:开始切割的字节位置(如:0)
  • 参数 2 end:结束切割的字节位置(如:1010241024 → 10MB)
  • 特点:原文件不会被修改,只是生成一个切片副本
3. 分片上传核心流程(极简版核心代码)
// 选择大文件
const fileInput = document.querySelector('#fileInput');
fileInput.addEventListener('change', async () => {
    const file = fileInput.files[0];
    if (!file) return;
    
    // 1. 配置分片规则:每片 10MB
    const chunkSize = 10 * 1024 * 1024;
    
    // 2. 计算总片数
    const totalChunks = Math.ceil(file.size / chunkSize);
    
    // 3. 文件唯一标识(给后端,用于合并切片)
    const fileId = Date.now() + '-' + file.name;
    
    console.log(`文件大小:${file.size},切片数:${totalChunks}`);
    
    // 4. 遍历所有切片,逐个上传
    for (let i = 0; i < totalChunks; i++) {
        // 计算当前切片的起始和结束位置
        const start = i * chunkSize;
        const end = Math.min(start + chunkSize, file.size);
        
        // 切割文件,生成切片
        const chunk = file.slice(start, end);
        
        // 5. 封装切片数据
        const fd = new FormData();
        fd.append('chunk', chunk); // 切片文件
        fd.append('fileId', fileId); // 文件唯一标识
        fd.append('chunkIndex', i); // 当前切片索引
        fd.append('totalChunks', totalChunks); // 总片数
        fd.append('fileName', file.name); // 原文件名
        
        // 6. 上传切片(axios 为例)
        await axios.post('http://localhost:3000/api/upload-chunk', fd);
        console.log(`第${i+1}片上传完成`);
    }
    
    // 7. 所有切片上传完成,请求后端合并文件
    await axios.post('http://localhost:3000/api/merge-chunk', { fileId, fileName: file.name, totalChunks });
    console.log('大文件上传成功!');
})

分片上传的进阶优化:断点续传(记录已上传的切片,下次只传未上传的),核心是「后端返回已上传的切片索引」,前端跳过即可,逻辑基于分片上传拓展,这里不展开。


九、文件上传的常见问题 & 解决方案

1. 问题:选择文件后,再次选择同一个文件,change 事件不触发?

原因: 的 value 值不变时,不会触发 change 事件 ✅ 解决方案:每次触发 change 后,手动清空 input 的 value 值

fileInput.addEventListener('change', function() {
    // 处理文件逻辑...
    this.value = ''; // 关键代码,清空 value
})
2. 问题:后端接收不到文件,或者提示文件为空?

排查清单(按优先级): ① 前端是否用了 FormData 封装文件?(必须用) ② FormData.append('字段名', file) 的「字段名」是否和后端约定的一致?(比如后端要 file,前端传了 avatar) ③ 是否手动设置了 Content-Type: multipart/form-data?(不要手动设置!FormData 会自动处理,手动设置会导致边界符丢失)

3. 问题:上传文件时,浏览器报 CORS 跨域错误?

原因:前端域名和后端接口域名不一致,浏览器的同源策略限制 ✅ 解决方案:后端在接口中配置跨域允许(前端无法解决,必须后端配合),比如:

  • 设置响应头:Access-Control-Allow-Origin: *
  • 允许跨域的请求头、请求方法
4. 问题:文件大小超过后端限制,上传失败?

解决方案: ① 前端提前做文件大小校验,超过则提示用户,不上传 ② 后端修改配置,增大文件上传的大小限制(比如:nginx 的 client_max_body_size、node 的 body-parser 等) ③ 大文件直接用「分片上传」方案


十、文件上传核心知识点总结

  1. 前端文件上传的入口是 ,选中文件后通过 this.files 获取文件列表;
  2. 每个文件都是 File 对象,包含 name/size/type 核心属性,用于文件校验;
  3. 文件预览用 URL.createObjectURL(file),生成本地临时预览地址;
  4. 文件上传的核心是 FormData 对象 + AJAX 请求,请求格式必须是 multipart/form-data;
  5. 原生 XHR 和 axios 都能实现上传,axios 更简洁,是项目主流;
  6. 大文件上传必须用「分片上传」,核心 API 是 File.slice();
  7. 常见坑:同一文件不触发 change、后端收不到文件、跨域,都有固定的解决方案。

以上就是前端文件上传的所有核心内容,从基础到进阶,从原理到实战,覆盖了开发中所有的常见场景,掌握这些内容,你就能应对所有的前端文件上传需求啦!

目录

  1. 前言
  2. 一、文件上传的核心前置知识:HTML 核心上传标签
  3. 1. 核心标签:<input type="file">
  4. 2. 核心属性:files
  5. 3. File 对象 核心属性(开发高频必用)
  6. 二、核心属性:multiple 实现「多文件上传」、webkitdirectory 开启文件夹选择(H5 新特性)
  7. multiple:
  8. webkitdirectory:
  9. 三、文件上传的核心 JS 逻辑:监听文件选择事件 change
  10. 1. 核心逻辑
  11. 2. 基础示例:获取选中的文件信息(单文件)
  12. 3. 多文件选择:获取所有选中的文件
  13. 四、开发高频需求:文件类型 / 大小 限制
  14. 案例 1:限制「只能上传图片文件」+「文件大小≤2MB」
  15. 锦上添花:accept 属性(前端筛选,提升体验)
  16. 案例 2:多文件上传 - 批量验证
  17. 五、开发高频需求:文件预览(图片 / 视频)
  18. 1. 核心 API 说明:URL.createObjectURL(file)
  19. 2. 案例 1:图片文件预览(最常用)
  20. 3. 案例 2:视频文件预览
  21. 六、文件上传的核心:通过 AJAX 把文件传给后端(重中之重)
  22. 1. 核心注意点:文件上传的请求格式
  23. 2. 关键对象:FormData
  24. FormData 核心方法
  25. 七、两种主流上传实现方案
  26. 方案一:原生 XMLHttpRequest (XHR) 上传文件(无依赖,推荐原生)
  27. 方案二:axios 上传文件(Vue/React 项目主流,推荐)
  28. 八、文件上传的高阶功能:大文件分片上传
  29. 1. 什么是大文件分片上传?
  30. 2. 核心 API:File.slice(start, end)
  31. 3. 分片上传核心流程(极简版核心代码)
  32. 九、文件上传的常见问题 & 解决方案
  33. 1. 问题:选择文件后,再次选择同一个文件,change 事件不触发?
  34. 2. 问题:后端接收不到文件,或者提示文件为空?
  35. 3. 问题:上传文件时,浏览器报 CORS 跨域错误?
  36. 4. 问题:文件大小超过后端限制,上传失败?
  37. 十、文件上传核心知识点总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Docker 部署 Ollama 全流程:CPU/GPU 支持与生产环境实践
  • 面向无人机和智能手机的 YOLOv8 模型改进实战—棉叶病虫害多尺度目标检测
  • Git-AI:实现 AI 生成代码归属追踪的 Git 扩展工具
  • TurboDiffusion 环境部署与 WebUI 配置指南
  • Flutter 库 llm_json_stream 的鸿蒙化适配与流式 JSON 解析
  • Lychee-Rerank-MM 本地部署教程:无网依赖图文重排序
  • Linux 文件系统基础操作与路径管理
  • 在 Ubuntu 24.04 上部署 n8n:中文界面、PostgreSQL 与 Ollama 集成
  • SD-Trainer 快速上手:AI 绘画模型微调实战
  • ComfyUI 实战:AI 抠图与背景替换(图片/视频)
  • FPGA 电子时钟设计与实现
  • 前端设计与布局常用术语中英速查指南
  • 教育类论文降重与 AIGC 检测的双重优化策略
  • OpenCode 使用 GitHub Copilot 计费机制分析与优化方案
  • Claude Code 模型参数配置与默认值详解
  • Rockchip Ubuntu24.04 移植 AIC8800 驱动并开启 AP 模式
  • Android 离线语音识别指南:基于 Whisper 实现本地语音转文字
  • C++ 红黑树实现详解:规则、结构与插入查找验证
  • JWT(JSON Web Token)结构化知识体系与工程实践
  • Qwen-Image-2512 模型详解:ComfyUI 与 WebUI 部署实战

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online