
考过百度校招笔试的同学应该都有印象那份《百度2018校招Web前端工程师笔试卷第二批》在当年的求职圈里被传得很广。和很多公司直接甩一套LeetCode题不同百度的这套卷子更偏向于考察基础功底的扎实程度JS闭包、事件循环、渲染机制、手写代码这些老生常谈的东西换着花样反复考。我当初刷这套题的时候最大的感受就是很多知识点平时觉得自己懂了真到了笔试卷上要你写出来、算出来、推出来的时候才发现自己其实是一知半解。现在回头看这套题里考的东西到今天依然是前端面试的高频核心。不管你是准备校招的应届生还是想跳槽的初级前端把这份卷子的考点彻底吃透收益远不止应付一场笔试。这篇内容我会把卷子里涉及的典型题目拆开讲补全每一道题背后的原理、推导过程和常见误区顺手整理一些实际笔试过程中踩过的坑和应对思路希望能给准备前端岗位面试的朋友一些真正能落地的参考。1. 试卷整体风格与考察思路1.1 题型结构与分值分布百度的这套笔试卷整体结构大致分为四个板块基础选择题、简答/判断题、手写代码题和综合应用分析题。选择题部分覆盖面很广从HTML标签语义、CSS盒模型、JavaScript数据类型到浏览器缓存策略、HTTP状态码、事件冒泡捕获几乎每个基础知识点都会扫一遍。简答和手写代码题是拉开差距的关键。我记得卷子里有直接让你手写深拷贝的也有让你实现一个防抖函数的还有给一段代码让你分析输出的。这类题目没有太多取巧空间平时有没有真正动手写过一测便知。综合应用题通常会结合一个具体的业务场景比如页面性能优化、组件设计思路考的是你面对真实问题时的分析和落地能力。从分值分布来看JavaScript部分的权重明显高于HTML和CSS大概占了一半左右。这也符合当时大厂前端岗位的考察倾向CSS决定了你的下限而JS水平决定了你的上限。网络上流传的2018年版本答案各大论坛上面都有人整理过但光看答案意义不大关键是理解每一题背后的考察点。1.2 考察重点与能力倾向通览整套卷子能明显感觉到百度出题人的思路——他们不追求偏题怪题而是在常见知识点上挖深度。同样是考闭包普通公司可能让你说说闭包的优缺点百度会给你一段代码让你逐行分析作用域链的变化每一个变量在哪个阶段指向什么样的值写错一步整题就废了。再比如事件循环选择题里考一次宏任务微任务的执行顺序简答题里可能还要你解释为什么输出是这样的顺序背后的调用栈和任务队列是怎么协作的。这种考法非常检验你是否真正理解JavaScript的运行机制而不是背了几个输出顺序的结论。另一个明显的倾向是重视代码实现能力而不是光靠记忆。数组中让你不借助原生API去实现数组去重不是让你调一次[...new Set(arr)]就完事而是要用双指针、哈希表或者排序的方式推演整个过程。这类题对算法基本功有一定的要求但和纯算法岗比前端的算法题通常更贴近数组、字符串和对象操作不会出现特别偏门的图论或高级数据结构。2. 前端基础核心题拆解2.1 HTML/CSS 高频考点还原基础知识部分HTML和CSS的题虽然占比不高但有一个特点喜欢考细节而且这些细节平时写业务代码时往往不会太在意。比如HTML语义化卷子里会给你几个标签组合比如div、span、section、article、aside、nav让你选出哪些是语义化标签并简述各自的适用场景。这里的坑点在于很多人分不清section和article的区别。简单来说article强调内容的独立性单独拿出来也是一篇完整的文章而section更强调内容的章节性通常是整体的一部分。如果整个页面只有一个section没有标题结构那就违背了它的语义初衷。CSS部分盒模型是必考题。我记得卷子里有一道很经典的题一个div设置了width: 200px; padding: 20px; border: 5px solid #000; box-sizing: content-box;问这个元素实际占用的宽度是多少。答案是200 20*2 5*2 250px。如果box-sizing换成了border-box整体宽度就固定为200px内容区会被压缩到150px。这题本身不复杂但能考验你对标准盒模型和IE怪异盒模型的理解是否透彻。还有一道印象很深的题父元素和子元素的margin-top都设置为 20px问父元素和子元素之间的距离。这就是经典的 margin 塌陷margin collapsing问题。结果为 20px而不是 40px因为相邻的垂直外边距会发生合并。解决方式有很多给父元素加overflow: hidden、padding、border或者是display: inline-block都能打断这种合并。卷子考这个点的目的就是看你在写项目时是否真的遇到过这类布局上的怪异行为。2.2 浏览器渲染与事件机制浏览器的渲染机制也是这套卷子的常客。有一道选择题很典型在HTML文档中当浏览器解析到script标签时会怎么处理答案是会暂停DOM的解析先下载并执行脚本文件再继续后面的解析流程。这也是为什么业内一直强调把普通脚本放在/body之前或者给脚本加上defer属性。这里需要补充的是defer和async的区别。题目经常会把这两个属性混在一起考defer是脚本会并行下载但会等到HTML文档全部解析完成后才按照出现顺序执行而async是并行下载完就立刻执行不保证顺序执行时可能会中断文档的解析。如果理解不到位很容易在这道送分题上丢分。事件机制部分考的是事件传播的三个阶段捕获阶段、目标阶段、冒泡阶段。我记得有一道题问的是addEventListener第三个参数传true和false时分别是在哪个阶段触发。当时很多人只记住了true是捕获、false是冒泡但没有深入想过事件从window开始往下走一直到目标元素再往上冒泡到window整个链路里stopPropagation()和preventDefault()分别作用于什么。前者是阻止事件继续传播后者是阻止浏览器默认行为两者不能混用。比如在表单提交场景下如果你只想阻止页面刷新但不想影响事件冒泡那只能用preventDefault()。事件委托也是常考的点。就是利用事件冒泡的特性把子元素的事件处理函数绑定到父元素上然后通过e.target判断实际触发的是哪个元素。当时卷子里有一道应用场景题一个ul列表有上千个li要给每个li绑定点击事件问最优方案是什么。正确答案就是事件委托而不是给每个li都addEventListener因为那样会创建大量监听器消耗内存。这个思路在现在的框架开发中依然很有价值尤其是处理动态渲染列表时。3. JavaScript 重难点实战解析3.1 作用域、闭包与 this 指向JavaScript这部分是整套卷子的重头戏第一类高频题就是作用域和闭包。有一道非常典型的代码输出题贴出来大家感受一下for (var i 0; i 5; i) { setTimeout(function() { console.log(i); }, 100); }输出是五个5而不是 0, 1, 2, 3, 4。原因在于var声明的变量没有块级作用域循环结束后i的最终值就是 5所有的setTimeout回调函数捕获的是同一个i变量在100毫秒后执行时读取到的自然是 5。解法有很多把var改成let利用let的块级作用域每次循环都会创建一个新的绑定或者用闭包包一层把i作为参数传进去也可以用bind或立即执行函数传参。闭包这块卷子还喜欢让你解释闭包的形成原理和内存问题。所谓闭包就是当内部函数引用了外部函数的变量时即使外部函数执行完毕这些变量依然会被内部函数引用而不会销毁。这个特性非常强大可以用来实现私有变量、函数柯里化、模块化等模式。但副作用也很明显如果闭包长期持有大型对象内存就无法释放导致泄漏。所以写业务代码的时候用完的闭包引用要记得置空。this指向在卷子里几乎是必然出现的。经典的那道题var obj { name: baidu, say: function() { console.log(this.name); } }; var fn obj.say; fn();输出是undefined或全局对象上的name属性而不会是baidu。原因很简单this的指向取决于函数被调用时的上下文fn()是普通函数调用在非严格模式下this指向全局对象严格模式则是undefined。真正想保持obj上下文得用obj.say()这种形式调用或者用fn.call(obj)、fn.bind(obj)。3.2 异步编程与 Event Loop异步编程在百度这套卷子里占据的篇幅比我想象中还要多。除了基础的回调函数重点考了 Promise 和 Event Loop 的执行顺序。有一道经典的输出顺序题console.log(start); setTimeout(() { console.log(timeout); }, 0); Promise.resolve().then(() { console.log(promise); }); console.log(end);正确的输出顺序是start、end、promise、timeout。这道题考察的是宏任务和微任务的区别。setTimeout回调属于宏任务会进入宏任务队列Promise 的.then回调属于微任务会进入微任务队列。每个宏任务执行完之后会把当前微任务队列里的所有任务全部执行完才会去取下一个宏任务。所以虽然setTimeout写在前面但它的优先级低于微任务。我当时备考时总结了一个记忆方法同步代码永远先执行微任务优先于宏任务一个宏任务之后清空所有微任务队列。这套规则搞清楚了大部分输出顺序题都能对。Promise 本身的设计也是考点。卷子会问Promise.all和Promise.race的区别以及什么场景下用哪个。Promise.all是所有 Promise 都 resolve 后才进入.then任何一个 reject 就会进入.catch适合处理多个无依赖的接口请求并合并结果的场景。Promise.race则是哪个先出结果就算哪个适合做超时控制比如请求超过3秒就提示失败。这套卷子的时间节点在2018年async/await已经算比较新的语法但已经进入了考察范围。有一道简答题让写一段async/await处理并发请求的代码。这里有个容易踩的坑如果写的是await a(); await b();那就是串行执行两次请求的时间是叠加的。如果想让它们并发执行应该先同时调用函数拿到 Promise再统一awaitconst p1 fetch(/api/a); const p2 fetch(/api/b); const [r1, r2] await Promise.all([p1, p2]);很多人在这一题上失分不是因为不会用async/await而是没有意识到串行和并发的性能差异。这种题考的很实际工作中接口并发优化也是性能优化的高频手段。3.3 手写代码题防抖节流、深拷贝、数组去重手写代码是笔试卷里最让人紧张的部分也是拉开差距的核心。这套卷子真正动手写的题不算特别多但每一道都很有代表性。第一个是防抖debounce。场景是用户在输入框中输入内容需要等用户停止输入500毫秒后才发送搜索请求。实现思路是每次触发时清除之前的定时器再重新设一个新的定时器function debounce(fn, wait) { let timer null; return function(...args) { clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, wait); }; }这里有几个细节要注意返回的函数必须保持this指向否则在作为对象方法使用时会出现问题...args要透传保证参数不丢定时器变量要放在闭包里保证多次调用之间共享同一个timer。节流throttle的思路和防抖不同它限定了函数在固定时间内最多执行一次适合监听滚动事件时使用。一个常见的实现是function throttle(fn, interval) { let last 0; return function(...args) { const now Date.now(); if (now - last interval) { last now; fn.apply(this, args); } }; }第二个典型题是深拷贝。当时标准答案通常是递归判断类型然后逐层复制function deepClone(obj, map new WeakMap()) { if (obj null || typeof obj ! object) return obj; if (map.has(obj)) return map.get(obj); const result Array.isArray(obj) ? [] : {}; map.set(obj, result); for (let key in obj) { if (obj.hasOwnProperty(key)) { result[key] deepClone(obj[key], map); } } return result; }用WeakMap处理循环引用是加分项因为普通递归遇到a.self a这种情况会陷入死循环。当时大部分考生能写出类型判断加递归的版本但只有少数人想到了循环引用的处理。第三个是数组去重。最简单的写法是[...new Set(arr)]但笔试通常要求自己实现。可以用哈希表、双重循环、排序后去重等方式。我记得评卷时看重的是你能否在O(n)时间复杂度内完成而不仅仅是能不能出正确结果。用Map或对象作为哈希表就是标准解法这个思路在需要统计频率的算法题中也会反复用到。4. 网络与性能优化类真题4.1 HTTP 缓存机制网络部分重点围绕HTTP缓存展开尤其是强缓存和协商缓存的区别。这是一道高频简答题浏览器请求一个资源第一次请求后服务器返回的响应头里带有Cache-Control和Last-Modified请简述第二次请求时浏览器的行为。答案要分几步说清楚第二次请求时浏览器先判断Cache-Control里的max-age是否过期。在有效期内直接从本地缓存读取完全不和服务器通信这就是强缓存。一旦过期浏览器会带上If-Modified-Since或If-None-Match重新请求服务器由服务器判断资源是否真的发生变化。如果没变返回304状态码继续使用缓存如果变了返回200和新的资源这就是协商缓存。这里面有几个常见误区。Cache-Control的no-cache并不是完全不缓存而是每次都需经过服务器校验no-store才是真正不做任何缓存。ETag是资源内容哈希类的标识优先级高于Last-Modified因为修改时间只能精确到秒某些修改可能在同秒内完成而 ETag 能捕捉到内容真正的变化。性能优化题通常会结合具体场景比如首屏加载很慢你会怎么排查和优化这类题不需要拿标准的性能优化清单背一遍关键是分主次作答。我当时的回答思路是从网络请求、资源体积、渲染路径三个层面展开减少不必要的请求、合理利用缓存和CDN、压缩JS/CSS资源、图片懒加载、拆分首屏关键的CSS、减少阻塞渲染的脚本等。面试官更在意的是你有没有形成一套自己的排查体系而不是记了多少个优化技巧。4.2 跨域方案与实战选择跨域也是笔试卷上的常客。我记得有一道题给出几种跨域方案让分析各自的优劣和适用场景。JSONP、CORS、postMessage、代理转发这些都是当时的常规选项。JSONP 的原理是利用script标签不受同源策略限制的特点动态插入一个 script 标签指向目标接口接口返回一段调用了回调函数的JS代码。优点是兼容性好缺点是只能支持GET请求而且会引入额外的回调函数命名管理问题。CORS 则是现代浏览器的标准跨域方案服务器在响应头里增加Access-Control-Allow-Origin等字段支持各种HTTP方法。两种方案在项目中都有应用场景比如早期一些开放API会用JSONP而正规的接口平台基本都走CORS。当时有一道场景题是前端页面部署在a.baidu.com接口部署在b.baidu.com应该如何解决跨域问题。最佳实践是看后端能否配合。如果可以改后端响应头优先用CORS如果后端不方便改开发环境下用 devServer 的 proxy 代理转发生产环境则在 Nginx 层做反向代理。这些内容在今天的工程化开发中已经很成熟但在当年能完整答出来的人并不多。5. 算法与数据结构题目思路5.1 常见算法题型校招笔试题里算法题的风格偏应用型不会直接让你写红黑树或者图的最短路径而是把算法嵌入到字符串、数组处理中。这套卷子出现的题目大致分三类第一类是字符串处理题比如给定一个字符串找出最长不重复子串的长度。这是典型的滑动窗口问题用双指针维护一个窗口窗口内不包含重复字符每次移动右指针扩展窗口遇到重复字符就移动左指针收缩同时记录窗口的最大长度。第二类是对称与回文相关问题。比如给你一个字符串判断是不是回文串或者找出字符串中的最长回文子串。回文判断本身不难但中心扩展法和动态规划两种解法的时间复杂度差异很大笔试时如果能在时间限制内写出来通常已经比大多数人强了。第三类是数组操作题包括数组交并差集、移动零到数组末尾、合并两个有序数组等。这些题的核心思想普遍是双指针和原地操作。移动零那道题要求把0全部移到末尾同时保持非零元素的相对顺序用快慢指针一次遍历就能完成时间复杂度O(n)、空间复杂度O(1)这就是标准解法。5.2 解题模板与复杂度分析备考刷算法题时不建议海量刷题而是先把常考题型归纳成模板。我当时总结了两类高频模板双指针模板适合有序数组的合并、查找、去重等场景。初始状态一个指针指向头部一个指向尾部根据计算结果决定移动哪个指针直到两个指针相遇。滑动窗口模板适合子串和子数组问题。固定左边界移动右边界扩展窗口不满足条件时收缩左边界。看到“最长”“最短”“连续子数组”“子串”这些关键词优先考虑滑动窗口。复杂度分析是笔试答题时容易被忽略的点。很多题目即使你给出了正确解法如果复杂度不达标得分也会打折。比如最长不重复子串暴力解是三重循环复杂度O(n^3)优化成滑动窗口后是O(n)。写答案时建议把时间复杂度和空间复杂度一并标出来既展示思路清晰也是加分项。6. 应试策略与备考建议6.1 时间分配与答题技巧整场笔试的时间通常比较紧凑题型又多如果按顺序死磕某一道难题很容易导致后面的大题来不及写。我见过不少同学栽在时间分配上前面的选择题反复纠结后面手写代码题潦草几笔就交卷了。我个人的建议是先花两三分钟快速浏览全卷把题目按难易程度分个层。容易的题先拿分困难的题做个标记最后有时间再回来想。选择题和判断题控制在总时间的四分之一以内不要恋战。不会的选择题凭第一感觉选一个然后立刻走人。手写代码题建议先写核心思路用注释简要描述边界条件和关键逻辑再写代码。因为笔试评卷时即使代码有些小错误思路清晰、关键步骤注释到位的答案通常也能拿到大部分分数。留白才是最可惜的。6.2 常见失分点与避坑清单整理几个实际考场上反复出现的失分点大家备考时对照自查。第一是作用域和this不熟代码输出题全靠懵。这类题没有技巧只能多写多练把var/let/const的区别、三种函数调用方式下this的指向全部理一遍用node跑测试代码验证自己的判断。第二是手写代码时忽略边界条件。比如深拷贝没处理null、数组和循环引用防抖函数没处理this指向数组去重没考虑空数组和稀疏数组。这些都是评卷时的高频扣分点。写完后习惯性地问自己输入是空值会怎样输入是异常值会怎样第三是只写代码不写思路。笔试和面试不一样面试你可以口头表达但笔试只能通过你写下来的内容来判断。代码之上清晰注释和思路描述就是你的“口语”。尤其是复杂逻辑务必在关键行旁边标注意图。第四是粗心导致的低级错误。比如把写成函数名和调用名不一致数组索引越界。考试紧张时尤其容易犯这种错误。写完代码后至少花一分钟从头到尾读一遍自己的代码再交卷。这套百度2018校招Web前端工程师笔试卷第二批对当时的前端求职者来说是一座很实用的标杆它把前端基础、JS功底、网络知识和通用算法能力完整地串了一遍。即使你现在不是准备校招这份考点清单也可以拿来当作自测标准能不看资料写对几道手写题能快速分析出一段代码的输出顺序能在源码级别解释出浏览器的缓存机制如果这些都能做到说明你的前端基础经得起考验。备考过程中我最大的体会是不要迷信刷题数量每一道真题做完之后多问自己一层“为什么”把背后的原理吃透比多做三套卷子都管用。