校招前端笔试高频考点解析:从闭包、事件循环到浏览器缓存
2026/9/5 9:35:03 网站建设 项目流程

早两年校招季,前端岗的笔试几乎是清一色的“JS基础+手写题”,欢聚时代2018校招那套web前端B卷算是其中很有代表性的。整体给人的感觉是:只要你认真写过半年以上页面,大部分题都能动笔;但能不能拿高分,取决于你平时写代码时有没有琢磨过“为什么”。这份试卷放到现在准备校招、社招的同学手上,依然有参考价值,因为它的核心考点——作用域、闭包、异步、布局、缓存、工程化——恰好是web前端开发这行吃了十年还没过时的基本功。就算你已经工作几年,把它当一套基础自测题做一遍,也能发现自己哪些地方“会用但讲不清”。

这篇文章不打算贴“原题答案”,而是从出题人的视角把题拆开,讲讲每类题目在考什么、怎么答能得分、有哪些现场容易翻车的点。也欢迎你自己先找张纸,限时90分钟做一遍,再回来对思路。

1. 试卷整体考察思路:2018年校招web前端要的是什么样的人

1.1 一份B卷背后的岗位画像

看一份笔试之前,先要搞清楚公司要招什么样的人。2018年前端环境大概是什么状态?Vue 2和React 16已经大面积落地,webpack 4刚出不久,ES6+基本成为默认语法,小程序开始冒头,TypeScript还没有像今天这样成为必选项。所以那个阶段的校招,面试官最担心的不是“候选人不会新框架”,而是“基础不牢、上来就背框架API”。

B卷作为非第一志愿或补录批次的题目,风格一般比A卷更“稳”。A卷敢出偏题怪题去筛尖子,B卷更看重平均水平——能不能干活、有没有基本工程素养、代码习惯好不好。这导致B卷里大量的题都落在“语言基础、浏览器机制、手写代码”这三个安全区里,真正的坑往往藏在“看起来会做,但又没完全会做”的题上。

我当时带团队面试新人,最喜欢看的就是这种卷子的简答题。选择题可以蒙,代码题可以抄,但简答题要你用几十个字讲清楚一个概念,三句话就能看出你是背的还是懂的。

1.2 题型分布与答题时间策略

按常见B卷的结构,题型大致有这么几块:

题型考察方向参考分值建议用时
选择题基础记忆、易混淆概念20分左右10~15分钟
简答题概念归纳、原理表达20分左右15~20分钟
手写代码题编码能力、边界意识40分左右35~40分钟
综合/开放题方案设计、性能优化20分左右10~15分钟

这里有个反直觉的建议:先把手写代码题看完,再回头做选择题。手写题通常放在试卷中后段,分值最重,如果你一开始在选择题上卡了太久,后面代码题可能连读题时间都不够。我见过很多考生前面选择做得特别爽,最后一道20分的综合题只写了三行字,非常可惜。

如果按100分估算,代码题基本占了四成。这说明出题人默认“能写出来”比“能说出来”更有说服力。你答选择题的时候,心里就要有数:后面还有几道大代码题在等你,该放弃的不要恋战。

2. 核心考点逐个拆解:JS、浏览器、CSS这些题到底在问什么

2.1 变量提升与闭包:输出题到底在问什么

B卷第一类高频题是“下列代码输出什么”。这类题看起来是送分,其实区分度极高。比如下面这个经典写法:

for (var i = 0; i < 5; i++) { setTimeout(function () { console.log(i); }, 100); }

如果没学过闭包,可能脱口而出“0、1、2、3、4”;实际输出是五个5。原因是var声明的i挂在全局/函数作用域上,循环结束后i已经变成5,定时器回调执行时读到的都是同一个i。改成let i或者用IIFE包一层,才能输出01234。

这道题扩展出去还有一串考法:函数声明与变量声明的提升顺序、let的暂时性死区、typeof一个未声明变量的结果。比如typeof a如果a未声明,结果是"undefined"而不是报错;但如果先console.log(a)var a = 1,结果是undefined而不会抛异常。这些细节不是要你背,而是在考你脑子里有没有完整的执行上下文模型。

闭包的隐藏考点还不止于此。面试官会追问“闭包会造成什么问题”——典型答案是内存泄漏风险,因为内部函数长期持有外部变量引用,外部变量无法被回收。但如果你能补一句“现代浏览器引擎已经做了优化,不是所有闭包都会泄漏,只有被外部引用逃逸的才会”,这题基本就满分了。

2.2 事件循环:Promise、setTimeout、async的先后顺序

异步题是前端笔试的“必争之地”。B卷里最经典的一道长这样:

console.log('script start'); setTimeout(function () { console.log('setTimeout'); }, 0); Promise.resolve() .then(function () { console.log('promise1'); }) .then(function () { console.log('promise2'); }); console.log('script end');

输出顺序是:script start、script end、promise1、promise2、setTimeout。原理是JS引擎把任务分为宏任务和微任务,同步代码执行完后,先清空当前宏任务下的所有微任务队列,再取下一个宏任务。Promise.then属于微任务,setTimeout属于宏任务,所以即使setTimeout延迟0毫秒,也得排在微任务后面。

2018年的卷子还不流行async/await的输出题,但放到现在,你得把await看成“让出线程的Promise语法糖”。await fn()后面的代码相当于.then里的回调,整体还是按微任务优先级来排。有些同学把async当成同步函数,一遇到await就卡壳,本质还是没有理解事件循环的推进顺序。

这部分想拿稳分,建议自己画一张“执行栈 + 宏任务队列 + 微任务队列”的图,把定时器、Promise、DOM事件、requestAnimationFrame分别归类。能把微任务比作“插队窗口”、宏任务比作“排队窗口”讲给面试官听,说明你真懂了。

2.3 CSS布局:垂直居中、BFC,被问烂但还能翻车

CSS题一般不会太难,但“垂直居中”这种题几乎年年见。2018年标准的答案已经不再是margin:auto那套负margin方案,而是flex大法:

.parent { display: flex; align-items: center; justify-content: center; }

但如果出题人加一句“父容器高度不固定”,很多人就开始犹豫。其实flex方案和grid方案都不依赖父容器高度,align-items: center本来就是拉伸后的交叉轴居中。真正容易丢分的是“子元素定宽定高”时用absolute + transform绝对居中少写一个top: 50%,这种小错误在笔试现场一紧张就会犯。

另一个必考点是BFC(块级格式化上下文)。一道常见题是:父容器里的子元素设置了float,父容器高度塌陷,怎么解决?标准答案是触发父容器的BFC,具体手段包括overflow: hiddendisplay: flow-rootdisplay: inline-blockposition: absolute。但要真讲清楚BFC是什么,需要回到“格式化上下文”这个概念:BFC内部的布局不会影响外部,浮动元素的高度在计算时会参与父容器的高度计算。

很多人会把overflow: hidden当成“万能清除浮动”而不知其所以然,一旦面试官追问原因就露馅。我的建议是,答这一类题时说“让父容器形成独立的BFC,内部浮动元素不再溢出”,这句话体现的是原理层面的理解,比背三种触发方式有用得多。

2.4 浏览器缓存与HTTP状态码:能不能把ETag讲清楚

简答题里十有八九会出现“强缓存与协商缓存的区别”。强缓存是浏览器不向服务器发请求,直接本地读,对应ExpiresCache-Control;协商缓存是浏览器带着标识问服务器“这个资源还能不能用”,对应Last-ModifiedETag

这里有一个非常好的答题框架,直接套用就能拿高分:

缓存类型关键响应头判断依据状态码
强缓存Cache-Control: max-age当前时间是否在有效期内200 (from disk cache)
协商缓存Last-Modified / If-Modified-Since修改时间是否变化304
协商缓存ETag / If-None-Match内容标识是否变化304

如果只答到这里,大概能拿七成分。想拿满分,要补充两句话:第一,Cache-Control的优先级高于Expires,现在主流推荐只用Cache-Control;第二,ETagLast-Modified更可靠,因为修改时间无法解决“文件改了内容但修改时间被还原”或者“同一秒内多次修改”的问题。

我那时候笔试答这道题,顺手写了“静态资源文件名带hash指纹,可以把缓存时间设到一年,上线时无感知更新”,面试官在卷子上打了个勾。这说明什么?说明校招笔试不是只考记忆,更考你有没有真实发布经验。

3. 手写代码题的解题套路:从读题到能跑通的完整思维链

3.1 防抖与节流:写的不是函数,是边界意识

手写题里出现频率最高的就是“防抖节流”。防抖(debounce)是“事件停止触发后等待t毫秒再执行”,适合搜索输入;节流(throttle)是“每隔t毫秒最多执行一次”,适合滚动和 resize。

一个能直接过面试的防抖实现长这样:

function debounce(fn, wait, immediate) { let timer = null; return function () { const context = this; const args = arguments; const callNow = immediate && !timer; clearTimeout(timer); timer = setTimeout(() => { timer = null; if (!immediate) fn.apply(context, args); }, wait); if (callNow) fn.apply(context, args); }; }

这里有几个细节,恰恰是评分点。第一,this指向必须绑定到调用防抖函数的对象上,不能直接用fn()调用;第二,要处理参数透传,用arguments或者剩余参数;第三,immediate参数表示首次是否立即触发。很多考生只写了个简单版,没有处理this,一看就知道是从博客抄的,看不懂为什么需要apply

节流的实现思路也差不多,可以用时间戳记录上次执行时间,也可以用定时器。我在批改时最看重的是“定时器初始化时是否置空”,这能看出来候选人会不会内存泄漏的常见坑。顺带提一句,只要把这两个函数写熟,笔试里的“实现一个函数防抖/节流”基本就能快速拿分,是性价比最高的手写题。

3.2 深拷贝的边界:从JSON.parse到循环引用

几乎每套前端笔试题都有深拷贝。最简单的答案JSON.parse(JSON.stringify(obj))能拿两分,因为它有致命缺陷:无法处理undefined、函数、Symbol、正则、Date,更处理不了循环引用。

递归版本是大多数人的选择:

function deepClone(obj, map = new WeakMap()) { if (typeof obj !== 'object' || obj === null) return obj; if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); if (map.has(obj)) return map.get(obj); const clone = Array.isArray(obj) ? [] : {}; map.set(obj, clone); for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { clone[key] = deepClone(obj[key], map); } } return clone; }

这里的核心加分点有两个:一是用WeakMap记录已拷贝对象,解决循环引用;二是对DateRegExp做特判,避免它们被打散成普通对象。笔试现场时间紧张,能写出递归版本已经不错,但如果你能主动提到“循环引用会导致栈溢出,需要用一个Map记录克隆过的对象”,就已经超越大部分考生了。

我经常跟新人说,深拷贝是检验“数据结构意识”最好的题目。很多人背了代码,但没想过为什么用WeakMap而不用Map——因为WeakMap的键是弱引用,不会阻止对象被垃圾回收,在拷贝大对象时更安全。这层原因讲清楚,比写完整个函数更让面试官记住你。

3.3 数组去重的“三代”写法

数组去重看似简单,但要写出层级感,一样能看出水平。第一代是双重循环,能跑但效率低;第二代是indexOfincludes,简洁很多;第三代是Set

const unique = [...new Set(arr)];

如果到这里就停,还是不够。真正拉开差距的是边界情况:NaNSet里只有一个,用indexOf去重时NaN永远找不到;字符串和数字1'1'Set里是两个不同值;如果数组里还有对象,Set去重要求引用相同,不能按内容去重。

加分的做法是补充“需要按属性去重”的场景:用Map的键来做唯一性判断。比如根据id字段去重:

const uniqueById = [...new Map(arr.map((item) => [item.id, item])).values()];

这样一道“数组去重”题,你可以从最简单写法一路聊到复杂场景,既展示基础熟悉度,又展示工程思维。笔试不是只看结论对错,更看你能不能把知识点串起来。

3.4 综合题思路:实现一个模板字符串解析

有些B卷会出一道综合题,比如“实现一个简易模板引擎”,输入这样的字符串:

var str = 'Hello, {name}! Your score is {score}.'; var data = { name: 'Alice', score: 95 };

期望输出Hello, Alice! Your score is 95.,并且要求考虑数据缺失的情况。

这题的解析思路很简单,用正则匹配花括号内容,再替换成对应属性:

function render(template, data) { return template.replace(/\{(\w+)\}/g, (match, key) => { return key in data ? data[key] : ''; }); }

这道题在网上流传的版本里还有一个变形:要求支持{user.name}这种路径访问。这时候就不能用简单的key,得把点号路径拆开逐层取属性:

function resolvePath(obj, path) { return path.split('.').reduce((acc, key) => (acc == null ? acc : acc[key]), obj); }

综合题给我们的启示是:出题人不会考你背诵某个API,而是考你“面对一个开放式需求怎么拆解”——先拆出占位符,再拆出数据查找逻辑,最后考虑边界。如果你在答题卡上能写出“先用正则匹配,再递归解析嵌套属性”这个思路,哪怕代码没写完,也能拿到一半分。

4. 真正拉开差距的题目:工程化与框架初探

4.1 模块化三兄弟:CommonJS、AMD、ESModule

2018年前后的笔试题特别喜欢考模块化规范。原因也简单:那时候webpack已经成为标配,但很多人只会配置loader,不理解模块系统怎么工作。

CommonJS是Node用的,require是同步加载,模块在本地磁盘上,读文件很快,所以同步没问题。AMD是浏览器早期用的,代表是RequireJS,为了解决浏览器里文件异步加载的问题,所以设计了define包裹的回调方式。ESModule是语言层面的规范,import静态导入,支持tree shaking,是前端开发后来的主流方向。

笔试里最常见的问法是“CommonJS和ESModule有什么区别”,参考答法:

  • CommonJS导出的是值的拷贝,ESModule导出的是值的引用;
  • CommonJS可以动态require,ESModuleimport语句必须写在顶层;
  • CommonJS是运行时加载,ESModule是编译时输出接口;
  • 如果模块内部改了值,CommonJS进来的一方拿不到最新值,ESModule能拿到。

如果能再补一句“ESModule在编译阶段就能分析出哪些导出没被使用,从而被摇树优化掉”,说明你对webpack的tree shaking也有概念,这在工程化部分非常加分。

4.2 Vue与React的对比题:会选更要会讲

校招笔试题里,框架题通常不会太深,一般停留在“用过哪个、怎么做数据交互”这种层面。但如果出题人想挑有真实项目经验的人,会出对比型简答题,比如“v-ifv-show有什么区别”“computedwatch怎么选”。

v-if是条件渲染,不满足条件时不渲染DOM节点;v-show是CSS切换,节点一直存在。频繁切换的场景用v-show,初始化时条件基本不会变的用v-if。这是送分题。

computedwatch则要有更深的理解。computed是“由响应式依赖计算出来的值,并且有缓存”,如果依赖没变,多次读取不会重新计算;watch是“观察某个值的变化,执行对应副作用”,适合异步操作或相对复杂的业务逻辑。答题时举一个具体例子比较好:列表筛选用computed,搜索框输入后请求后端接口用watch

那个阶段React也在高频出现,比如“setState是同步还是异步”。标准解释是:在React合成事件和生命周期钩子里是异步批量的,在原生DOM事件和setTimeout里是同步的;不是setState本身异步,而是React的更新调度机制。能答到这一层,说明真的遇到过React的优化机制,而不是只会写JSX。

4.3 从URL输入到页面渲染:一道压轴级的综合题

“在浏览器地址栏输入URL并回车,到页面完整渲染出来,中间发生了什么”几乎是前端面试的超级综合题。它横跨网络、浏览器、渲染三个领域,非常适合出在B卷压轴——因为即使你在某个环节了解不深,其他环节也能拿分。

这道题的标准回答路径是:DNS解析域名得到IP,建立TCP连接,如果是HTTPS还要完成TLS握手,发送HTTP请求,服务器返回HTML,浏览器按HTML生成DOM树,同时解析CSS生成CSSOM树,两者合成渲染树,浏览器根据渲染树计算布局(layout),最后绘制到屏幕。过程中遇到<script>标签还可能阻塞DOM解析,遇到图片会异步加载。

想拿高分的延展点有这些:

  • DNS解析有本地缓存、路由器缓存、递归服务器等多级查询;
  • 现代浏览器一般会建立6个左右同域TCP连接,实现并发请求;
  • HTML解析过程中遇到没有asyncdefer属性的普通脚本会暂停DOM解析;
  • CSS默认不会阻塞DOM解析,但会阻塞渲染,因为渲染树依赖CSSOM;
  • DOMContentLoadedload事件的区别在于是否等到图片等资源加载完。

2018年这道题的考点还是完整链路,放到今天还可以补充HTTP/2多路复用、preload预加载、content-visibility跳过屏外渲染等新机制。但核心链路不变,底层逻辑还是那些。把这道题背熟,对理解前端性能优化有非常大的帮助。

5. 答题策略复盘与踩坑记录

5.1 三个最容易丢分的现场细节

第一,手写题不写注释。不是非要你写注释,而是在关键行后面补一句“这里清空定时器是为了避免重复触发”,一方面帮自己理清思路,另一方面让阅卷人一眼看到重点。很多人写对了但阅卷人找不到得分点,非常亏。

第二,简答题只写一句话。比如“v-ifv-show的区别是一个渲染一个不渲染”,这句话没错但太单薄。聪明写法是“v-if是条件不满足时不渲染DOM,属于真正的按需渲染;v-show是节点始终存在,只是切换display属性。频繁切换用v-show,初始条件就几乎不变用v-if。”先结论、后原理、再举例,这个结构在笔试题里非常吃香。

第三,遇到不会的题直接留白。其实填空题、简答题写上你推断的答案大概率有分,就算不完全对,也能让阅卷人看到你的思考路径。代码题不会写完整实现,把伪代码写出来,把思路一步步列出来,也能拿一半分。我批过很多卷子,最怕的就是那种大面积空白,想给分都找不到落点。

5.2 答题时间分配与做题顺序

再强调一遍顺序:开考后前5分钟,快速浏览全卷,标记出代码题的位置和分值;然后按“选择 -> 简答 -> 手写 -> 综合”的顺序做,但每一类题型内先做有把握的。

选择题控制在每题1分钟之内,犹豫超过2分钟直接标记跳过。简答题每题控制在5分钟,名词解释只要点出核心概念加一句应用场景就足够。手写题每道控制15分钟左右,先搭框架再补边界,不要一上来就在编辑器式头脑里抠细节。

如果最后还剩10分钟,回头检查两个地方:一是代码题有没有写分号、括号匹配,二是综合题有没有漏掉“考虑异常输入”这种加分语句。这种临门一脚的细节,经常能救回5分左右。

5.3 现在回头看:这些考点还重要吗

这套B卷放在2018年看,代表的是那个时代对“基础扎实的web前端开发”的期待。放到今天,考点重心肯定有变化:TypeScript成为基本盘,框架考察深入到源码级diff和响应式原理,工程化变成场景题而不是概念题,还多出了性能监控、微前端、AI辅助开发等新方向。

但底层的东西一点没变。事件循环、作用域闭包、缓存策略、渲染流程,依然是你排查任何前端线上问题的根基。当年考的是手写防抖,今天考的是“为什么这种频繁触发的场景会卡顿,怎么解决”;当年考的是深拷贝循环引用,今天考的是不可变数据在状态管理里的意义。题型变了,问题本质没变。

所以如果你现在准备前端相关岗位,我建议别把这份老卷子当成“真题背诵素材”,而是当成“基础自测清单”。做错的地方不要只看正确答案,去翻一翻对应的MDN文档或源码实现,把“这个API为什么这么设计”想明白,比多刷十套题都管用。

我个人在带新人时,还会让他们把这套题重新做一遍,但要求用TypeScript重写手写题,并补上单元测试。这一下就把“会做题”升级成了“会工程化”,也顺便检验了代码风格和测试意识。说到底,笔试只是入场券,能持续把代码写清楚、把问题讲明白的人,才是这个岗位真正需要的人。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询