← 全部材料 场景题
SCENARIO · 10 个高频场景

先问约束,再给方案

场景题考的不是"你会不会写",而是你会不会问。同一个需求,10MB 和 10GB 的答案完全不同。这份每个场景都先给判断依据,再给实现和它的边界。

01先问四个问题数据量、网络、能否改服务端、失败代价。答完方案基本就定了。
02说清瓶颈分片解决的是失败成本、虚拟列表解决的是 DOM 数量,别把手段当目的。
03主动报边界能说出"这个方案到多大规模就不行了",比假装完美可信得多。
1

这类题怎么答:先问清约束★

没有约束就没有最优解
场景题考的不是"你会不会写",而是"你会不会问"。上来就给方案,对方会觉得你没做过真需求。正确姿势是先问四个问题:数据量多大、网络条件如何、能不能改服务端、失败重试的成本高不高。答完这四个,方案基本就定了。
以"大文件上传"为例,四个问题怎么问Q1 文件多大?单文件上限、并发上传数 10MB 以内 → 不用分片,直接传 100MB 以上 → 必须分片 + 断点续传 Q2 网络环境?内网还是公网、移动端多不多 内网稳定 → 可以少做重试 移动端弱网 → 必须做断点续传和分片重传 Q3 服务端能不能改?有没有对象存储 能改 → 用直传对象存储 + 预签名 URL,前端不经过业务服务器 不能改 → 只能在现有接口上做分片 Q4 失败的代价?用户会不会重传整个文件 会 → 秒传、断点续传是刚需 不会 → 简单校验就够了 问完这四个,"标准答案"反而没那么重要了。
面试怎么答场景题我更关注先确认约束。我会先问四件事:数据量多大、网络条件怎么样、服务端和存储能不能改、失败重试的代价高不高。因为同样是文件上传,10MB 以内直接传就够了,100MB 以上才需要分片和断点续传;服务端能改的话可以直接预签名传对象存储,前端不经过业务服务器。问清这些再给方案,才不会给出一个用不上的架构。
2

大文件上传:分片、并发与顺序★

核心是"把一次大失败拆成多次小失败"
分片解决的不是速度,是失败成本:传 1GB 传到 999MB 断了要重来,切成 5MB 一片就只重传那一片。所以设计的重点是分片大小、并发数、以及失败片的追踪,而不是"怎么切开"。
◐ 可交互:改分片大小,看"一片失败"要重传多少
文件 100MB · 第 5 片失败一次
分片数 / 请求数20
失败重传5 MB
若不分片,失败要重传100 MB
分片解决的不是速度,是失败成本。切得越细,单次失败要重传的越少。
分片大小的权衡太小(1MB) 请求数暴涨:1GB = 1000 个请求 每个请求都有头部开销、连接复用受限 服务端合并压力大 太大(50MB) 一片失败重传 50MB,弱网下几乎必然反复失败 浏览器内存占用高(slice 是引用,但 FormData 序列化会读进内存) 常见值:2-10MB 移动端偏小(2-5MB),桌面端可以 5-10MB 更聪明的做法:根据网络速度动态调整(首片测速)
面试怎么答大文件上传的核心是把一次大失败拆成多次小失败,所以分片解决的是失败成本不是速度。设计要点有三个:分片大小通常 2-10MB,移动端偏小;并发数用并发池控制在 3-6,不能直接 Promise.all 否则几百个请求同时发会把内存撑爆;每个分片独立重试用指数退避。合并一定要用服务端的能力,前端不拼二进制,取消上传要同时清理服务端的临时分片。
3

断点续传与秒传★

一个靠 hash,一个靠查询
两个能力都建立在文件内容 hash 上:秒传是"这个 hash 服务端已经有了,直接返回成功";断点续传是"这个 hash 已经传了 3 片,你从第 4 片开始"。所以关键在于怎么快速算出 hash。
两段流程【秒传】 1. 前端算 hash 2. 请求服务端 /api/exists?hash=xxx 3. 命中 → 直接把文件记录关联到当前用户,返回成功 4. 未命中 → 走正常上传 注意:秒传要做权限校验, 不能因为"内容相同"就把别人的文件给你。 【断点续传】 1. 前端算 hash 2. 请求 /api/check?hash=xxx 3. 服务端返回 { uploadId, uploaded: [0,1,2] } 4. 前端跳过已有分片,从第 3 片开始 5. 全部完成后调 merge 关键:分片进度要能持久化 浏览器关闭再打开还能续传 → 把 hash 和进度写进 localStorage 或者完全依赖服务端的 uploaded 列表(更可靠)
面试怎么答秒传和断点续传都依赖文件内容 hash。秒传是算完 hash 先问服务端"这个文件有没有",有就直接关联返回成功;断点续传是问"这个 hash 传了多少片",返回已上传的分片列表,前端跳过这些从下一个开始。关键难点是 hash 计算,全量 SHA-256 对大文件太慢还会卡主线程,所以实际做法是抽样取头中尾加上文件大小算,放到 Worker 里;但抽样有碰撞风险,服务端要做二次校验,否则会出现秒传成功却拿到别人文件的问题。
4

扫码登录:两种实现★

PC 端怎么知道手机上确认了
扫码登录的本质是PC 端拿不到手机上的登录态,需要一个中间人传递。流程是:PC 生成一个临时票据并轮询,手机扫码后用自己的登录态"授权"这个票据,PC 轮询到授权结果再用票据换 token。关键设计是票据要短时效、一次性、且不能直接放 token。
完整流程1. PC 请求 /api/qrcode → { ticket: "abc123", expire: 120 } → 同时渲染二维码,内容是一个短链 ?ticket=abc123 2. PC 开始轮询 /api/qrcode/status?ticket=abc123 状态:waiting(未扫)→ scanned(已扫待确认)→ confirmed(已确认) 3. 手机扫码,打开短链(此时手机已有登录态) → 服务端记录 ticket 对应的用户,状态置为 scanned 4. 手机上点"确认登录" → 服务端把 ticket 置为 confirmed,同时生成一个一次性 code 5. PC 轮询到 confirmed,拿到 code → 请求 /api/login/exchange { code } → 返回真正的 token 6. 二维码作废,ticket 标记已使用
面试怎么答扫码登录的核心问题是 PC 端拿不到手机上的登录态,需要一个中间票据传递。流程是 PC 请求生成 ticket 并渲染二维码,然后轮询状态;手机扫码后用自己已有的登录态把 ticket 标记为已扫,用户确认后置为已确认;PC 轮询到确认后用一次性 code 换真正的 token。关键是 ticket 要随机、短时效、只能用一次,并且不能直接返回 token,否则任何拿到 ticket 的人都能登录。手机上还要显示设备信息让用户确认,防止扫到钓鱼码。
5

前端水印

防的是顺手截图,不是专业破解
前端水印做不到真正防泄露,它防的是"顺手截图发出去"和"截图上没有归属信息"。做法有两类:DOM 水印(一层 div 铺文字,可被删)和 Canvas 水印(画成图片再平铺,删了能监听重建)。真正的防护要靠服务端加盲水印和审计日志。
面试怎么答前端水印防不住专业破解,它防的是顺手截图和"截图没有归属信息"。实现上是生成一张带文字的 Canvas 图片作为背景平铺,铺在一个 pointer-events: none 的全屏层上,再用 MutationObserver 监听被删除后重建。要提高门槛可以做成伪元素或者背景图的一部分。水印内容放用户名、id 和时间戳,目的是泄露后可追溯。真正的防护要靠服务端在图片上打标或者盲水印,加上访问审计日志。
6

无限滚动:什么时候加载下一页★

判断点选错就体验崩了
无限滚动的核心是什么时候触发加载。三种做法:监听 scroll 算距离(最灵活)、IntersectionObserver 观察哨兵元素(最简单)、以及"预加载提前量"(用根容器的 rootMargin 实现)。推荐后两种。
面试怎么答无限滚动的关键是触发时机。推荐用 IntersectionObserver 观察一个哨兵元素,而不是监听 scroll 算距离,因为 scroll 事件频率高还要算 scrollHeight 会强制同步布局引起掉帧,而 IO 在合成阶段判断不占主线程。要设置 rootMargin 提前 200px 左右触发,用户滚到底时数据已经到了,看不到加载态。必须加 loading 锁防重复触发,没有更多数据时 disconnect 掉观察器。
7

虚拟列表:只渲染看得见的那些★

一万行表格为什么能秒开
虚拟列表的核心是只渲染视口内的几十个节点,用空白把滚动高度撑起来。所以关键参数只有三个:每项高度、视口高度、当前滚动位置。难度全在"高度不固定"和"快速滚动时白屏"这两件事上。
三种虚拟化方案【固定高度】最简单的 已知每项 H,直接 start = scrollTop / H 适合:表格、聊天记录、固定行高的日志 【不定高度 + 预估】 先按预估值排,渲染后测量真实高度,修正偏移表 需要一个数组记录每项的实际偏移 适合:列表项内容长短不一(评论、商品卡片) 【不定高度 + 滚动锚定】 渲染后如果测量值变了,调整 scrollTop 保持视觉不跳 难点:用户正在滚动时不能动,否则会抖 这是所有虚拟列表库最复杂的部分
面试怎么答虚拟列表只渲染视口内的节点,用总高度把滚动条撑出来,然后用 transform 把实际内容平移到正确位置。三个核心参数是每项高度、视口高度、滚动位置。上下要预留几个 buffer 防止快速滚动时白屏,位移用 transform 而不是 top 因为不触发布局。固定行高直接用除法算,不定高度要先预估再测量修正偏移表,最难的部分是测量值变化时不能让它视觉上跳动。
8

图片裁剪与压缩

上传前先瘦身
前端压缩的价值是省上传时间、省服务端存储和带宽。做法是画到 Canvas 再 toBlob 导出。三个关键点:用 canvas 重绘而不是直接改 quality、注意 EXIF 方向、以及大图要在 Worker 里处理。
面试怎么答前端压缩是把图片画到 Canvas 再导出,价值是省上传时间和存储带宽。三个要点:用 createImageBitmap 而不是 new Image,因为它会自动处理 EXIF 方向,否则手机照片会是躺着的;导出用异步的 toBlob 而不是同步的 toDataURL,后者对大图会阻塞主线程且 base64 比二进制大三分之一;尺寸要按 maxSide 等比缩放,格式优先 WebP 再回落 JPEG。大图处理要放 Worker 避免卡界面。
9

Excel 导入导出

前端生成还是后端生成
判断标准是数据量和是否已经有服务端。导出:小数据(几千行)前端用 SheetJS 生成,大数据(十万行)必须后端流式生成,前端生成会直接把浏览器内存撑爆。导入:小文件前端解析校验后提交,大文件直接上传由后端解析。
四种组合【导出-前端生成】SheetJS / exceljs 优点:不占服务端资源,能导出"当前页面筛选后的数据" 缺点:十万行以上浏览器会卡死甚至崩溃 适合:几千行以内、需要在客户端做格式处理 【导出-后端生成】服务端流式写 xlsx 优点:能处理百万行,内存可控 缺点:占服务端 CPU,大文件要异步任务 + 下载通知 适合:大数据量、定时报表 【导出-纯前端流式】手写 CSV CSV 其实不需要 SheetJS,字符串拼接就行 缺点:没有格式、多 sheet、公式;中文要用 BOM 防乱码 适合:只要数据不要样式的场景 【导入】 小文件(<5MB):前端用 SheetJS 解析,先做格式和必填校验,把错误行标出来让用户改 大文件:直接上传,后端异步解析,用轮询或 WebSocket 通知进度
面试怎么答Excel 导出选前端还是后端,看数据量。几千行以内可以前端用 SheetJS 生成,优点是能导出当前筛选后的数据、不占服务端资源;十万行以上必须后端流式生成,前端生成会撑爆内存。导入方面小文件前端解析做格式校验给即时反馈,但后端必须重新校验,因为前端校验能被绕过。导出要注意两个细节:CSV 要加 BOM 否则中文乱码,长数字要设成文本格式否则会被转成科学计数法。
10

拖拽排序

HTML5 拖拽 API 为什么不好用
原生 HTML5 拖拽(draggable + dragstart 那一套)在移动端完全不支持,而且拖拽影像是浏览器自己画的,样式没法控制。所以实际项目几乎都用 Pointer Events 自己算,或者用 dnd-kit / SortableJS 这类库。
面试怎么答原生 HTML5 拖拽 API 在移动端不支持,而且拖拽影像样式不可控,所以实际都用 Pointer Events 自己实现或者用 dnd-kit 这类库。实现要点是 pointerdown 时调 setPointerCapture 让后续事件都被这个元素捕获,否则鼠标快速移出元素就会中断;拖动过程中只改 transform 不真的移动 DOM,放下时再提交顺序;触屏要加 touch-action: none 否则和页面滚动打架。还要补自动滚屏和键盘替代操作。
11

速答清单

考前十分钟只看这一页
01场景题该怎么开头?
先问约束:数据量多大、网络条件、能不能改服务端、失败重试代价高不高。问完再给方案,不要上来就背代码。
02大文件上传为什么分片?
分片解决的不是速度,是失败成本。1GB 传到 999MB 断了要重来,切成 5MB 一片只重传那一片。
03分片大小怎么定?
常见 2-10MB,移动端偏小桌面端偏大。太小请求数暴涨,太大弱网下反复重传。
04为什么不能 Promise.all 发所有分片?
几百个请求同时发会把 FormData 堆在内存里,移动端可能直接崩,且浏览器同域会限流。要用并发池控制在 3-6。
05秒传的原理和风险?
算文件 hash 问服务端"有没有这个文件",有就直接关联。风险是抽样 hash 有碰撞,会出现秒传成功却拿到别人文件,所以服务端要做二次校验。
06断点续传的进度存哪?
存服务端最可靠,换浏览器也能续。存 localStorage 只能同浏览器。两者都做时以服务端为准。
07文件 hash 怎么算才不卡?
全量 SHA-256 对大文件太慢还会卡主线程。实际做法是抽头中尾三段加文件大小算,放 Worker 里执行。
08扫码登录为什么不能直接返回 token?
任何拿到 ticket 的人都能登录。所以要多一步用一次性 code 换 token,code 只能换一次且有效期极短。
09扫码登录的状态有哪几个?
waiting 未扫码、scanned 已扫待确认、confirmed 已确认。手机上还要显示设备信息让用户确认,防钓鱼。
10前端水印能防住破解吗?
不能。它防的是顺手截图和"截图没有归属信息"。要做到删了重建,但用户禁用 JS 就没了。真正的防护靠服务端盲水印加审计日志。
11无限滚动为什么用 IntersectionObserver?
scroll 事件频率高还要算 scrollHeight,会强制同步布局掉帧。IO 在合成阶段判断,不占主线程。
12无限滚动要设什么防白屏?
rootMargin 提前 200px 左右触发,用户滚到底时数据已经到了。还要加 loading 锁防重复请求。
13虚拟列表的核心参数?
每项高度、视口高度、当前滚动位置。用总高度撑滚动条,用 transform 把内容平移到正确位置。
14虚拟列表为什么要 BUFFER?
滚动事件是离散的,两帧之间可能跳过几十像素。上下各预留几个避免快速滚动时白屏。
15图片压缩为什么用 createImageBitmap?
它会自动应用 EXIF 方向。用 new Image 加载手机照片常常是躺着的,要手动读 Orientation 再旋转。
16导出 CSV 中文乱码怎么解?
文件开头加 BOM(\uFEFF),否则 Excel 按 GBK 解析。长数字要设成文本格式,否则会被转成科学计数法。
17拖拽为什么不用原生 HTML5 API?
移动端完全不支持,且拖拽影像样式不可控。用 Pointer Events 自己实现,注意 setPointerCapture 防止移出元素后中断。