ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

2024百度前端笔试复盘:事件循环、缓存与算法实战解析

2024百度前端笔试复盘:事件循环、缓存与算法实战解析 2024年秋招百度的前端笔试第一批我刚好赶上。考完到现在还觉得有些地方值得说道说道。如果你正准备投前端岗尤其是大厂这篇东西应该能帮你少走不少弯路。我尽量把题目、思路、踩坑点都掰开揉碎讲清楚不是那种“网上八股文汇总”而是我真实坐在考场里准确说是线上笔试系统里的感受和复盘。先说结论百度的前端笔试不玩虚的。它不考你背了多少API而是考你“在真实开发里遇到问题会不会解决”。选择题覆盖面很广编程题则很考验基本功和边界处理能力。整体难度偏中上时间紧需要提前做好策略。1. 整体印象与题型分布1.1 笔试形式与时间分配百度的秋招笔试一般是通过牛客网或者自家系统进行双机位监控那种。我参加的第一批总共分两部分客观题选择题和编程题。客观题大概30道左右编程题2道。总时长我记得是90分钟到120分钟具体要看批次通知。这里要提醒一句时间极其紧张。客观题里不少是“多选题”和“排序题”比单选题费时间。编程题虽然只有2道但第二道往往不是一眼能AC的难度需要留足时间思考。我的建议时间分配是客观题45分钟以内必须搞定遇到卡壳的立刻标记跳过别恋战。编程题剩下时间全部投入。第一道争取20分钟内AC第二道留至少30分钟以上去磨。实测下来如果前面客观题超过50分钟后面编程题基本要慌。慌就容易写错边界条件得不偿失。1.2 客观题覆盖范围这次客观题覆盖面相当广我回忆了一下大概涉及以下几块JavaScript 语言基础原型链、闭包、事件循环、this 指向、ES6 新特性。浏览器与网络HTTP 缓存、跨域、渲染机制、TCP/UDP。框架相关React 和 Vue 都有生命周期、虚拟DOM、hooks 原理、diff 策略。CSS 与布局盒模型、Flex/Grid、BFC、层叠上下文。工程化与工具链Webpack 打包原理、Vite 特性、git 操作。前端安全XSS、CSRF 的原理和防护。乍一看都是“八股”但实际做起来会发现它考察得更细。比如“事件循环”不只问你宏任务微任务顺序还会让你分析一段含 async/await 和 Promise 嵌套的代码输出顺序涉及微任务队列里的前后依赖关系。这种题你光背结论不够得真理解执行机制。1.3 编程题难度定位两道编程题第一道偏“模拟/实现题”第二道偏“算法设计题”。第一道通常你只要思路清晰、注意边界条件就能过。第二道则需要更深入的思考复杂度卡得比较严暴力解法可能过不了全部用例。我这次遇到的第一道题是“实现一个带过期时间的内存缓存”第二道是“寻找数组中第K大的数含重复元素要求稳定性”。这两道我下面都详细拆解。2. 核心知识点的深挖与解题思路2.1 JavaScript 引擎执行机制事件循环与微任务笔试里“事件循环”几乎是必考而且是那种看代码写输出的形式。我印象很深的一道题是这样console.log(A); setTimeout(() { console.log(B); }, 0); Promise.resolve().then(() { console.log(C); }).then(() { console.log(D); }); queueMicrotask(() { console.log(E); }); console.log(F);问你输出顺序。这题说难不难但它是典型的“一知半解就翻车”的题。我先说正确输出A F C E D B。为什么关键点在于同步代码先执行A 和 F 先打印。微任务队列的执行顺序Promise.then 和 queueMicrotask 都属于微任务但Promise 的 then 回调之间是有微任务队列依赖的。也就是说第一个 then 的回调打印 C入队后它内部返回的 promise 又注册了第二个 then打印 D所以要等 C 执行完D 才会被加入微任务队列。E 是通过 queueMicrotask 注册的微任务它在 C 之后、D 之前执行因为 D 是在 C 执行后才注册的。这里有一个非常容易忽略的细节queueMicrotask和Promise.then都往微任务队列里塞回调但如果在同一个事件循环中它们的入队顺序决定了执行顺序。上面这题第一次微任务队列里只有 C 和 E按注册顺序C 先注册E 后注册所以 C 先执行然后 E 执行。C 执行完才把 D 推入微任务队列所以 D 在 E 之后。很多同学会想当然地把 D 排在 E 前面因为觉得“Promise 的链式调用是一块的”。实际上它们是分开入队的。你再去看看 await 的情况会更复杂。比如async function foo() { console.log(1); await bar(); console.log(2); } function bar() { return new Promise(resolve { console.log(3); resolve(); }); } foo(); console.log(4);这输出是 1 3 4 2。注意 await 会把后续代码包装成一个微任务但 bar() 是同步执行的在 resolve 之前打印 3所以 1、3 先打印然后 foo 让出执行权主线程继续打印 4最后微任务执行打印 2。这种代码在网络请求、动画帧调度、性能监控里太常见了。你如果不理解执行细节写出来的代码就是埋雷。2.2 浏览器缓存与 HTTP/HTTPS客观题考了很多浏览器缓存的内容这是前端性能优化的基石。我帮你把核心点提出来强缓存 vs 协商缓存强缓存直接从本地读根本不发请求。相关头是Cache-Control: max-agexxx和Expires。协商缓存本地有缓存但不确定是否过期向服务器发请求验证。相关头是If-None-Match/ETag以及If-Modified-Since/Last-Modified。一个高频考点是强缓存和协商缓存同时存在时优先走强缓存。只有强缓存失效后才进入协商缓存流程。HTTP 和 HTTPS 的差别有个选择题问的是“HTTPS 为什么安全”本质上是通过 TLS/SSL 握手协商出对称密钥后续用对称加密传输数据。这里要注意HTTPS 不是全用非对称加密非对称加密只用于握手阶段交换密钥真正的数据加解密是高效的对称加密如 AES。状态码每年必考各种 2xx / 3xx / 4xx / 5xx 状态码的语义。印象深的是301永久重定向302临时重定向304协商缓存命中的响应。401未认证需要登录403已认证但无权限。这两个很多人容易搞混特意记一下。502Bad Gateway 和504Gateway Timeout 的区别502 是网关/代理收到上游非法响应504 是网关/代理等了半天上游没响应。我当时在浏览器控制台看到 502 第一反应是查后端服务状态而 504 第一反应是查超时配置。从实践角度这两个排查方向完全不同。2.3 框架题React 还是 Vue百度前端一直有 React 和 Vue 两个技术栈所以笔试题往往会二者兼顾。我这次遇到的主要围绕React 相关useEffect的依赖数组机制。问一个组件里useEffect(() {}, [])和useEffect(() {})有什么差别答案众所周知一个只在挂载时执行一个每次渲染都执行。但进一步问“useEffect 里 setState 会触发重新渲染怎么避免死循环” 这就要注意依赖项里是否引用了函数、对象等引用类型。React 函数组件和类组件的本质区别我曾经在项目里踩过坑函数组件通过闭包捕获当前渲染的 props/state而类组件的 this 是可变对象。如果 setTimeout 里访问 state很容易拿到旧值。Vue 相关Vue 2 的 Object.defineProperty 和 Vue 3 的 Proxy 原理。问到“Vue 2 为什么不能监听数组索引变化” 我当时的思路是defineProperty 是在指定属性上做拦截新增属性不会自动劫持。Vue 3 的 Proxy 直接拦截整个对象的 get/set所以新增、删除都能响应。模板编译、虚拟 DOM 和 diff 策略。问 Vue 3.2 里patchKeyedChildrendiff 算法核心用了双端对比这个和 React 的 fiber diff 不完全一样但都追求“尽量复用已有 DOM”。框架题从来不问“你用过哪个”而是底层原理。所以面试前一定要把框架源码的若干核心机制吃透尤其是 diff、响应式、组件生命周期。2.4 CSS 与页面布局那些你以为简单的题CSS 客观题也不白给。有一道题问“两个相邻同名 class 的优先级”。很多人张口就是“后写的覆盖先写的”但答案其实取决于“选择器特异性”和“后文覆盖”。都写在style里同样的选择器权重后出现的规则生效。若权重不同高权重的覆盖低权重。BFC块级格式化上下文也是常客。典型触发条件overflow: hidden、display: flow-root、position: fixed、display: inline-block等等。考法多是“如何清除浮动”“如何防止 margin 穿透”之类。我之前做过一个页面子元素margin-top会把父元素往下推就是因为父元素没创建 BFC让子元素的 margin 和父元素外边距合并。解决办法之一就是给父元素加display: flow-root或overflow: hidden。另一道 Flex 布局题我印象也深父容器display: flex三个子项flex: 1 0 200px问宽度不足时会不会收缩。因为设置了flex-shrink: 0所以即使父容器宽度不够子项也不会收缩会出现溢出。很多人只记得flex: 1是“平均分配宽度”却忽略了flex三个值分别对应flex-grow、flex-shrink、flex-basis。这个知识点在移动端适配、后台管理布局里都极其常见。3. 两道编程题目的完整复盘与实现3.1 第一题实现带过期时间的缓存系统题目描述回忆版设计一个键值对缓存系统支持以下操作set(key, value, duration)在duration毫秒内有效超过则不可访问。get(key)返回有效期内对应的 value否则返回 -1。要求get和set的时间复杂度都尽量接近 O(1)。这题其实就是 classic LRU TTL 的简化版。关键是“怎么处理过期”。我的思路用一个普通的 Map 存key - { value, expire }。再维护一个最小堆或时间轮来处理过期键的清理。如果只需要 get/set 判断可以先懒删除——get 的时候检查当前时间是否超过 expire超过就删掉返回 -1。为什么 Map 优于对象{}因为 Map 的 key 可以是任意类型迭代时保持插入顺序而且它自带size属性遍历性能更稳。如果你用对象还得额外维护一个计数器而且对象原型链拖慢属性查找。实现代码大体长这样class TimeLimitedCache { constructor() { this.cache new Map(); // key - { value, expire } } set(key, value, duration) { const now Date.now(); const expire now duration; // 如果 key 已存在且未过期返回 true否则 false const existed this.cache.has(key) this.cache.get(key).expire now; this.cache.set(key, { value, expire }); return existed; } get(key) { const entry this.cache.get(key); if (!entry) return -1; if (Date.now() entry.expire) { this.cache.delete(key); return -1; } return entry.value; } count() { // 可选统计未过期的条目数量 let count 0; const now Date.now(); for (const [key, entry] of this.cache) { if (entry.expire now) count; else this.cache.delete(key); } return count; } }笔试时有个容易犯的错set时判断是否已存在是用“旧的过期时间 当前时间”而不是“有没有这个 key”。如果 key 存在但已经过期从语义上应该算“新插入”返回 false。不加这个细节会挂掉隐藏用例。延迟清理的缺陷懒删除最大的问题在于过期 key 会一直占着内存直到被访问或 count 清理。如果长时间不访问缓存会越来越大。工程上一般配合定时清理或者惰性清理策略。不过笔试场景下get/set 判过期已经满足需求不需要过度设计。3.2 第二题寻找第 K 大的数含重复要求稳定题目描述回忆版给定一个未排序数组找到第 K 大的元素。注意数组里可能有重复元素。要求如果第 K 大元素有多个相同值取其中最后一个出现的作为答案即稳定性的含义或者说要找到最后一次出现的那个。例如[3, 2, 1, 5, 6, 4]K2输出 5。[3, 2, 3, 1, 2, 4, 5, 5, 6]K4输出 4。这题第一反应是排序或者快速选择Quick Select。但因为要求“如果有重复取最后一个出现的”就不能简单地排序后直接取索引。思路拆解第一步先用常规方法找到第 K 大的数值 value。可以用排序nums.sort((a,b)b-a)然后第 K 个就是 nums[K-1]。但这里有个问题有重复值的时候第 K 大的“数值”好确定可我们要的是这个数值最后一次出现的下标对应的元素还是只要数值本身仔细读题会发现它要求的是“稳定”意义上的第 K 大即相同值按最后一次出现的位置来算。我第二题当时理解成如果第 K 大的数有重复应该返回最后一次出现的那个元素的值值相同索引最大。例如[5, 5, 5, 3, 1]K2第 2 大是 5最后出现的 5 还是 5所以返回 5。这本质上不影响返回结果反而像是陷阱——考察你是否读懂了“稳定性”含义但最终返回的依然是数字值。另一种解读是要求返回索引而不是值。这更少见。但不管怎么说去重之后找到目标值再从右侧扫描第一个等于目标值的元素并记录索引这就是“稳定”解。当时代码我写的是这样function findKthLargest(nums, k) { // 先求第 k 大的值 const sorted [...nums].sort((a, b) b - a); const kthValue sorted[k - 1]; // 从右往左找最后一个等于 kthValue 的索引 for (let i nums.length - 1; i 0; i--) { if (nums[i] kthValue) { return { value: kthValue, index: i }; } } }当然排序的时间复杂度是 O(n log n)如果数据量大可能不是最优。为了拿满分可以用快速选择Quick Select平均 O(n)但需要额外处理重复值。笔试时间有限我用了排序法但注释里写清楚“若需优化可用 Quick Select”。稳妥比炫技重要因为复杂算法反而容易写错边界。关于快速选择的加强版思路如果想要 O(n) 平均可以利用“三路快排”的思路把数组分成小于、等于、大于三部分。如果第 K 大落在“等于”区间里就直接返回否则递归进入相应区间。这样重复值会被集中处理不需要额外去重。实现略微复杂但工程上更优雅。这题给我们的教训是笔试中如果时间紧张先写最稳妥能过的解法再在注释里描述更优方案。千万不要一上来就抱着 Quick Select 硬写万一 index 算错比排序法更惨。排序法虽然慢一点但思路可靠至少有分。4. 答题策略、常见问题与备战建议4.1 答题顺序与时间管理经验我参加过多场大厂笔试通用的策略是先做编程题再做客观题。很多人习惯从头做到尾结果客观题消耗了太多时间编程题来不及写其实非常亏。因为编程题分值占比高通常 60% 以上。就算客观题答得再好编程题挂了基本凉凉。我个人的习惯是一上来先快速扫一遍编程题判断难度。如果第一道编程题 5 分钟内想到思路立刻写。写完后哪怕不提交也放在那里再去做客观题。客观题控制在 40 分钟内遇到不会的立刻跳过。最后回头死磕编程题。这次我就是按这个顺序来客观题有些模棱两可的我标记后跳过最后还剩 20 分钟用于检查。帮助很大。4.2 常见笔试翻车点结合这次笔试我整理了几个高频“翻车”点忽略输入边界比如 K 超出数组长度、数组为空、duration 为 0 或负数。读题不完整“最后一个出现的值”这种描述容易被忽略。用了setTimeout模拟缓存过期千万别在笔试系统里用真实定时器时间不可控而且计时器回调会不停触发。正确的做法是相比时间戳来判断。Node 环境下处理输入输出牛客笔试用readline或fs.readFileSync(/dev/stdin, utf8)很多人不理解输入格式导致代码写对了但读入错误。我当时也踩了一个坑用console.log打印调试信息时忘了去掉导致输出行数不对一道题的 AC 率直接从 100% 掉到 0%。所以考前要养成习惯调试信息要么注释掉要么全部删除。4.3 针对百度笔试的知识点备战清单结合这次笔试内容和往年经验如果你时间有限按优先级准备这些知识点优先级知识点备考建议高JavaScript 事件循环、Promise/async自己写代码验证输出多钻几个嵌套场景高算法TopK、二分、链表反转、树遍历手写白板注意边界用例高浏览器缓存 HTTP 状态码背表 理解别死记中React/Vue 核心原理读一遍官方文档的“渲染机制”章节或者看源码的 diff中CSS 布局Flex/Grid/BFC拿个小页面手动改样式观察效果中TypeScript 基础类型 泛型近两年笔试出现频率明显上升低Webpack/Vite 工程化配置不用深挖理解 lazy loading、tree shaking 核心概念即可4.4 后续面试阶段的衔接准备笔试过了之后通常会有 1-2 轮专业技术面。笔试里考察的原理面试里大概率会被追问。尤其是你编程题里用了什么数据结构、为什么这么选、有没有更优解面试官会一直顺着问下去。我在笔试里遇到的那个带过期时间的缓存面试时就被追问“如果并发读写你会怎么处理如果多实例部署缓存的一致性怎么保证”这种问题笔试不会让你写但面试会考察工程化能力。我当时的回答是从单机内存缓存讲到分布式缓存方案如 Redis 的过期策略再讲缓存穿透、击穿、雪崩的应对。准备时可以往这个方向延伸。另外百度比较看重手写代码的规范性和可读性。比如变量命名要清晰分支逻辑直白注释适当。我见过有人笔试代码用a1,b2这种变量名即便算法对了也会给面试官留下不好的印象。5. 关于这次笔试的一些个人体会回顾整场笔试我最深的感受是百度前端笔试不是在“筛背书的人”而是在“筛能干活的人”。它把项目里常见的场景——缓存、异步、状态管理、性能优化、兼容性——抽象成题目然后看你在有限时间内怎么取舍和落地。如果你正在准备 2025 或更晚的秋招我建议你从现在开始每天手写一道经典算法题重点练 TopK、快排、链表、二叉树、动态规划入门。把 JavaScript 事件循环彻底吃透用node跑各种异步代码直到能准确预测所有输出。重读一遍 HTTP 缓存相关的 MDN 文档画一张缓存流程图贴在自己的笔记里。至少读一遍 React 或 Vue 的文档中“渲染机制”相关章节不用背源码但要知道核心原理。培养“先问边界再写代码”的答题习惯笔试系统里判题严苛边界条件漏一个整个 case 就挂。笔试只是整个秋招流程的第一道坎它的意义不在于一题定输赢而是让你提前感受大厂的考察节奏和偏好。如果你能在笔试中做到“题目没做完也不慌”后面的面试心态也会稳很多。最后再分享一个小技巧笔试开始的 5 分钟别急着动笔。先把所有题目完整浏览一遍不仅看内容还要看分数分布和难度。时间永远有限优先拿稳确定性的分再考虑挑战难题。这个习惯帮我拿下了不少场次希望你也能用上。
返回列表