终于有人把这份题拿出来聊了。我2018年春招的时候也做过360这套Web前端开发工程师的客观题,当时做完感觉“选择题而已,不至于太难”,考完对答案才发现,真正拉差距的根本不是会不会,而是对一些基础细节有没有形成条件反射式的准确判断。那会儿Vue 2.5刚火起来、React 16刚出Fiber、Webpack 4还在beta,跟现在的前端生态比,很多东西已经变了,但一套高质量的客观题之所以值得反复看,是因为它考的不是“最新API背没背”,而是“底层原理有没有搞透”。哪怕放到现在,这套题里涉及的JavaScript核心机制、浏览器渲染、网络协议、安全策略,依然是前端面试绕不开的硬底子。
我先把结论放在前面:这套客观题合集,对准备校招、跳槽、甚至是刚入行想自检基础的前端开发,都有很强的复盘点价值。它覆盖的知识面非常宽,但考得并不浅,很多题表面在问“选哪个”,实际上是在问“为什么”。这篇文章我就按自己做题时的复盘逻辑,把当时的考点拆开揉碎讲一遍,也顺手聊一聊哪些题目放在今天依然有效,哪些已经需要结合新特性重新理解。
1. 为什么值得回头啃一套2018年的老题
1.1 那个年代的前端生态坐标
先做个时间定位。2018年初的前端,ES6已经全面普及,但ES Modules在浏览器端的原生支持还不算好,TypeScript还没有成为大厂的默认选项,Vue和React两大框架进入白热化竞争阶段,Angular在国内的声量已经明显下滑。构建工具方面,Webpack 3是主流,Webpack 4刚出,很多团队还在踩跨域、热更新、tree-shaking的坑。小程序刚亮相不久,前端岗位的招聘要求里开始出现“有小程序开发经验优先”。
这个时间节点意味着什么?它意味着当时的笔试客观题既不会考太新的东西,也不会停留在最老的前端写法上。jQuery还有一些题,但已经不多了;ES6的let/const、解构赋值、Promise、箭头函数一定是重头;CSS3动画和flex布局也是必不可少。整体命题思路,可以概括为“在经典知识基础上,考查候选人有没有跟上时代更新的能力”。
放到今天回头看,这些题里的“时代背景”已经淡化了,但底层的语言机制、浏览器原理、网络协议并没有过时。比如事件循环、原型链、闭包、HTTP缓存,这些在2025年的面试题里依然反复出现。所以啃老题的价值不是押题,而是通过这些经典的考查维度,搭建一套完整的前端知识框架。
1.2 客观题不等于送分题
很多同学一看“客观题”三个字,就觉得是考察记忆力的,背一背就过了。实际上大厂的笔试客观题,恰恰是最能筛选出“有没有真正写过代码”的题型。
我举个典型的例子:一道关于Array.prototype.sort的题目,如果你只是背过“sort会改变原数组”,那考场上遇到“[10, 9, 8].sort()的结果是什么”,很容易想当然地选[8, 9, 10]。但正确答案是[10, 8, 9],因为默认排序顺序是把元素转为字符串后按Unicode码点排序的。这种题目,写过代码的人不一定会特别注意,但至少不会毫无防备;而完全靠做题背答案的人,基本一道题就暴露了。
360这套题的设计逻辑,我认为就是沿着这种思路走的。它不太会问你“Vue的生命周期有哪些”这种纯背诵题,而是会换一个场景,比如“在created里修改数据,视图会不会更新”、“父子组件的mounted执行顺序”这种需要在真实开发中观察过的细节。这类题的区分度极高,懂就是懂,不懂就是只能蒙。
所以我建议大家刷这类题的时候,不要只盯着正确率,而是把每道题都当成一个引子:选对了,能不能给旁边的人讲清楚为什么?选错了,是知识盲区还是理解偏差?把每道题的问题都问到底,才是做真题的正确姿势。
2. JavaScript客观题:原型链、闭包与异步时序的核心战场
2.1 原型链:看似基础,实则最容易出错
JavaScript的客观题里,原型链是绝对的高频考点。那会儿的题一般不会直接让你默写原型链的图,而是会通过某段代码的执行结果来考查。比如:
function Person() {} Person.prototype.say = function() { return 'hello'; }; var p = new Person(); console.log(p.say()); console.log(p.constructor === Person); console.log(p instanceof Person);这道题的三个输出分别是什么?hello、true、true,看起来很简单。但360这种级别的笔试,很可能把问题再变一下,问p.hasOwnProperty('say')的值。如果对hasOwnProperty和原型链查找机制理解不透,很容易把“p能调用say方法”和“p拥有say属性”混为一谈。前者的答案还需要多读几遍,因为[10, 9, 8].sort()这个例子因为默认字典序排序,结果是[10, 8, 9],但很多人心里预期是[8, 9, 10],这跟sort的默认行为有关。
还有一个高频陷阱点:构造函数显式返回对象的情况。
function Foo() { this.name = 'foo'; return { name: 'bar' }; } var f = new Foo(); console.log(f.name);这题的结果是'bar'。原理是构造函数如果显式返回一个对象,那么new出来的实例会被这个对象覆盖;只有当返回值是原始类型时,才会被忽略并返回this。很多人在项目里几乎没有写过“构造函数返回对象”这种写法,所以遇到这种题就懵了。但它考的是对new操作符执行过程的完整理解——创建一个新对象、把原型关联到构造函数的prototype、执行构造函数内部逻辑、根据返回类型决定最终结果,任何一个环节不理解,都可能选错。
2.2 闭包:从经典循环坑到垃圾回收的变体
闭包的客观题,360肯定不会缺席。经典的循环题大概是这样的:
for (var i = 0; i < 3; i++) { setTimeout(function() { console.log(i); }, 0); }输出是3 3 3,而不是0 1 2,这个相信现在很多同学已经烂熟于心了。但笔试的难度在于,它可能会换一个变体:
for (var i = 0; i < 3; i++) { (function(j) { setTimeout(function() { console.log(j); }, 0); })(i); }这样输出就是0 1 2,原因是用了IIFE把每次循环的i值传入了新函数作用域。更进阶一点的考法,是把var换成let:
for (let i = 0; i < 3; i++) { setTimeout(function() { console.log(i); }, 0); }输出是0 1 2。let在每次循环迭代中都会创建一个新的绑定,这跟var的“函数作用域只有一个变量”有本质区别。这道题哪怕放在现在也是经典中的经典,因为它考的不仅是语法差异,更是对作用域机制的理解。
笔试里闭包还常和垃圾回收挂钩。比如问“返回一个引用了外部数组的闭包函数,外部数组什么时候被回收”。答案是:只要闭包函数还被引用,它引用的外部变量就不会被回收。这个知识点如果在真实项目里优化性能时没踩过内存泄漏的坑,光靠背概念很容易在变体题上翻车。我记得当时有一道题,给了一段返回函数的代码,然后问“改变外部变量后再调用闭包函数,输出是什么”——考的就是闭包保存的是变量的引用,而不是值。这比直接问“闭包是什么”要高明得多。
2.3 事件循环与异步顺序:区分度的分水岭
如果说原型链和闭包是基础分,那么事件循环相关的题目就是拉开差距的地方。2018年的时候Promise已经普及但async/await还没那么泛滥,所以题目更喜欢考Promise和setTimeout的组合。
一个经典的输出顺序题:
console.log('A'); setTimeout(function() { console.log('B'); }, 0); Promise.resolve().then(function() { console.log('C'); }); console.log('D');正确顺序是A D C B。原理是同步代码先执行完,然后微任务队列(Promise的then回调)优先于宏任务队列(setTimeout回调)执行。这道题放在今天依然适用,但现在的考题可能会加入async/await,比如:
async function test() { console.log(1); await console.log(2); console.log(3); } test(); console.log(4);输出是1 2 4 3。原因是await后面的代码会被包装成微任务执行,所以console.log(3)会在同步代码全部执行完之后才执行。这里有个容易混淆的点:await console.log(2)中的console.log(2)是同步执行的,await的等待行为只影响后续代码。
我当时做这类题的经验是,先脑子里面跑一遍“同步代码 → 微任务 → 宏任务”的流水线,再一道一道对答案。不用急着看解析,自己能把顺序推出来,才算真正掌握了事件循环的调度逻辑。
3. 浏览器与网络基础:缓存、渲染与安全一个都不能少
3.1 HTTP缓存:一次请求中间的判断链路
浏览器相关的客观题里,HTTP缓存的高频程度超出很多人想象。360的题里也出现了,因为它直接关系到Web性能优化。缓存的关键在于对几个头字段的理解:Cache-Control、Expires、ETag、Last-Modified。
我建议大家把这几个字段按“请求前”和“请求后”两个阶段拆开理解:
- 请求前(强缓存阶段):浏览器先检查
Cache-Control里的max-age,如果资源没过期,直接用本地缓存,不发请求。Expires是HTTP/1.0时代的字段,指定的是一个绝对时间,容易被本地时间干扰,所以现代应用基本都用Cache-Control的相对时间。 - 请求后(协商缓存阶段):如果强缓存失效了,浏览器会带上
If-None-Match(对应服务端的ETag)和If-Modified-Since(对应服务端的Last-Modified)去问服务器资源有没有变。服务端返回304表示没变,浏览器可以继续用本地缓存;返回200的话说明资源变了,直接替换。
笔试题目喜欢考查的点是:ETag和Last-Modified的优先级、no-cache和no-store的区别、304状态码的含义。这里最容易被绕晕的是no-cache。它的意思并不是“不缓存”,而是“使用缓存前必须先和服务端确认”,也就是强制走协商缓存;而no-store才是真正的不允许缓存。
如果把它们比作生活中的场景,max-age=3600就像是“这一小时内我直接用手头的信息,不用再打电话问你”;no-cache则是“每次我都要打电话问你一句‘之前那个信息还能用吗’,你说能用我才用”;no-store是“你给我的信息我转头就扔,下次再要就重新给我发一份。”
3.2 渲染过程:从输入URL到页面呈现的关键环节
渲染原理也是前端笔试必考的经典。题目可能问“在浏览器地址栏输入URL并回车,中间发生了什么”,这种题其实在客观题里通常会被拆成若干个选择题,比如“HTML解析过程中遇到<script>标签会怎样”、“重排和重绘的区别是什么”。
这里有个很重要的知识点:JavaScript的下载和执行会阻塞HTML解析。所以一个不讲性能的页面,如果把一个大型<script>标签放在<head>里,那么页面白屏时间会被明显拉长。这也是为什么业界一直强调“把脚本放在body底部”或者使用defer/async属性。那年的题目大概率会问这两者的区别:
defer:脚本会并行下载,但会等待HTML解析完成后再按顺序执行。async:脚本也是并行下载,但下载完就立即执行,不保证执行顺序。
进一步深挖,浏览器解析HTML时遇到<link rel="stylesheet">,CSS不会阻塞DOM解析,但会阻塞渲染,因为渲染需要CSSOM和DOM合并成渲染树。这部分很多客观题会结合“CSS放在头部、JS放在尾部”的最佳实践来出题,考查你能否把实践和原理对应起来。
渲染过程中的重排和重绘也是高频考点。重排(reflow)是当DOM的几何属性发生变化时,浏览器需要重新计算元素的尺寸和位置;重绘(repaint)是样式变化不影响布局时,浏览器只需要重新绘制元素。直观来说,修改width、height、margin、display都会触发重排;修改color、background-color、visibility则只触发重绘。而transform为什么常被用来做动画?因为它可以触发GPU合成,不一定会引起重排和重绘。这个知识点在当时的性能优化题里是得分点,今天也依然是前端优化的基本素养。
3.3 XSS与CSRF:安全题里最常见的两类基础功
360本身就是安全公司,笔试里考Web安全可以说是“主场作战”,所以这部分题目一般不会太浅。最常见的两道题是跨站脚本攻击和跨站请求伪造,虽然名字容易混淆,但攻击思路完全不同。
XSS的核心问题是“用户输入的内容被当成代码执行了”。比如一个评论区,用户输入了<script>alert('xss')</script>,如果前端直接把这段内容插入到页面里,脚本就会执行。防御手段主要有三种:对输入做过滤、对输出做编码、使用CSP策略限制脚本来源。客观题里还会考一个点:textContent和innerHTML的区别,前者安全,后者如果插入用户内容就会触发XSS。
CSRF的核心思路是“借用用户的登录凭证发起恶意请求”。比如你在A网站是登录状态,攻击者在B网站放了一个自动提交的表单,请求A网站的转账接口,浏览器会自动带上你的Cookie,服务器以为是你主动发起的操作。防御手段包括校验Referer字段、使用CSRF Token、设置SameSiteCookie等。客观题比较喜欢考“哪个请求会携带Cookie”以及“为什么CSRF Token能防住攻击”。
安全这块的题,建议不要死记硬背,最好能动手写一小段demo,复现一下XSS和CSRF的攻击链路。一旦理解了攻击链路,选择题基本不用猜,因为你知道那个环节“如果没有任何防护就会出事”。
4. CSS与HTML:看似送分的客观题,其实藏着不少失分点
4.1 盒模型与BFC:绕不开的基础之一
CSS的客观题,从盒模型开始是惯例。标准盒模型里,width指的是内容区(content)的宽度,padding和border在宽度之外;而IE盒模型(也叫怪异盒模型,对应box-sizing: border-box;)里,width指的是内容区加padding加border的总宽度。
题目如果给一个width: 200px; padding: 20px; border: 1px solid #000;的div,问标准盒模型下元素实际占用的宽度,那就是200 + 20*2 + 1*2 = 242px。如果在border-box下,那实际宽度就是200px,内容区会被压缩成200 - 40 - 2 = 158px。这种题只要理解了计算规则就不会错,但考场上时间紧,很多人会忽略“border也要算进去”这个细节。
BFC(块级格式化上下文)通常不会单独出一道大题,而是穿插在“如何解决外边距塌陷”“如何清除浮动”这类题里。BFC的考点其实很集中:它是页面上的一个独立渲染区域,内部元素的布局和外部的元素互不影响。触发BFC的条件包括overflow值不为visible(比如hidden)、float不为none、display为inline-block或flex、position为absolute或fixed。
我当时复习BFC的时候,总结了一句话:只要遇到“父子元素的margin合并”或者“父元素高度塌陷”这种问题,第一个想到的思路就是给父元素创建一个BFC。客观题大概率会给你几个候选CSS属性,问哪个能解决浮动塌陷,这时候只要记得“overflow: hidden可以触发BFC”就能拿分。
4.2 CSS居中方案与选择器优先级
居中问题是CSS里最容易出选择题的,因为方案太多了,不同场景用哪个,本身就值得考。比如一个div水平居中,最简单的margin: 0 auto;要求元素有确定宽度;如果是flex容器,justify-content: center;就够了。垂直居中方案更多:line-height只能处理单行文本、display: table-cell + vertical-align: middle可以处理任意内容、绝对定位加transform: translate(-50%, -50%)是经典方案、flex的align-items: center是最省事的。
客观题会给出一个场景,比如“父元素高度不确定,子元素需要水平垂直居中”,然后给四个选项,让你选可行方案。如果你只会背诵“flex可以居中”,而没有考虑浏览器兼容性,可能会选错。2018年的语境下,flex已经足够安全,但有些老项目里可能会考查position + transform的兼容性更好。
选择器优先级也几乎是必考。规则很简单:!important> 内联样式 > ID选择器 > 类选择器/属性选择器/伪类 > 元素选择器/伪元素。但数字计算容易在长选择器链上出错。比如#app .content .item是1, 0, 2,而.content .item .active是0, 0, 3,前者优先级更高。注意这里不是十进制加法,也不是“ID多就一定赢”,而是按位比较,从左到右,ID选择器数量多的赢;相同再比较类选择器数量;再相同才比较元素选择器数量。很多人在这种地方丢分,不是不知道优先级,而是比较方式搞错了。
4.3 HTML语义化与DOM操作的隐藏考点
HTML部分的客观题,大概率会考语义化标签的选用,比如<header>、<nav>、<article>、<section>、<aside>、<footer>分别应该用在什么场景。这类题不难,但要注意别选错:<section>不是“通用的容器标签”,如果只是想包一层div做样式,应该用<div>而不是<section>,因为<section>带有“文档中的独立部分”这层语义。标题往深了说,语义化对SEO和无障碍访问都有影响,这个答题的时候可以作为辅助判断依据。
DOM操作的客观题也不少,比如“document.getElementById返回的是什么”。这个问题有两个坑:一是某些浏览器会把name为指定值的元素也返回,二是该方法的返回值在绝大多数标准场景下是一个元素对象,而不是数组或集合。而document.querySelectorAll返回的是静态的NodeList,不是HTMLCollection也不是数组,所以不能直接调用map方法。这个问题常和“getElementsByTagName返回的是动态集合”放在一起考,用来判断你分不清“静态”和“动态”的区别。
我复习DOM这块的经验是,一定要区分几个容易混淆的集合类型:HTMLCollection是动态的,NodeList不一定是动态的,querySelectorAll返回的是静态快照。一旦出题人把getElementsByClassName和querySelectorAll放在同一个题里,对这两个概念的理解就直接决定对错。
5. 框架、工程化与算法:那个年份的取舍与风向
5.1 Vue与React的常见选择题点位
2018年笔试里的框架题,已经占了不少比重。Vue的题爱考响应式原理和生命周期,React的题爱考setState和虚拟DOM。
Vue 2的响应式原理是基于Object.defineProperty的,它会遍历data对象中的每个属性,把每个属性都转成getter/setter。因此,Vue 2中给对象新增一个属性(比如this.obj.newKey = 1)是不会触发视图更新的。正确姿势是用Vue.set或者this.$set。这个考点在当年的笔试题里出现频率很高,因为很多初学者就是在这里踩坑。放在今天的语境里,Vue 3改用Proxy之后已经没有这个问题了,但如果这套题考的是Vue 2,答案还是老一套。
生命周期顺序也是个经典考点。Vue 2的父子组件生命周期顺序:父beforeCreate→ 父created→ 父beforeMount→ 子beforeCreate→ 子created→ 子beforeMount→ 子mounted→ 父mounted。一句话总结就是“父组件先创建,但子组件先挂载”。这个结论如果没实际调试过,单靠背很难稳定记住,因为它的顺序不是直觉能推出来的。既然这套题里出现过,刷题的时候最好自己写个demo,把每个钩子的输出打一遍,建立直观记忆。
React的题在2018年已经会考Fiber了,但不会太深,常见的还是setState的异步批量更新。连续调用三次setState(count => count + 1),最终count只会加3,而且如果用的是函数式更新就不会出现覆盖问题。这个知识点在笔试题里常以“下面哪种写法能保证状态正确累加”的形式出现,区分度在于你知不知道setState在合成事件和生命周期里是异步批量的,而在原生事件和setTimeout里是同步的。
5.2 工程化与Webpack:客观题考到哪一层
工程化方向的客观题,主要是围绕Webpack来出。2018年最常见的考法是问你Webpack的loader和plugin有什么区别。loader负责对模块的源代码进行转换,比如把ES6转成ES5、把Sass编译成CSS、把图片转成base64;plugin则处理更大范围的任务,比如代码压缩、资源管理、环境变量注入。一句话:loader是“转换器”,plugin是“扩展器”。
还有一个高频考点是source map。题目可能会问“生产环境应该使用哪种source map”或者“eval-source-map和source-map有什么区别”。当年很多同学的误区是“上线也开source map”,这其实会暴露源码。原则上生产环境可以用nosources-source-map这种不带源码内容的类型,或者干脆关闭,只在测试环境开启。这类题的背后其实是工程化素养,考的不是API,而是你有没有真正部署过项目。
5.3 算法与数据结构:客观题里的复杂度计算
前端笔试的算法部分,客观题一般不会太难,但会考复杂度。比如问“冒泡排序在最坏情况下的时间复杂度是多少”,答案是O(n²);问“在有序数组中二分查找的时间复杂度”,答案是O(log n)。还有些题会结合数据结构选型:频繁在数组头部插入元素,应该用哪种结构?
这里有个容易混淆的点:数组在头部插入是O(n)的,因为需要移动后面的所有元素;而链表在头部插入是O(1)的,只需要改指针。但如果题目说的是“数组的push操作”,那在JavaScript里一般是O(1),因为是在尾部追加,但也可能触发扩容。笔试的客观题很少会考虑扩容这种细节,但如果你能说出“均摊O(1)”,在主观题环节会很加分。
栈和队列的考点更直白:JS数组的push和pop实现的是栈,push和shift实现的是队列(但shift是O(n)操作,性能差)。有一种常见考法是“用两个栈实现一个队列”,这题如果以选择题出现,多半不是让你写代码,而是问“进队和出队的平均时间复杂度”。答案是入队O(1)、出队均摊O(1),因为每次出队时如果辅助栈为空,才需要把主栈的元素全部弹过去一次。
6. 做真题的正确姿势:从对答案到建立知识网络
6.1 复盘时把每道题拆成知识图谱
我刷360这套题的时候,除了对答案,还会把每道题背后涉及的知识点拆出来,画成一张自己的知识图谱。比如一道“HTTP缓存”的选择题,我会沿着它延伸出:强缓存和协商缓存的区别、Cache-Control有哪些常用指令、ETag和Last-Modified谁优先、304状态码什么时候出现、刷新页面和首次访问的缓存策略有什么不同。这样一道题就变成了一组知识点的入口,比单独背十道题效率高得多。
这种拆解式的复盘,可以把零散的知识点串成网。比如从“事件循环”出发,可以连到宏任务、微任务、Promise、async/await、DOM事件回调、requestAnimationFrame;从“浏览器渲染”出发,可以连到CSSOM、重排、重绘、合成层、性能优化。知识一旦成网,考试时遇到没见过的题也不会慌,因为你会用已有的知识去推理。
6.2 错题档案:不要只看正确答案
错题整理是备考里最枯燥但最有价值的环节。我自己的习惯是,不仅要记录正确答案和解析,更要把“我当时为什么选错”写下来。如果是知识盲区,就去补对应章节;如果是看题不仔细,比如漏看了“选出错误的一项”,那就要提醒自己考试时先圈出题干的限定词。
另一个可以尝试的方法是“假装自己是出题人”。做完一道题后,想一想:如果我要在这道题的基础上变一个条件,应该怎么变?比如原题是“把var换成let后输出是什么”,那我可以继续想:如果把setTimeout改成Promise呢?把循环次数从3改成5呢?这种“变题训练”能帮你在考场上应对陌生感,因为出题人翻来覆去就是那几个考点,换的只是外表。
6.3 客观题的临场时间分配
客观题虽然每道题占用的时间短,但整套题做下来,时间管理依然重要。我的策略是:第一遍把会做的题直接选掉,不会的题标记出来先跳过;第二遍回头处理标记的题,优先做那些“排除两个选项后可以蒙”的题;最后如果还有时间,再集中精力算复杂度或者读代码输出题。
对于完全不会的题,也不要直接乱蒙。排除法在技术客观题里特别有用,因为很多时候错误选项的错误点很明显,比如“ES6不支持箭头函数”“flex布局一定是两列”这种绝对化表述,基本可以直接排除。你可以把不确定性从四选一降到二选一,这样蒙对的概率就翻倍了。这套题刷完之后,我个人最大的体会是:客观题最大的价值不是拿那一两分的对错,而是通过它,把那些你以为自己懂、其实经不起推敲的知识点全部暴露出来。每次做错一道题,我都会逼自己去把对应章节重新翻一遍,然后自己给自己讲一遍。这个过程比刷十套新题都管用。如果你也是准备春招的应届生,或者工作两三年想系统查漏补缺,建议把这套题放下“考过就扔”的偏见,静下心来把每道题背后的原理抠一遍,你不会后悔的。