
2025年春招我投了掌阅集团的前端岗笔试安排在晚上七点两个半小时全程一个在线编码平台加一道限时方案设计题。说实话投完简历之后我做了不少准备刷了两个月八股文和算法题自认为前端基础还算扎实但看到卷子的那一刻还是有点蒙它不是单纯考某一个点而是把 JS 基础、框架原理、工程化、浏览器机制和实际业务场景全揉在一起有些题连题干都好几百字读题就花了大量时间。这篇文章把我这次笔试的完整经历和复盘写出来包括题型分布、核心考点拆解、失分点和我后来总结的备考思路希望能给接下来准备前端岗位笔试的同学一点参考。掌阅做数字阅读平台前端岗位不只是写后台管理系统还涉及阅读器、H5 页面、移动端适配、内容分发这些业务所以我对笔试的预期是会更偏重真实场景。实际做下来也确实如此基础题占一半剩下的是框架原理、工程化和综合设计题。下面我按题型和考察点一条一条说。1. 笔试整体观感题型分布与考察重点1.1 掌阅前端笔试考了什么从题型说开去先说卷面结构。我这次的笔试分四块单选题、多选题、编程题、方案设计题。单选和多选加起来大概 20 道覆盖 JS 基础、CSS 布局、浏览器机制、HTTP 缓存、Vue/React 原理。编程题有三道一道是手写防抖和节流一道是数组扁平化加去重排序还有一道是 LeetCode 中等偏下难度的动态规划题目大意是求最长递增子序列。方案设计题是两道选一道我选了“大文件上传”那道另一道是“阅读器章节预加载与缓存策略”说实话那道更贴掌阅业务但我当时对大文件上传更熟就选了这个。整体下来我的感受是题目不算偏怪但非常考验熟练度和知识的体系化。比如单选题里有一道问0.1 0.2 ! 0.3的原因很多人会背成“浮点数精度问题”就选了但它后面还追问了“在二进制中 0.1 的表示是什么”这就是把八股文往底层多挖了一层。还有一道多选题关于事件循环里面混了 Promise、async/await、setTimeout 甚至requestAnimationFrame如果只是背“微任务先于宏任务”很容易做错因为requestAnimationFrame的时机和普通的宏任务不太一样。这些题目其实反映了一个趋势前端笔试不再满足于“你知道这个 API 吗”而是考“你知不知道它为什么这样设计底层发生了什么”。掌阅的笔试尤其明显毕竟阅读器对性能要求高首屏渲染、滚动列表、图片懒加载这些场景都需要理解浏览器底层才能做好。1.2 为什么这些题目会成为“拦路虎”很多同学复习前端笔试的时候习惯于刷面经、背答案结果一到真实笔试就翻车是因为题目稍微“换壳”就认不出来了。掌阅这次有好几道题就是这么设计的它不直接问“防抖是什么”而是给你一段实际代码问“快速点击按钮时为什么只执行一次如果要求第一次立即执行怎么写”。如果你只是背了防抖的模板不知道immediate参数的含义和实现逻辑就会卡住。另一个失分点是时间分配。两个半小时听起来很多但单选多选涉及大量阅读每道题都可能要花两三分钟手写代码题还要考虑格式和边界最后留给方案设计题的时间往往不足三十分钟。我考场里明显感觉到后半程心态有点崩因为阅读器缓存那道方案题虽然没选但光看完题干就花了不少时间以至于后面检查编程题的时间被压缩了。所以我把这次笔试的“拦路虎”总结为三点一是题目很长需要快速抓重点二是考察底层原理不能只背结论三是方案设计题没有标准答案必须在有限时间内展示出清晰的思路。后面我会针对每一类问题展开讲。2. 基础题与手写题送分题还是陷阱题2.1 那些“看似简单”的JS题先挑几道印象深刻的单选题说说。第一道是typeof null和null undefined的结果这题很多人秒选。但后面跟了一道“如何判断一个变量是真正的对象”或者“如何区分数组和对象”这就不是纯记忆了需要理解Object.prototype.toString比instanceof更可靠。我当时写了Object.prototype.toString.call(value) [object Object]也把数组、正则、日期这些特殊情况列了出来。因为面试官想知道你是记了结论还是理解了类型判断的原理。第二道是关于的隐式转换比如[] ![]的结果。这题几乎每一轮笔试都会出现但有个容易忽略的坑![]会先转成布尔值false再和[]比较于是变成了[] false接着[]转原始值成为空字符串false转数字变成 0空字符串转数字也是 0所以结果是true。如果只看表面很容易选成false。这种题考的就是“转换优先级”和“ToPrimitive”规则不是靠背答案能应付的。第三道是原型链和this结合起来的题。给出一个构造函数里面有实例方法、原型方法、静态方法然后问你不同调用方式下的输出。这里要注意箭头函数没有自己的this普通函数的this取决于调用位置。我当时做的时候特意在草稿纸上画了原型链把instance、Constructor.prototype、Constructor三者的关系标清楚才没有掉坑。建议大家平时做题也养成画图的习惯笔试不比面试不能问面试官只能自己稳一点。2.2 手写Promise、防抖节流和深拷贝的评分点编程题第一道是“手写防抖和节流并说明区别”这题我写了三个版本基础版、带immediate参数的防抖、带throttle的首次执行。注意笔试评分时会看边界处理比如this指向和参数透传。如果只写一个setTimeout壳子可能只能拿一半分数。我当时的实现大致是function debounce(fn, wait 300, immediate false) { let timer null; return function (...args) { const callNow immediate !timer; if (timer) clearTimeout(timer); if (immediate) { if (!timer) fn.apply(this, args); timer setTimeout(() { timer null; }, wait); } else { timer setTimeout(() { fn.apply(this, args); timer null; }, wait); } }; }但注意我这个实现里有个小问题当immediate为 true 时timer本身是 timeout 的 id在callNow判断里会把它当成布尔值用虽然timer是数字但为了严谨应该用timer ! null判断。笔试里的代码不需要一定跑通但逻辑要自洽。第二道是数组扁平化加去重排序。很多人会直接Array.from(new Set(arr.flat(Infinity))).sort((a, b) a - b)但面试官可能更想看到你徒手递归实现flat因为这样才能考察对递归和reduce的掌握。我写了两种解法一种用reduce递归一种用while循环加展开运算符还考虑了稀疏数组的情况。数组的边界情况很多[1, , 2]在遍历时undefined会被忽略但flat会保留空位这些细节都是加分项。第三道是手写 Promise但不是让你实现完整的Promise/A而是实现Promise.all和Promise.race并且要考虑输入不是数组的情况。我写了Promise.all的一个简化版用计数器判断是否全部完成同时用try/catch捕获每个 Promise 的异常。关键点是“返回一个新 Promise”并且“结果数组保持原顺序”。如果直接把then结果 push 到数组里遇到异步任务先完成就会顺序错乱这其实是最常见的失分点。2.3 CSS与浏览器渲染的那点事CSS 题这次出现的比例不低有一道考“水波纹进度条如何实现”的正好问到怎么用 CSS 动画做点击反馈。题目给了两种方案一种是transform: scale()加opacity动画另一种是改变width做进度条问哪个性能更好。显然transform和opacity的变化不会触发重排重绘而是走合成器所以性能更好。这背后就是“浏览器渲染流程”的问题从 HTML/CSS 到解析、样式计算、布局、绘制、合成每一层都可能成为性能瓶颈。还有一道 CSS 布局题是“实现一个高度自适应、左右两栏固定宽度、中间自适应的三栏布局”。除了经典的 flex 和 grid 之外还可以用圣杯布局、双飞翼布局。但笔试里最稳的是写 flex因为简单直接。不过题目追问了“如果中间栏要先渲染DOM 结构该怎么排”这就涉及到双飞翼布局或者 grid 的order属性了。我当时先写了 flex 版本又补充说明如果要求中间优先渲染需要用双飞翼布局并在注释里标了一下。虽然没写完整但让批卷人看到思路是能拿分的。CSS 和浏览器机制往往是联系在一起的比如重绘重排、requestAnimationFrame、合成层。掌阅这类内容平台非常看重列表滚动性能所以笔试中反复出现“如何减少重排”的选择题比如批量修改样式用classList、先把元素display: none再修改最后显示、读写分离避免强制同步布局。这些点我复习的时候都看过但真正做选择时还是会犹豫因为有两个选项看起来都对。建议平时把“触发重排的属性和方法”整理成一张表背下来例如offsetTop、scrollTop、getComputedStyle这些读操作会强制刷新渲染队列容易引发 Layout thrashing。3. 框架与工程化Vue/React背后的原理考察3.1 响应式原理与虚拟DOM八股文也有深水区掌阅前端技术栈从招聘描述看主要是 VueReact 也有一部分。笔试里 Vue 题明显多于 React而且深入到了响应式原理。单选题考了 Vue3 的响应式是基于 ProxyVue2 是基于 Object.defineProperty然后多选追问了“Proxy 相比 defineProperty 的优势”四个选项包括“可以监听新增属性、可以监听数组索引变化、性能更好、可以监听删除操作”。如果没有真的用过 Vue3很容易把“性能更好”当成必然选项但 Proxy 并不天然更快它只是在语义上更完整性能还要看具体实现。这种细节就需要源码阅读经验了。还有一道是关于nextTick的。题目说为什么修改数据后不能马上获取更新后的 DOM应该怎么办答案是 Vue 的异步更新队列机制nextTick内部会用 Promise 或 MutationObserver 模拟微任务。这里有个容易忽略的点在 Vue3 中nextTick返回的是一个 Promise所以可以直接await nextTick()。我在备选答案里看到了这个选项心一稳说明我之前看过源码里的实现。虚拟 DOM 和 diff 算法也考了但不是直接问“diff 算法的时间复杂度”而是给了一段列表渲染的代码问为什么加key能优化渲染以及用数组 index 作key有什么问题。这题我在实际开发中踩过坑所以答得比较顺index 会导致组件复用错误比如列表头尾插入节点时所有 key 都会变化从而引发不必要的重建和状态错位。关键是要答出“key 是 diff 算法的复用标识不是简单的唯一值”。3.2 Webpack/Vite配置文件里的学问工程化题占的比例比我预期高大概有 4 道选择和一道简答题。简答题是“线上 CSS 和 JS 文件为什么要加 hash 后缀hash、chunkhash、contenthash 有什么区别”。这题其实先要知道 hash 是为了浏览器长缓存文件内容变了 URL 才会变从而避免旧缓存问题。我有一次项目上线后用户反馈样式没更新就是因为没做 contenthash导致文件名没变浏览器一直用旧缓存。所以答这题时我结合了实际经验用contenthash按内容生成 hash这样只有该文件变更时 URL 才变化chunkhash是同一 chunk 共享 hash如果异步 chunk 里一个文件变了同 chunk 其他文件也可能跟着变缓存失效范围变大hash是整个构建一次一个 hash一般不适合长期缓存。这题如果只会背概念很难拿满分必须能说出“为什么”。另一道选择题是关于 Vite 为什么比 Webpack 快。选项里有“基于 ESM 原生模块、按需编译、使用 esbuild 预构建依赖、采用多进程打包”。实际上 Vite 开发环境冷启动快主要是因为“利用浏览器原生 ESM只在请求时编译模块”预构建依赖用 esbuild 把多包转成 ESM减少了浏览器请求数。而“采用多进程打包”是 Rollup 插件可以做的不是 Vite 启动快的核心原因。我之前用过 Vite 搭过小项目这些点能对得上。工程化还考了 Tree Shaking 的条件问“什么情况下import { a } from ./utils中的 b 函数会被自动删除”。答案是“模块是 ESM 静态结构、没有副作用、没有使用 b”如果 b 函数里调用了console.log或者有 top-level 副作用一般不能删除如果引入了整个对象import utils from ./utils也很难 tree shaking。实际项目里配置sideEffects: false是有副作用的需要小心不能无脑配置。3.3 微前端与模块联邦笔试里的“加分项”掌阅前端岗笔试里出现了一道微前端相关的多选题问“微前端方案中主应用和子应用之间的通信方式有哪些”选项包括 props 下发、自定义事件、路由参数、全局状态库。这题本身不难难的是后面有一道“为什么需要微前端”的简答题要求从业务角度分析。我在回答时先说了微前端的核心价值解决巨石应用带来的团队协作、独立部署、技术栈隔离问题。然后结合阅读器业务举例如果掌阅要做“阅读”生态把书城、社区、VIP 会员这些模块拆成独立子应用各团队可以独立发版主应用只负责导航和登录态。但我同时也提到微前端不是银弹如果团队规模不大、应用边界模糊强行上微前端反而会带来样式隔离、依赖复用的复杂度。模块联邦我也准备过但笔试没考太深只在多选里出现了一个选项“Module Federation 相比 qiankun 的优势是什么”。我选了“运行时共享依赖而不需要统一技术栈”。其实 qiankun 也支持技术栈隔离但模块联邦是 Webpack 原生能力能在构建阶段就把公共依赖打成 shared 包避免子应用重复打包 React/Vue 框架从而减小体积。不过这题问的是“优势”容易有歧义我权衡后选了运行时依赖共享和动态远程加载。这类题近几年出现频率明显变高因为中大型公司的前端团队都在搞微前端改造。准备笔试时不能只看概念要能说清“微前端适合什么场景、不适合什么场景”并且知道 qiankun、wujie、Module Federation 的差异不然很容易在多选里蒙错。4. 方案设计题与算法题拉开差距的地方4.1 大文件上传、实时通信这些“场景题”怎么答我选的方案设计题是“设计一个支持大文件上传的 H5 页面要求支持进度显示、断点续传、暂停/恢复并考虑多文件并发场景”。题目给了一个实际场景阅读 App 中用户上传 PDF、epub 文件用于云端书架单个文件可能达到 200MB。我的解题思路分三层。第一层是文件切片用Blob.prototype.slice或File.prototype.slice把大文件切成固定大小的 chunk比如每片 2MB然后给每个 chunk 加序号和文件唯一标识上传时可以用并发控制同时发 3 到 5 个分片。第二层是上传通道我写了两种用XMLHttpRequest可以监听upload.onprogress事件得到每个请求的上传进度然后聚合成总进度用fetch需要自己封装进度模拟不太适合精确上传进度。第三层是断点续传核心是“后端先接收分片记录已上传分片列表前端上传前先调接口获取已上传分片然后只上传缺失的分片”。在实现上我强调了几点文件唯一标识可以用文件的sha1或者spark-md5计算但超大文件计算哈希很耗时可以用“抽样 增量计算”的方式比如取文件头、中、尾三个片段算哈希或者用 Web Worker 来做避免主线程卡顿。正好这次热词里就有人提到“前端使用 worker 上传大文件”我用 Worker 做文件分片和哈希计算这样即使文件大也不会阻塞 UI。暂停和恢复则通过 AbortController 取消未完成的请求上传队列里维护一个Set记录已完成和进行中的 chunk id。关于多文件并发我提了一个“线程池”思路维护一个队列最多同时上传 N 个文件每个文件内部最多并发 M 个分片总体控制浏览器连接数。这里我画了一张简单的队列逻辑图在纸上代码里用一个forEach实现并发池大概这个样子async function uploadFiles(files, poolSize 3) { let index 0; const workers new Array(poolSize).fill(0).map(async () { while (index files.length) { const file files[index]; await uploadOneFile(file); } }); await Promise.all(workers); }虽然方案设计题不要写出完整代码但把并发控制、错误重试、进度聚合这几个点讲清楚比列一堆 API 更有说服力。笔试结束后我也在想如果选“阅读器章节预加载与缓存策略”那题可能更贴近掌阅业务但大文件上传是我实际做过的功能能写出更多细节所以选自己熟悉的题目永远是稳妥策略。4.2 前端算法题别只背题目要会推状态转移算法题是“给定一个数组求最长连续递增子序列的长度”题目描述有点绕但其实就是 LeetCode 673 或 300 的变体不过它要求的时间复杂度是 O(n log n)而且需要输出序列长度。我之前刷过这题但笔试里不能直接写 O(n²) 的 DP否则会被扣分。我最后用“dp 二分”的方式写了核心是维护一个tails数组tails[i]表示长度为 i 的递增子序列的最小末尾值然后遍历数组用二分找到第一个大于等于当前元素的位置更新tails。这样最终tails的长度就是最长递增子序列的长度。笔试里写算法题有个坑题面没有给详细的方法名和输入输出示例需要自己定义函数签名。我考场里花了几分钟推理输入是什么、输出要什么还好题目给了示例[10,9,2,5,3,7,101,18]应该输出 4。这里要注意“连续递增”和“递增子序列”的区别如果是连续递增那就简单多了但题目明确举例[1,3,5,4,7]的最长连续递增子序列是1,3,5长度 3而不是 DP 的 4。说实话我第一眼差点做错因为平时刷题“最长递增子序列”默认是非连续的需要仔细读题。后来我按连续递增做O(n) 搞定。这提醒我笔试时候审题非常重要不能凭肌肉记忆写代码。还有一道算法题在编程题最后是“判断二叉树是否对称”要求用递归和迭代两种方式。递归的思路是isMirror(left, right)迭代用队列成对入队。这道题的难点是边界条件很多比如空节点怎么处理、左右子树同时为空怎么返回。我写递归时花了很久因为总想用层序遍历但层序遍历需要记录空节点代码量偏大。其实对称二叉树用递归最清晰笔试时间有限不要为了炫技选复杂解法稳定通过才是第一目标。4.3 反问环节如何从笔试题目反推团队技术栈做完整张卷子我意识到笔试里的方案设计题其实能看出团队的技术倾向。大文件上传那题明显是“App 内嵌 H5 或收银台”常见的需求掌阅后端服务可能已经有分片上传接口前端更多是和它对接。阅读器预加载和缓存那题更贴业务如果选择了它需要结合“上一章/下一章预加载、章节列表缓存、长文本分片渲染”来答可能还会涉及localStorage、IndexedDB甚至RxDB这一类前端数据库方案。虽然现在我还没拿到后续通知但我个人建议后续准备面试时可以多了解一下掌阅 App 的阅读器实现比如 epub 解析、字体适配、滚动翻页和滑动翻页的性能取舍。另外方案设计题并没有标准答案但阅卷人会看你的技术视野。比如我在大文件上传里提到了使用 Web Worker 做文件分片和哈希提到使用spark-md5计算指纹提到XMLHttpRequest的上传进度比fetch更好这些都说明我有实际项目经验。反过来如果只回答“用 Element UI 的 Upload 组件”可能只能拿基础分。所以准备笔试时一定要把自己做过的项目细节提炼成“场景 技术选型 为什么这样选 遇到的坑”这种结构这样在方案设计题里才能有的放矢。方案设计和算法题是拉开差距的地方因为基础题大家都会刷但能不能在有限时间里把思路表达清楚靠的是平时积累。我的建议是平时多写技术笔记把项目里的难点和解决方案记录下来笔试时才不会大脑空白。5. 常见失分点与避坑指南5.1 我踩过的坑时间分配与题面阅读这次笔试最让我吃亏的是时间分配。前面单选多选我做得太细每道题都反复推敲导致留到编程题的时间只有五十分钟。手写 Promise 那道我写了很久因为一直纠结要不要完整实现 resolve、reject、then、catch、finally最后只写了简化版。事后复盘这类手写题不是让你写生产级代码而是看核心逻辑应该用尽量短的时间完成第一个版本再回头优化。还有一个坑是题面阅读。选择题里有一道问“以下哪个是 margin 塌陷的解决方案”我一开始以为是问“margin 重叠”是什么选了相邻两个块级元素 margin 取最大值但题目其实是问“父元素和第一个子元素的 margin-top 什么时候会合并”答案应该是给父元素加overflow: hidden或者加 padding/border。这两个概念经常混在一起笔试里如果读题不仔细就会选错概念。当时我花了不少时间对照选项才意识到自己差点选偏。建议大家在笔试前四十个小时里不要盲目刷偏题怪题而是把高频考点的基础概念重新过一遍特别是容易混淆的和、margin塌陷和合并、节流和防抖、强缓存和协商缓存、hash和chunkhash等。用表格列出来考前扫一眼比临时刷题更有效。5.2 面试官阅卷时的“隐形评分标准”我自己也帮团队筛过前端简历和笔试所以大概知道笔试阅卷时会关注什么。第一是思路哪怕代码写不全只要注释里写了“用两个指针从两边向中间移动”也能拿到步骤分第二是边界条件比如空数组、空对象、空指针、输入为null的情况有没有处理第三是代码风格命名是否清晰、有没有过多 console、是不是用了一个大函数搞定一切。如果代码里能体现模块化思想比如把文件切片、上传、进度计算拆成函数印象分会好很多。另外方案设计题要特别注意“可维护性”。面试官喜欢的答案不是堆技能树而是先讲清楚需求再列出可选方案最后给出推荐方案和理由。比如大文件上传我会先说为什么不用单一PUT上传因为网络中断后需要重传整个文件浪费流量然后说切片上传虽然增加后端复杂度但能断点续传再说切片大小怎么定太大容易超时、太小请求数太多2MB 是比较常见的选择。这种“为什么这么选”的思考过程比单纯的方案列表更有说服力。5.3 备考期的资料清单和学习路线如果你也是为前端春招笔试准备我比较推荐把时间花在四块第一是 JS 语言核心不只是背 API要能手动实现Promise.all、call/apply/bind、深拷贝、防抖节流第二是浏览器原理特别是事件循环、渲染流程、缓存、跨域第三是框架源码Vue 和 React 至少要选一个深入理解响应式、虚拟 DOM、更新调度第四是工程化Webpack/Vite 的核心机制、微前端、模块联邦。具体资料方面我刷的是牛客网的前端笔试题库手写题会自己再在 IDE 里跑一遍而不是只看答案。算法题我用 LeetCode 刷了大概两百道重点刷了数组、字符串、二叉树、动态规划这四类。这里有个小技巧不要按题号刷而是按“解题模式”刷比如“双指针”、“滑动窗口”、“前缀和”、“二分答案”、“状态压缩”这些模式每类挑十道左右快很多。如果你想知道自己离掌阅这类公司还差多少可以拿一套模拟题计时练一次。我是在笔试前三天做了两套模拟卷严格按照时间走这才发现原来自己做题慢是因为老在“不想写注释”和“想写完整”之间摇摆。提前暴露问题考试时才不会慌。6. 个人复盘与下一步计划笔试结束后我马上把错的题回忆了一遍自己在搜答案时发现有两道多选其实记错了一道是关于MutationObserver是否属于微任务虽然它一般作为微任务调度但在浏览器规范里它不属于标准微任务队列而是单独的任务队列另一道是关于contenthash在提取公共依赖时可能产生的问题虽然我选对了“公共模块提取会导致业务代码 contenthash 变化”这个选项但没有深入解释为什么。后来我查了一些资料发现掌阅这种体量的公司很看重“性能优化”和“业务理解”。笔试里反复出现的阅读器场景不是偶然它需要前端同学理解长列表渲染、字体加载、缓存策略、文本选择、翻页动画和内存管理。如果我能进到面试我会重点准备这些方面用createDocumentFragment和虚拟滚动优化阅读器目录、用IntersectionObserver做图片懒加载以及用 IndexedDB 缓存章节内容降低网络请求。如果后面还有类似笔试我可能会主动选择那道“阅读器章节预加载与缓存策略”的方案题因为它才能真正体现我对掌阅业务的理解。最后再说一个小技巧笔试的时候最好开一个本地编译器我习惯用 VS Code 写代码但笔试平台是网页编辑器没有代码高亮和自动补全手感很不一样。考前尽量用同样的在线平台做几道题提前适应。我觉得这次笔试虽然没有发挥到完美但它让我清晰看到了自己的短板在哪里后面几个星期我知道该往哪个方向使劲了。