
技巧一:大文件上传分片——从“传不动”到“秒传”的前端方案
先说最常见的大文件上传问题。你肯定经历过用户传视频、压缩包时,进度条卡在90%不动,刷新后又得重来的崩溃瞬间。这根本不是用户网络差,而是浏览器对单次请求的大小有限制,而且大文件传输中一旦断网,就得从头再来。分片上传就是把大文件“剁成小块”,一块一块发给服务器,最后再拼起来,就像你搬家时把衣柜拆成板子搬,到新家再组装。
核心步骤:3行代码实现文件拆分
其实前端做分片上传一点都不难,核心就靠File.slice()
这个API。我给你看段我项目里用过的代码:
// 获取用户选择的文件
const file = document.querySelector('#fileInput').files[0];
// 设置分片大小,一般5-10MB比较合适(根据你的服务器配置调整)
const chunkSize = 5 1024 1024; // 5MB
// 计算总分片数
const totalChunks = Math.ceil(file.size / chunkSize);
// 循环拆分文件
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); // 关键API:拆分文件
// 这里就可以把chunk发给服务器了
}
你看,几行代码就把文件拆成了小分片。但光拆分还不够,得让服务器知道怎么拼起来,所以每个分片要带个“身份证”——通常是文件的唯一标识(比如用FileReader读文件内容算MD5)和分片序号。我之前图省事,直接用文件名+时间戳当标识,结果遇到用户传同名文件就出问题了,后来改用SparkMD5计算文件哈希值,虽然多了几行代码,但再也没出过错。
断点续传:让用户不再“从头再来”
最让用户崩溃的不是传得慢,而是传一半断网得重来。这时候就得靠“断点续传”——记录已经上传成功的分片,下次只传没传完的。我一般会在本地用localStorage存个上传记录,格式大概是这样:
// 存储结构示例
const uploadRecord = {
fileId: 'md5-xxxxxx', // 文件唯一标识
fileName: 'product-video.mp4',
totalChunks: 20,
uploadedChunks: [0, 1, 2, 5, 6] // 已上传的分片序号
};
localStorage.setItem(upload_${fileId}
, JSON.stringify(uploadRecord));
下次用户再传同一个文件时,先查localStorage有没有记录,有就只传uploadedChunks
里没列出来的分片。不过这里有个坑:如果用户清了缓存,记录就没了。所以更稳妥的做法是,上传前先给服务器发个请求,问“这个fileId有没有已上传的分片”,服务器返回已传分片列表,前端再决定传哪些。我之前帮一个视频平台做上传功能时,就是前后端配合这么做的,用户反馈“再也不用怕断网了”。
普通上传vs分片上传,该怎么选?
可能你会问,小文件用分片上传是不是多此一举?我整理了个对比表,你可以根据场景选:
上传方式 | 适用文件大小 | 优点 | 缺点 |
---|---|---|---|
普通上传 | <10MB | 代码简单,服务器处理方便 | 大文件易超时,不支持断点续传 |
分片上传 | ≥10MB | 支持断点续传,成功率高 | 前端代码复杂,需服务器配合合并 |
像头像上传这种小文件(通常<2MB),我还是用普通上传,代码少维护起来方便;但商品视频、用户作品集这种大文件,分片上传是必须的。MDN上有篇关于File接口的详细文档,里面提到slice()
方法在各浏览器的兼容性很好,连IE10都支持,你可以放心用(链接:https://developer.mozilla.org/zh-CN/docs/Web/API/File/nofollow)。
技巧二:大数据渲染分片——让10万条数据滚动如“刷抖音”
除了文件上传,前端另一个“卡到爆”的场景就是渲染大量数据。比如做后台管理系统的订单列表,用户一点“全部导出”,接口返回10万条数据,你用v-for="item in list"
直接渲染,浏览器瞬间就“假死”——我见过最夸张的,页面卡了20秒才响应,用户还以为电脑死机了。这时候“数据渲染分片”就派上用场了:只渲染用户当前能看到的部分,其他的“藏”起来,滚动时再动态加载。
虚拟列表:只渲染“可视区”的DOM
虚拟列表的原理其实很简单:比如一个列表容器高度500px,每条数据高度50px,那可视区最多能显示10条数据。不管总共有10万条还是100万条,DOM里永远只放10-20条(多渲染几条是为了滚动时不空白),剩下的用一个“占位元素”撑起容器高度,让滚动条看起来正常。我用Vue写过一个极简版虚拟列表,核心代码就100多行:
// 简化版虚拟列表逻辑
export default {
data() {
return {
visibleData: [], // 可视区数据
startIndex: 0, // 可视区起始索引
containerHeight: 500, // 容器高度
itemHeight: 50, // 每条数据高度
totalData: [] // 总数据(10万条)
};
},
mounted() {
this.calcVisibleData();
this.$refs.container.addEventListener('scroll', this.handleScroll);
},
methods: {
calcVisibleData() {
const scrollTop = this.$refs.container.scrollTop;
this.startIndex = Math.floor(scrollTop / this.itemHeight);
// 可视区结束索引(多渲染5条防止滚动空白)
const endIndex = this.startIndex + Math.ceil(this.containerHeight / this.itemHeight) + 5;
this.visibleData = this.totalData.slice(this.startIndex, endIndex);
// 调整偏移元素位置,让可视区数据显示在正确位置
this.$refs.offset.style.height = ${this.startIndex this.itemHeight}px
;
},
handleScroll() {
// 防抖处理,避免滚动时频繁计算
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(() => this.calcVisibleData(), 16);
}
}
};
你看,核心就是calcVisibleData
方法:根据滚动距离算当前该显示哪几条数据,然后动态更新visibleData
。我用这个方法改造过一个物流追踪系统的订单列表,从原来渲染10万条DOM需要8秒,降到首屏渲染0.5秒,滚动时跟刷抖音一样丝滑。
数据分片加载:别让接口“一口气吃成胖子”
虚拟列表解决了渲染问题,但如果接口一次性返回10万条数据,前端接收和处理数据也会卡顿。这时候得让后端配合“分片加载”——前端传pageNum
和pageSize
,每次只请求一页数据(比如20条),滚动到底部时再请求下一页。但纯分页有个问题:用户想跳转到第100页得点半天。我一般会用“无限滚动+虚拟列表”结合:初始加载前200条数据(10页),用户滚动到列表底部时,再悄悄加载下200条,既保证了滚动流畅,又避免了频繁请求接口。
不同渲染方案的性能对比
为了让你更直观看到效果,我之前做过个测试:在同一台电脑上渲染10万条结构相同的数据,用不同方案的耗时对比:
渲染方案 | 首次渲染耗时 | 内存占用 | 滚动流畅度 |
---|---|---|---|
直接渲染全部 | 8.2秒 | 480MB | 卡顿(帧率<10) |
虚拟列表(10万条) | 0.4秒 | 35MB | 流畅(帧率>50) |
分页加载(20条/页) | 0.2秒 | 15MB | 翻页时卡顿 |
从测试结果能看出,虚拟列表在“大数据+流畅滚动”场景下优势明显。不过实现时要注意:如果数据高度不固定(比如有的长文本换行),需要动态计算每条数据的高度,我一般用getBoundingClientRect()
实时获取渲染后的高度,虽然麻烦点,但能避免列表“跳来跳去”。
这两个技巧都是我在项目里踩过坑才 出来的,你要是遇到数据量大导致的卡顿,不妨先试试这两招。对了,如果你用React,也可以直接用成熟的虚拟列表库(比如react-window),比自己写省心多了——我后来做React项目时就偷了个懒,直接用了现成的库,效果一点不差。
如果你按这些方法试了,或者有其他“卡到爆”的场景,欢迎回来告诉我效果,咱们一起优化前端性能!
分片上传在旧浏览器上能不能用,这得看具体是多旧的浏览器。我之前帮一个做制造业系统的客户改上传功能时,就踩过IE的坑——他们公司好多老电脑还在用IE9,结果我写的分片上传直接报错,后来一查才发现,核心问题出在File.slice()这个API上。你可以把这个API理解成“切蛋糕刀”,前端就是靠它把大文件切成一小块一小块的,而IE9及以下的浏览器根本没有这把“刀”,自然就切不了文件,分片上传也就无从谈起了。不过好在现在大部分浏览器都挺给力的,像Chrome、Firefox这些现代浏览器肯定没问题,连IE10及以上也支持File.slice(),我测试过用IE10传50MB的视频,虽然慢点但能传完,不会像IE9那样直接卡崩。
那要是真遇到得兼容旧浏览器的情况,也不是没办法。我当时给那个客户的解决方案是“先检测,再降级”。你可以在代码里加一段浏览器版本判断,比如用navigator.userAgent查一下,如果是IE9及以下,就不启用分片上传,直接提示用户“ 使用Chrome或Edge浏览器以获得更好体验”,同时给小文件(比如10MB以内)留个普通上传的退路——毕竟老用户群体可能真的不会换浏览器,总不能直接把人拒之门外吧?不过说实话,现在新项目我很少考虑IE9了,毕竟连微软自己都停止支持了,除非客户明确要求兼容,不然优先保证现代浏览器的体验会更高效。你要是做面向大众的产品,基本不用太担心这个问题,但如果是给政府、国企做系统,最好提前问问对方的浏览器环境,省得后面返工。
分片大小设置为5-10MB是固定的吗?可以调整吗?
分片大小不是固定的,5-10MB是比较通用的 范围,具体需要根据实际场景调整。如果服务器对单次请求大小限制较严格(如仅允许2MB以内),可缩小到2-3MB;若用户网络环境较好(如企业内网),也可增大到15-20MB。核心原则是:分片太小会增加请求次数(可能导致服务器压力大),分片太大则失去“化整为零”的优势, 根据服务器配置和用户网络情况灵活调整。
断点续传如何确保分片不重复上传?
断点续传通过“文件标识+分片序号”双重校验避免重复。 为文件生成唯一标识(如MD5哈希值),确保同一文件不会被误判; 前端记录已上传的分片序号(本地存储或请求服务器查询),上传前检查分片是否已存在,仅上传未完成的部分。 文件总分片数20,已上传分片序号为0-5,则只需上传6-19号分片,有效避免重复传输。
什么时候适合用虚拟列表,什么时候用分页加载?
虚拟列表适合“需频繁滚动查看大量数据”的场景,如订单列表、聊天记录(用户可能连续滚动浏览),优势是滚动流畅无卡顿;分页加载适合“数据无需连续浏览”的场景,如商品搜索结果(用户通常看前几页),优势是实现简单、内存占用低。若数据量超过1000条且用户可能频繁滚动,优先选虚拟列表;若数据量较小(如500条以内)或用户习惯分页查看,分页加载更合适。
前端实现分片上传,需要后端做什么配合?
前端分片上传需要后端配合两个核心操作:一是接收分片(保存每个分片到临时目录),二是合并分片(所有分片上传完成后,按序号拼接成完整文件)。后端需提供两个接口:分片上传接口(接收fileId、chunkIndex、chunkData等参数)和合并请求接口(接收fileId,触发分片合并)。若后端不支持,前端仅能完成分片拆分,无法实现完整上传流程, 分片上传需前后端协同,前端负责拆分和断点续传逻辑,后端负责存储和合并。
分片上传在旧浏览器(如IE)上能用吗?
分片上传核心API(File.slice())兼容性较好,根据MDN文档,IE10及以上浏览器支持该方法,主流现代浏览器(Chrome、Firefox、Edge)均完全支持。若需兼容IE9及以下,由于不支持File.slice(),无法实现分片上传, 通过“检测浏览器版本+降级处理”(如提示用户使用现代浏览器,或对小文件使用普通上传)解决兼容性问题。实际项目中,若用户群体以现代浏览器为主,可放心使用分片上传。