前两天整理电脑资料,翻到了一沓2020年校招季存的笔记,其中一份就是小红书前端校招的笔试题卷三。当时很多同学被这张卷子问住,考完出来直呼“前端怎么还能这么考”。后来我带实习生、做面试官的时候,也经常拿这套题改吧改吧当模拟卷用,因为它的考点覆盖和出题思路,放在今天来看依然很有参考价值。
这篇文章不是单纯把题贴出来,而是我当时做完之后,一道一道复盘过的版本:题型分布、考点解析、踩坑点、最优解思路,以及备考时该往哪个方向使劲。适合正在准备前端校招的应届生,也适合刚入行想补基本功的前端新人。不管你是照着刷题还是想了解大厂笔试都考什么,这份复盘都能帮上忙。
1. 这份卷子考什么:题型、时长和出题逻辑
先说整体体感。卷三的题量中等偏上,一共十六道题左右,答题时间60分钟,分成了单选、多选、手写编程题和简答题四块。前面十道左右是选择题,穿插两道左右的手写题,最后两三道是偏问答和设计类的。
很多第一次参加校招笔试的同学容易犯一个认知错误:以为前端笔试就是考背概念。实际上,这卷子里几乎没有“直接背能拿分”的题目,所有选择题都给了代码片段,让你推结果。这说明出题人真正想考察的不是“你知不知道”,而是“你想不想得清楚”。同样一个知识点,背定义只能拿基础分,能写出运行结果、说出为什么,才是他们筛选人的标准。
从考点分布来看,卷三覆盖得比较均衡:
| 考察方向 | 大致题数 | 占比感觉 |
|---|---|---|
| HTML/CSS基础 | 3-4题 | 20%左右 |
| JavaScript核心(类型、作用域、闭包、事件循环) | 5-6题 | 35%左右 |
| 手写代码(防抖节流、深拷贝等) | 2题左右 | 20%左右 |
| 框架/工程化/网络(React、webpack、HTTP缓存) | 3-4题 | 25%左右 |
这套比例和大多数中大厂校招笔试题是接近的,核心权重在JavaScript和手写代码上。CSS也考,但不会考什么特别炫酷的布局奇技淫巧,而是真正常用的、写页面一定会遇到的东西。所以备考阶段如果你时间有限,先把JS啃透,再回头补CSS细节,优先级会高很多。
60分钟的时长拆到十六道题上,平均每题只有3-4分钟。这意味着前面选择填空如果卡太久,后面的手写题会非常被动。我后面会专门聊时间分配的问题,这里先提醒一句:校招笔试不是“每道题都要完美答完”,而是“在有限时间内拿到最大总分”。
2. 基础考点逐题拆解:HTML/CSS/JS易错题详解
2.1 HTML语义化标签的坑:section和article到底怎么选
第一道选择题问的是语义化标签的用法,给了四个选项中关于section、article、div使用场景的判断题。
我猜不少人会在这道题上犹豫。section和article都是HTML5用来表示“一块内容区域”的标签,很多同学背了定义,但一落到具体场景就分不清。这里有我后来整理出的判定方法:article表示的是一个独立完整的、可单独分发或复用的内容单元,比如一篇帖子、一条评论、一个产品卡片;section表示的是一个有主题的内容分组,它通常自带一个标题(h1-h6)。如果一块内容拆开之后本身没有意义、只是单纯为了样式方便包一层,那就用div。
当时有不少人误以为“section里必须有article”或者“article里不能套section”,这俩都是错的。正确的嵌套逻辑是:article之间可以并列,article内部可以有多个section来描述不同部分;section内部也可以再套section。一道选择题考到这个深度,其实是在提醒你,标签不是记住了名字就行,而是要看它对应的语义模型。
2.2 Flex布局居中方案:三行代码的边界情况
选择题里有一道问“如何让一个不定宽高的子元素在父容器中水平垂直居中”,选项给了几个Flex写法。
这道题按说很基础,但错误率意外地高,主要出在对align-items属性的记忆混淆上。水平居中用的是justify-content: center,垂直居中用的是align-items: center,这俩是主轴和交叉轴的关系。父容器加了display: flex之后,默认主轴是水平方向,所以justify-content控制的是左右方向的分布,align-items控制的是垂直方向的分布。
我见过有同学把这两个写反,还说“明明没问题”。如果有这种困惑,试着把flex-direction改成column,主轴就变成垂直方向,两个属性的效果也交换了。这么在脑子里过一遍,就不容易再混。这道题的结论很简单:给父容器加display: flex、align-items: center、justify-content: center,子元素不管宽高是定值还是由内容撑开,都能居中。核心是理解主轴概念,而不是背那三行代码。
2.3 CSS选择器优先级:为什么写了一大堆类名还是覆盖不了
CSS优先级这道题我印象很深,给了一个嵌套了好几层类名的HTML结构,然后要求判断最终文字颜色。选项里故意放了好几种看似“写得越多越有效”的迷惑项。
优先级规则说穿了就一句话:权值相加。!important > 行内样式(1000)> id选择器(100)> 类/属性/伪类(10)> 标签/伪元素(1)> 通配符(0)。两个选择器作用在同一元素上时,谁的权值大听谁的;权值相同,后面的覆盖前面的。
但这道题的陷阱不在这里,而在于很多人把“选择器写得更具体”和“选择器数量更多”混为一谈。比如.container .item .text这种三个类名相加的权值是30,而#app .text的权值是110,后者虽然只写了两个选择器,但优先级反超。我当时就在旁边备注了一句“面试爱考的不是你会不会数权值,而是你会不会处理嵌套关系”,想表达的核心是:写CSS的时候尽量不要靠堆选择器来解决优先级问题,而是在命名层面就用BEM这类规范解决问题,这是工程化的基本功。
2.4 类型转换:[] == ![] 到底是不是true
这张卷子的第一道JS选择题,绝对是全卷最劝退的题之一。它问的是[] == ![]的结果是多少。
很多同学看到[]和![]就懵了。实际推理过程并不复杂:![]的优先级高于==,所以先算![]。空数组也是个对象,转布尔是true,取反后是false。原式就变成了 [] == false。等式两边有一个是布尔值,按规则先把false转成数字0,等式变成 [] == 0。然后对象和数字比较,把[]转成原始值,先试valueOf返回的还是空数组,不是原始值,再调toString得到空字符串,等式变成 "" == 0。然后字符串再转成数字,空字符串转成0,最后0 == 0是true。
整个链条走完要经过四步转化,每步都不能漏。这是典型的“JavaScript隐式类型转换”考点,也是笔试出题人最喜欢拿来制造区分度的题。想在这类题上不丢分,没捷径,把ToPrimitive和ToNumber的规则整理一张表,再找二三十道类似的题目练到能默写推导过程,基本就稳了。
2.5 作用域和闭包:经典计数器背后是执行上下文的销毁与保留
有一道填空题给了这样的代码:
for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 100); }问输出什么,以及怎么改才能输出0、1、2、3、4。
输出全部是5,这个结论大部分同学能答对。但第二问的改法就各显神通了。最简单的就是把var改成let,让每一轮循环都生成独立的块级作用域绑定。另一个思路是包一层立即执行函数,把i显式传进去。
这道题背后真正考察的是闭包的本质:内部函数引用了外部变量,当外部函数执行结束、执行上下文销毁时,被引用的变量不会跟着销毁,而是被内部函数的引用捕获。用let和用IIFE两种方案都能解决,但出题人其实更期待你顺手讲出它们各自的原理差异,let是语言层面新语义,IIFE是模拟块级作用域的经典做法。笔试时直接写答案就好,面试如果追问,能说清这层区别会加分不少。
2.6 事件循环与输出顺序:宏任务、微任务的执行时机
事件循环是前端笔试的高频题,卷三里自然也没落下。给了一段混有Promise、setTimeout的代码,要求写出输出顺序。
这类题我就说一个最重要的心得:先画两个队列,一个是宏任务队列,一个是微任务队列。每执行完一个宏任务,就把当前微任务队列里所有任务全部清空,然后再从宏任务队列里取下一个。代码从头到尾按顺序推演一遍,推完再对照答案,比死记硬背结论靠谱得多。
有个细节值得单独说:Promise构造函数里的代码是同步执行的,resolve之后回调进的是微任务队列。很多人一看到Promise就默认“都是异步的”,结果把构造函数那部分的同步输出顺序搞错了,一道题直接白给。我当时复盘时在旁边写了句话提醒自己:同步代码、微任务、宏任务,三种级别,顺序永远是从上到下先同步,再微任务,再宏任务,每次宏任务结束后都要再检查一次微任务队列。这题丢分不是不会,而是把同步和异步的判断搞混了。
3. 手写题怎么拿分:防抖、深拷贝和现场思路
3.1 防抖和节流:别光背代码,说清楚适用场景
卷三的手写题第一道是“手写一个防抖函数”。这道题乍一看很基础,但拿满分并不容易。我当时的写法是:
function debounce(fn, wait) { let timer = null; return function(...args) { if (timer) clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, wait); }; }这里最容易被忽略的有两个点。第一个是this的绑定,如果不用箭头函数,直接写setTimeout(function() { fn.apply(this, args) }),this就会丢失;用箭头函数可以保证回调里的this就是外层return的那个函数的this,也就是调用方。第二个是args的透传,得把事件对象e传给fn,不然防抖出来的函数,监听器里拿不到事件对象。
如果能顺手提一句防抖和节流的区别,这道题会显得更完整。防抖是“停止触发后才执行”,适合搜索框输入、窗口resize这种操作;节流是“固定时间内最多执行一次”,适合滚动监听、按钮频率限制。在笔试题旁边用一行注释写清楚适用场景,面试官看到的就不只是一段代码,而是工程判断能力。
3.2 深拷贝:循环引用这个坎,当场拦住多少人
卷三另一道手写题是深拷贝。如果只要求基础版,很多人能写出来:
function deepClone(obj) { if (obj === null || typeof obj !== 'object') return obj; const result = Array.isArray(obj) ? [] : {}; for (let key in obj) { result[key] = deepClone(obj[key]); } return result; }但这道题后面还附了一问:对象里存在循环引用怎么办。比如a.self = a,如果直接用上面这个递归版本,会无限递归,最终栈溢出。
我当时用的解法是加一个缓存容器(WeakMap),每次拷贝前先检查缓存里有没有这个对象,有就直接返回之前拷贝的结果,没有就存进去再递归。这样一个循环引用对象只会被处理一次,后续引用直接复用。后来这道题在面试环节也经常被翻出来追问,还会继续问到Date、RegExp、Symbol这类特殊类型要怎么处理,所以如果笔试时间宽裕,尽量把基础版写完,再写一点特殊类型判断,比最后留一个半成品要好。
3.3 手写题的现场策略:先保正确,再补优雅
手写题是整个笔试最考验心态的部分。很多人一看到代码题就紧张,担心写得不够优雅,担心变量命名不好看,结果越写越乱。
我的建议是,手写题按“正确优先、结构其次、优雅最后”的顺序推进。先写出一个能跑通的版本,哪怕是一层浅拷贝加递归,先保证主流程正确;然后再补循环引用、补特殊类型,顺手把解构、展开这些语法用上;有富余时间再优化代码风格和注释。笔试是按点给分的,代码跑通比代码优雅值钱得多。如果写不出完美版,也要把思路用注释写下来,比如“这里用WeakMap存储已拷贝对象,防止循环引用”,不会完整写出来,但让评分人看到你的思路,也能挽回不少分。
4. 框架与网络题:从会用到会答为什么
4.1 setState到底是同步还是异步:场景决定答案
卷三有一道React相关的题,问setState之后能不能立刻拿到最新的state值。答案是:在React合成事件和生命周期函数里是异步的,在setTimeout、原生事件监听器里是同步的。
这个题坑就坑在“非黑即白”。很多同学背了一句“setState是异步的”就去答题,结果忽略了React18之前,不同调用场景的表现并不完全一致。我当时做这道题的时候,不只在选项上纠结,还在旁边把原因捋了一遍:setState之所以设计成异步,是为了把同一事件循环里的多次更新合并成一次重新渲染,减少不必要的渲染开销。
到了下一轮面试,这道题还会升级成“React18自动批处理有什么变化”,所以在笔试复盘时,把React从15到18的更新机制变化整理一遍,性价比很高。至少你要知道:React18之后,即使是在setTimeout、Promise回调里,setState也会被自动批处理,只有在需要强制同步读取最新状态时,才用flushSync之类的方案。
4.2 虚拟DOM的key:为什么不能用index
卷三的问答题里有一道关于key的:“列表渲染时,为什么key不建议用index”。这道题看起来简单,但很多人回答停留在“如果用index,删除某个元素后,后面的元素会错位”。
其实这只是表象。真正本质的原因是:key的作用是让React能识别每个列表项的身份,在diff过程中判断哪些元素是新增、删除、还是只改了位置。如果拿index当key,当数组头部被插入或删除了一个元素,后面所有元素的index都变了,React会认为它们是不同的元素,于是把整列节点都卸载重建,这既浪费性能,也可能导致有状态子组件状态错乱。
我在面试里追问过候选人“错乱的具体场景是什么”,能答出“输入框在移动位置后内容被清空”的,基本就说明他是真遇到过类似问题。如果你现在刷题刷到这里,建议自己写个简单demo试一遍,点按钮从数组头部插入一条,看看带输入框的列表项表现,一跑便知。
4.3 HTTP缓存:强缓存和协商缓存,谁先谁后不能反
网络题在卷三里占了两道左右,其中一道是HTTP缓存机制的填空:强缓存、协商缓存、ETag、If-None-Match之间的关系。
我见过的最大误区,是把强缓存和协商缓存当成两个平级概念,觉得“强缓存不行就走协商缓存”,顺序倒是没错,但没说明白判断标准。完整的过程是:浏览器发请求时,先查强缓存,如果命中了且没过期,直接用本地缓存,不发请求;如果过期了,就带上协商缓存的标识(If-None-Match对应ETag,If-Modified-Since对应Last-Modified)去问服务器,服务器根据标识判断资源有没有变化,没变化就返回304,浏览器重定向到本地缓存;有变化就返回200和新资源。
这道题的答题关键是把“过期”和“变化”两个词分开。强缓存关心的是“有没有过期”,协商缓存关心的是“有没有变化”。这两个维度完全不同,理清之后,再背Cache-Control和Expires的区别、ETag和Last-Modified的优先级,就会顺很多。
4.4 跨域问题:CORS预检请求到底怎么触发
关于跨域,卷三有一道选择题给了一个POST请求场景,问什么时候会触发CORS的预检请求。选项有“请求带自定义header”“请求Content-Type是application/x-www-form-urlencoded”“请求用GET方法”等等。
按CORS规则,满足下面任一条件就会触发预检:请求方法不是GET/HEAD/POST;请求方法是POST但Content-Type不是表单三种之一(比如application/json);请求带了自定义header;这是简单请求和复杂请求的分界点,核心判断标准是“会不会对服务端数据产生副作用”。
这道题很多同学失误在选项设置上。Content-Type是application/x-www-form-urlencoded和multipart/form-data,都不触发预检;一旦变成application/json,就要预检。我当时在试卷上把这个点单独圈出来,因为这是日常开发最常踩的坑:前后端联调,前端把请求体格式换成了JSON,突然多出一个OPTIONS请求,后端没处理直接报错。这种情况在工作里太常见了,笔试考这个不是没事找事,是真实开发场景的映射。
5. 时间分配复盘:笔试现场我是怎么取舍的
笔试做完后,我复盘了这次答题的时间分配,发现顺序对分数的影响非常大。我的实际时间分配是这样的:
| 题型 | 用时 | 我的策略 |
|---|---|---|
| 选择题前5题(HTML/CSS/JS基础) | 12分钟 | 快速扫题,能确定的直接选,模棱两可的先标记,不恋战 |
| 选择题后5题(事件循环、React等) | 15分钟 | 稍微放慢,在草稿纸上推演代码输出 |
| 手写题(防抖、深拷贝) | 20分钟 | 先写能跑通的基础版,再写进阶点 |
| 简答题和设计题 | 10分钟 | 按要点分条写,写满但不啰嗦 |
| 检查复核 | 3-5分钟 | 只检查标记过的题目和手写题有没有低级语法错误 |
这里想多说一句,标记不确认的题这个习惯很关键。很多人做选择题,遇到不确定的,靠直觉选完就走,后面根本不会再回来想。我的做法是:一眼能确定的直接选;有点模糊的,在旁边画个问号;完全不会的先填一个最可能的,然后继续往下走。等简单题都拿完了,再回头啃不确定的题。这样能保证“会的题不因为时间不够而丢分”。
时间分配还能顺带检验一个东西:你对某个知识点的熟练度。如果一道题你一眼就知道考什么,并且能在30秒内算出来,说明这个点你掌握到位了;如果一道题你反复读了三遍还没抓住考点,大概率不是题目难,而是这个知识点本身没学透。复盘的时候把这些“没学透”的点全部标记出来,一条一条查漏补缺,比盲目刷五十套题都管用。
6. 备考提醒:常见误区与刷题路线建议
6.1 盲目刷面试题和八股文,不如做题后认真复盘一套真题
我到今天都记得一个数据:2020年很多同学备考前端校招,刷了小几百道面试题,背了一堆结论,结果做这套小红书卷三时,选择题还是错了一半。原因很简单,面试题背的是“某道题的答案”,而笔试考的是“知识点的推导过程”。你把结论背得再熟,出题人换个代码片段,条件一变,你就废了。
我建议的刷题方式是:拿到一套真题,先不看答案做一遍,做完后花同样的时间逐题复盘。复盘内容很具体:这道题考察的是什么知识点;我是怎么得出答案的;要是换个条件还能不能应对;我写错的题,错在哪个步骤。把复盘写到自己的错题本里,比刷题本身更重要。错题本是拿来反复翻的,不是写完就扔的。我在带教的时候就一直和新人强调,会做一道题不厉害,能把同类题讲明白才算真会。
6.2 动手写demo和只刷题,差距有多大
有一个让我印象特别深的例子。当时我和一个同学一起准备笔试,他刷了三百多道题,我看他每天刷得也很认真。但有一次我拿同一个React key相关的问题去问他,他忽然卡住了,因为他从来没在真实浏览器里跑过那些代码片段。
纸面刷题有一个致命短板:你永远是在“代码运行之前”判断结果,而不会看到代码运行之后到底发生了什么。前端这个领域是离运行时最近的工程领域,浏览器、网络、渲染都是真实存在的。很多问题,你在浏览器里跑一遍,效果瞬间直观,比背十道题都管用。所以我一直建议准备校招的同学,每周至少花一天时间,把这一周刷题里涉及到的代码片段,在本地起一个最小demo跑一遍。防抖就写个输入框看效果,深拷贝就打印一下循环引用对象会不会报错,HTTP缓存就开DevTools看Network面板里的状态码。只看纸面的话,很多东西你永远理解不到位。
6.3 简历上写“精通”,笔试却翻车,问题出在哪
经常在笔试复盘群里看到一些同学说:“我简历上写了熟悉React和webpack,结果笔试考的webpack我完全不会。”简历和笔试对不上,是2020年校招季很常见的现象,近几年也依然存在。
问题背后其实是技能分层意识不够。简历上写“熟悉”和“了解”,标准完全不一样。熟悉,意味着你能独立解决这个方向的问题,笔试里出现的相关题目必须能做对;了解,意味着你知道有这个方案,给你时间查资料能解决,笔试里遇到不会也很正常。不少同学把两者混着写,结果面试官按最高标准去考你,自然翻车。
我当时给自己定了一个标准:简历上的每一项技术,至少要能回答出“它解决了什么问题、核心技术思路是什么、我实际用过哪个场景、有什么坑”这四个问题。用这个标准过一遍简历,再决定哪些写“熟悉”,哪些写“了解”,笔试和面试都会踏实得多。
6.4 一套知识点查漏补缺清单
结合小红书卷三和我后来经历的校招真题,我整理了一份前端校招笔试的高频知识点检查清单,大家备考时可以对着自查:
- HTML语义化标签、块级元素和行内元素的区别
- Flex和Grid布局,主轴和交叉轴,常用居中方案
- CSS优先级计算,BFC/IFC概念
- JS数据类型、隐式类型转换、深浅拷贝
- 作用域、闭包、this指向、原型链
- 事件循环、宏任务微任务
- 防抖节流、手写Promise、手写数组方法
- 浏览器渲染过程、重排重绘
- HTTP缓存、跨域、HTTPS握手
- React/Vue生命周期、setState、diff、key、Hooks
- 打包工具(webpack)基本配置和构建优化
这份清单不是背一遍就行,而是要求每一条都能“讲出原理+写出代码+说明应用场景”。能做到这个程度,前端笔试基本就稳了。
写在最后的一次复盘
这张卷子做完,给我最大的触动不是哪道题特别难,而是它让我意识到:校招笔试筛选的从来不只是知识的储备量,而是你在有限时间内调用知识解决问题的能力。一道选择题,背后是对原理的理解;一道手写题,考验的是对代码的掌控力和临场心态。
我后来在带新人的时候,也经常建议大家不要只囤面经、刷题集,最好把做过的每一套真题都做一次深度复盘,把错题背后的知识点一一补齐。这比泛泛地刷几十套题更有用。前端这个行业变化很快,工具链一两年就换一批,但底层的JavaScript、浏览器原理、工程思维这些东西,是穿越周期的硬通货。希望这篇复盘能帮你在准备校招的路上少走一点弯路。