前端校招笔试真题解析:欢聚时代2018年A卷考点拆解
2026/9/5 15:36:40 网站建设 项目流程

1. 试卷全景与考察定位

1.1 为什么一份2018年的笔试题现在还值得刷

先交代一下背景。欢聚时代就是大家熟知的YY,2018年这波校招笔试在当年的前端圈里流传度还挺高,原因是它的考察覆盖面比较全,且题型设计踩中了不少前端er的痛处。网络上流传的版本有不少残缺,很多是候选人考完凭记忆拼出来的,原卷已经找不到了。我结合自己做前端面试官的经验和带应届生的复盘笔记,以当年的A卷考纲为骨架,把核心考点逐一拆开,补上解题思路和避坑点。

先说结论:这份试卷虽然出题年份停在2018,但它考察的知识底座到现在都没有过时。HTML、CSS、JavaScript三件套的底层功力,浏览器的运行原理,经典的算法手写题,依旧是大厂校招前端笔试题的固定组成部分。唯一变化的,是近几年的试卷里越来越多地加入了TypeScript、工程化、框架运行时原理、微前端等内容,但基础模块的权重并没有降低。

我之所以推荐大家拿这份题来练,是因为它比较适合用来做“知识体检”。一份卷子做完,哪些地方卡壳,哪些地方能秒答,基本能反映出你前端基础的真实水位。而且,笔试和面试不太一样,笔试考的是你在没有提示、没有交流的情况下,能不能独立还原出一个正确的结论。这个能力,恰恰是很多培训班出身、习惯看视频边看边敲的同学最容易缺的。

对于正在准备校招的应届同学,我的建议是:不要太纠结“这是哪一年的题”,而要把注意力放在“这道题到底在测什么能力”上。下面我按当年A卷的模块结构,把每一类题目的考察逻辑和准备方向捋一遍。

1.2 试卷结构复盘与各模块权重

虽然原始卷面已经不在我手上,但根据当时参加笔试的同学反馈,整个A卷基本覆盖了以下六类内容。

模块常见题型大致权重难度
JavaScript基础输出题、原型/闭包/作用域30%
浏览器与网络事件循环、渲染机制、缓存20%
HTML/CSS布局手写布局、盒模型、居中15%中低
算法与编程题手写数组/字符串处理、排序20%中高
代码阅读与调试给出代码判断输出或bug10%
综合与扩展前端工程化、性能优化5%中高

这个比例在今天依然有参考价值。JavaScript永远是前端笔试的核心,这类题不太需要背,主要靠对语言机制的理解。如果在JS上栽了跟头,后面再扎实的框架经验也难救回来,因为笔试阶段通常不考框架,考的就是你没框架时还能不能写出可靠的代码。

1.3 笔试答题的核心姿势

线上笔试和面试不一样,面试官不在你旁边,你思考的过程没人看,最终提交的答案就是一切。所以答题顺序很关键,我的习惯是先扫一遍全卷,把会做的、能拿分的题先标记出来,优先死磕会做的,再回头啃难题。不要在一道题上死磕超过15分钟,尤其是后面的手写算法题,往往分值更高、性价比更大。

另外,手写代码题不要只写代码,可以在注释里写清思路。现在不少笔试系统支持运行和判题,但即便不支持,注释能帮阅卷人理解你的逻辑,也方便后续面试环节中面试官拿着你的答卷问细节。我在后来做面试官时,最反感的就是答案只给一个函数签名,主体逻辑全空着,那基本等于没做。

2. JavaScript基础题拆解

2.1 作用域、闭包与变量提升

这份卷子里,JS部分的开场题通常是读代码写输出。翻来覆去,考的就是几个点:var/let/const的区别、变量提升、函数提升、闭包、this指向、原型链。

我用当年流传很广的一道经典变形题来演示。

var a = []; for (var i = 0; i < 3; i++) { a[i] = function () { console.log(i); }; } a[0](); a[1](); a[2]();

很多人第一次做这道题,会以为结果是0、1、2。但真正跑出来是3、3、3。原因在于var i声明的变量是函数作用域,整个循环共享同一个i,函数被调用时循环早已结束,i已经变成3。

如果把var i改成let i,结果就变成0、1、2。因为let是块级作用域,for循环的每次迭代都会创建一个新的绑定。

这种题表面考的是作用域,实际上背后是“执行上下文”和“闭包捕获变量”的机制。我在实际阅卷时发现,很多同学知道该把var改成let,但要他说清楚为什么,就支支吾吾了。这里有一个比较直观的理解方式:你可以把闭包想象成一个小房间,房间里的窗户可以看到外面的变量。用var时,所有房间共用同一扇窗户,窗外只有一台数字显示器;用let时,每次循环都新建一台显示器,每个房间看到的是自己对应的那一台。

准备这类题时,建议动笔把执行上下文的内存分布画一遍。面试官后续大概率会追问“闭包会造成内存泄漏吗”“怎么避免”,如果你能顺手答出“引用计数、标记清除、及时释放不需要的引用”,印象分会明显不一样。

2.2 this指向与箭头函数

this指向几乎是必考点。常见出题形式还是给一段代码,让候选人判断输出。核心规律就那么几条,按优先级排序:

  1. new关键字调用,this指向新创建的实例
  2. call、apply、bind可以显式指定this
  3. 方法调用,谁调用指向谁
  4. 普通函数调用,非严格模式下指向全局对象,严格模式下是undefined
  5. 箭头函数没有自己的this,沿用定义时外层作用域的this

真题里最喜欢考的是第3条和第4条结合:

var name = "window"; var obj = { name: "obj", getName: function () { return this.name; }, }; var fn = obj.getName; console.log(fn()); console.log(obj.getName());

这里obj.getName()输出obj,很好理解。fn()是存了引用之后直接调用,相当于普通函数调用,在浏览器环境下this指向window,输出window。很多人栽在第4条,因为没意识到“取方法再调用”会丢失this。

顺带说一个容易混淆的点:回调函数里写this和箭头函数里写this是两回事。事件监听器、setTimeout回调里的普通函数,this指向由调用方式决定,而箭头函数则始终指向定义所在作用域的this。理解了这点,再遇到“Vue methods里为什么箭头函数不推荐”这类扩展题,就能顺藤摸瓜答出来。

2.3 原型链与继承

原型链是JS模块里区分度很高的一块。2018年的A卷,给了一段构造函数和实例的代码,要求判断obj.__proto__ === Constructor.prototype这类表达式的真假。

基本功需要掌握到以下程度:

function Animal(name) { this.name = name; } Animal.prototype.say = function () { console.log(this.name); }; var dog = new Animal("旺财");

此时dog.__proto__ === Animal.prototypeAnimal.prototype.constructor === Animaldog instanceof Animal这几个表达式的结果都要能一口说出来。再深一层,Animal.__proto__ === Function.prototype,因为Animal本身就是Function构造出来的实例;Animal.prototype.__proto__ === Object.prototype,因为原型链最终会收敛到Object。

笔试中关于继承的题,一般会让你手写ES5的寄生组合式继承,或者让你指出几种常见继承方式的优缺点。我见过大量答案是把“原型链继承”“构造函数继承”“组合继承”混在一起说不清楚。要区分的话,记住一个判断标准:子类的实例是否共享父类原型上的引用类型属性、父类构造函数是否被调用了一次还是两次。组合继承有个小问题就是父类构造函数被调用了两次,而寄生组合式继承就是为了解决这个问题的。

3. 浏览器与网络基础题解析

3.1 事件循环:从经典输出题到微任务宏任务

浏览器部分的题目,第一梯队就是事件循环。当年那套卷里有一道流传甚广的题,大概是这样的变形:

console.log(1); setTimeout(function () { console.log(2); }, 0); Promise.resolve().then(function () { console.log(3); }); console.log(4);

执行结果是1、4、3、2。很多人不理解为什么Promise的回调比setTimeout还早。原因是:Promise的then回调属于微任务(microtask),setTimeout回调属于宏任务(macrotask)。当前宏任务执行完后,JS引擎会先把微任务队列清空,再取下一个宏任务执行。

以Chrome的渲染主线程为例,可以简化理解为:每一次从宏任务队列取一个任务,执行过程中如果产生了微任务,全部塞进微任务队列,当前宏任务结束后按顺序清空微任务队列,然后执行渲染相关任务,最后再取下一个宏任务。

我第一次带新人时,经常用“食堂打饭”来比喻:宏任务窗口是普通窗口,微任务是窗口旁边的小卖部,你刚打好饭(执行完一个宏任务),第一件事是先跑小卖部把酸奶买了(清空微任务),然后再去下一个普通窗口打饭。

关于Promise本身,A卷里还有一类改错题,比如要求写出Promise.all的替代实现。给出三个异步请求,要求全部成功后在页面上展示合并后的数据。核心就是Promise.all的用法,以及失败时用catch兜底。这道题看着不难,但实际手写时很多同学会忘记Promise.all的入参必须是可迭代对象,也容易忘记处理“传入空数组”时的边界情况。准备这类题时,建议把Promise的静态方法全过一遍:resolve、reject、all、race、allSettled、any,每个都要知道入参和返回值。

3.2 从输入URL到页面展示:必背主链路

这部分很少以问答题形式出现在笔试卷里,更多是在面试环节追问,但笔试也会通过选择或简答题间接考察。最典型的简答题是:“在浏览器地址栏输入一个网址到页面渲染完成,中间发生了什么。”

完整链路可以拆成以下几段:

  1. DNS解析,把域名解析成IP地址,中间会经过浏览器缓存、系统缓存、路由器缓存、根域名服务器等层级
  2. 建立TCP连接,三报文握手
  3. 如果用的是HTTPS,建立TLS握手,协商加密套件和密钥
  4. 发送HTTP请求,服务器返回响应
  5. 浏览器拿到HTML后,解析HTML生成DOM树,解析CSS生成CSSOM树
  6. DOM和CSSOM合并成渲染树
  7. 布局计算各元素的位置和尺寸
  8. 绘制到页面,合成图层

这段内容虽然长,但它其实是浏览器原理里最好背的。准备方向可以结合性能优化来理解:为什么CSS要放头部、JS要放尾部再配合defer和async?为什么样式的层级不宜过深?这些问题的答案都能在上面这条链路里找到依据。

3.3 HTTP缓存与前端网络优化

虽然2018年的卷子对HTTP缓存考得没有现在这么重,但基础概念还是要过一遍。强制缓存和协商缓存这对概念,几乎每家公司的笔试都会碰。

强缓存相关响应头是Cache-Control,常用值有max-ageno-cacheno-store,优先级高于老牌的Expires。协商缓存相关响应头是Last-Modified/If-Modified-SinceETag/If-None-Match

笔试常考的问答是:强缓存命中时返回什么状态码?答案是200,并且会显示from disk cachefrom memory cache。协商缓存命中时返回304。我见过很多人把这两个概念搅在一起,其实只要抓住一点:强缓存不发请求,直接本地取;协商缓存是发一个带条件的请求,由服务器决定使用缓存还是返回新资源。

理解了这套机制,再看前端工程化里的文件名指纹,逻辑就通了。webpack打包时会在文件名里带上hash,本质上就是为了让静态资源更新后能自然跳过强缓存命中,重新走一遍协商缓存或直接拉取新文件。这是笔试里常见的综合题,很多同学看过webpack配置但从来没想过它为什么那样设计。

4. HTML/CSS布局与样式问题

4.1 等高布局

CSS部分在笔试里一般不会考太偏的题,重点集中在盒模型、浮动、定位、Flex、Grid,以及几类经典布局的手写题上。其中,手写两栏布局和三栏布局的概率出奇的高。

三栏布局中比较推荐的做法是flex,代码量少,边界情况也少。

.container { display: flex; } .left { width: 200px; } .middle { flex: 1; } .right { width: 200px; }

如果要求中间栏自适应,左右固定,这个方案就够了。如果想要中间栏优先加载,传统做法是圣杯布局或双飞翼布局,它们都是基于浮动和负margin实现的。很多同学会把这两个布局的结构原理弄混,这里我提供一个记忆方法:圣杯布局在container上加padding,用相对定位移动左右两栏;双飞翼布局在中间栏内部再加一层,用margin留出左右空间。前者是“container让位”,后者是“中间栏内部让位”。

除了布局,居中问题也是高频中的高频。水平垂直居中,我的首选方案是flex或grid,极端兼容场景下再考虑transform或绝对定位。这里有一道容易翻车的问题:“不定宽高的元素,怎么实现水平垂直居中?”只要答出以下这三种方式之一并说明原理即可:

  1. flex:父容器display:flex; align-items:center; justify-content:center
  2. grid:父容器display:grid; place-items:center
  3. 绝对定位:左右设为0,margin设为auto,配合top/bottom设为0

值得注意的是,绝对定位加margin auto这种方式,需要元素有明确的宽高,否则内容会塌缩。不设宽高的时候,用transform方式更稳妥。

4.2 盒模型与BFC

笔试题里盒模型基本是送分题,但送分也总有人拿不到。关键在于Doctype。在标准模式下,box-sizing: content-box时width只包含内容宽度,padding和border按标准叠加出去;设为border-box时width包含内容padding和控制border。怪异模式是旧IE时代的产物,现在基本不用管,但面试官偶尔会拿它来测试候选人是否真的理解盒模型的历史背景。

BFC看起来有点理论化,但结合实际问题就很好记。BFC全称是Block Formatting Context,块级格式化上下文。在一个BFC内部,子元素的margin不会和外部元素合并,float的元素也被包含在BFC的范围内。实际操作上,触发BFC的方式包括:

  • overflow不为visible
  • float不为none
  • display为inline-block、table-cell、flex等
  • position为absolute或fixed

最常见的应用场景就是清除浮动。父容器内的子元素都float了,父容器的高度塌了,给父容器加overflow: hidden,它就触发BFC,子元素的浮动被包含进来,高度恢复正常。明白了原理,再背十个清除浮动的写法都不如看一眼本质。

4.3 手写轮播图时的样式细节

2018年A卷的编程题里有一道定时轮播的小题,要求实现图片自动切换,并且配了左右按钮手动切换。很多人把JavaScript逻辑写了半天,却忽略了样式细节,结果一排图片水平排列时就出问题。

我当时给同事的建议是:先用统一容器包住所有轮播项,容器宽度设成图片数量的百分比倍,每张图片宽度设为容器的几分之一,再利用父容器的overflow: hidden裁剪可视区域。切换时用transform的translateX移动容器,而不是改left或margin,这样可以避开reflow,性能更好。

.carousel { overflow: hidden; } .carousel-track { display: flex; transition: transform 0.3s ease; } .carousel-slide { flex: 0 0 100%; height: 300px; background-size: cover; }

对应js逻辑大概是这样:

let index = 0; const track = document.querySelector(".carousel-track"); const total = 3; function show(i) { if (i < 0) { i = total - 1; } else if (i >= total) { i = 0; } index = i; track.style.transform = `translateX(-${index * 100}%)`; } setInterval(() => { show(index + 1); }, 3000);

这套代码虽然简单,但体现了几个关键点:用transform做动画、用flex避免float布局的麻烦、边界处理时对负索引和越界索引做取模逻辑。这些都是前端开发中真正常用的小能力。

5. 算法与手写编程题

5.1 常考手写题清单

笔试里的编程题一般不会上太难的算法,重点落在数组、字符串、排序、去重、防抖节流一类的题目上。我见过的高频手写题,按出现概率排序,大致是这十个:

  1. 数组去重
  2. 数组扁平化/拍平
  3. 防抖与节流
  4. 深拷贝
  5. 手写Promise.all
  6. 字符串反转或回文判断
  7. 排序算法基础(冒泡、快排)
  8. 查找数组最大/最小元素
  9. 手写bind/call/apply
  10. 父子树的遍历或查找

这里我把数组去重展开讲一下,因为它能考察的知识点非常多,从Set到Map再到reduce,几种方式的复杂度都不一样。最简洁的写法是[...new Set(arr)],但如果你只会这一种,面试官追一句“如果要去重对象数组呢”就容易卡住。

更好的方式是掌握两层遍历加splice的经典写法,再在此基础上给出利用对象键名去重或Map去重的方案。数组扁平化也一样,递归版本要会写,还要知道Array.prototype.flat的用法和它的默认展开层数。

function flatten(arr) { return arr.reduce((prev, cur) => { return prev.concat(Array.isArray(cur) ? flatten(cur) : cur); }, []); }

5.2 事件循环题目里的输出顺序

除了常规算法,A卷还有一种编程题,是给一张页面,要求用setTimeout实现某个间隔执行的效果。这类题不完全考算法,而是考异步编程能力。比如要求每一秒打印递增的数字,能实现的方式很多。

最常见的一种错误解法是:

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

这又回到了闭包题的坑。一秒后打印出来的不是0到4,而是五个5。正确写法是用let:

for (let i = 0; i < 5; i++) { setTimeout(() => { console.log(i); }, 1000 * i); }

或者用IIFE包一层,再或者用bind传参。我在笔试题的阅卷记录里,这道题的自定义错误率极高。如果你做出来了,但又想真正弄懂,建议再想想:如果要求每间隔1秒打印一个数字,而不是1秒后全部一起打印,那么延迟时间应该怎么写。这才是setTimeout题型的核心。

5.3 手写防抖与节流的边界条件

防抖节流看起来简单,但真正手写的时候,很多人的代码缺少边界处理。以防抖为例,核心逻辑是:事件触发后,设置一个定时器,在等待时间内再次触发,就清掉旧定时器并重新计时,只有最后一次触发生效。

function debounce(fn, delay) { let timer = null; return function (...args) { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { fn.apply(this, args); }, delay); }; }

关键问题是thisargs。很多初学者写出来的版本在setTimeout里没有保留this指向,结果控制台报错或者this指向了window。正确的做法是先用fn.apply(this, args)把当前上下文传进去。节流则相反,它的核心在于“限频”,一段时间内最多执行一次,常见实现有时间戳版本和定时器版本。

如果笔试时间允许,我建议写完防抖之后,顺便把“是否可以立即执行一次”的版本也写好。比如搜索框场景一般希望防抖延迟请求,但按钮点击场景可能更希望第一次点击立即触发,后续点击被节流。两个版本都练一遍,面试时被追问也不慌。

6. 框架与工程化启蒙题

6.1 从笔试看大厂对框架的要求

2018年的校招笔试题几乎不直接考察Vue或React的API调用,因为笔试没法验证你真实使用框架时的能力。但会在综合题里隐含地问你:数据驱动的本质是什么、组件通信的本质是什么、为什么虚拟DOM能提升性能。

我见过A卷流传版本里有这样一道半开放题:“Vue中data为什么必须是一个函数”。这题放到今天依然很经典。原因是组件可能被复用,如果data直接写成一个对象,那多个实例会共享同一个数据引用,一个实例改了数据,所有实例都会变。写成函数,每次创建组件实例时都能返回一份新的数据对象。

如果这套题里不出现类似问题,面试环节也大概率会问到。所以准备笔试时,不要只抱着算法刷,框架的底层机制起码要能在“一句话讲清楚”的粒度上回答出来。比如Vue2的响应式原理是Object.defineProperty,Vue3换成了Proxy,React的setState是异步的,状态更新后组件会重新渲染,渲染后可以拿到新的props和state。

6.2 模块化与打包基本盘

工程化相关题在这个阶段不会出得太偏,但会考CommonJS和ES Module的区别。这是很多人容易模糊的点。笔试里常见的坑是:require是同步加载,import是静态导入,可以被静态分析,所以在编译期就能做依赖分析,支持tree-shaking。而CommonJS是运行时加载,只能在运行时确定依赖关系。

类似地,webpack的构建流程要能说出大致链路:入口文件解析,模块依赖收集,loader转换,插件处理,代码分割,最终输出bundle。如果时间紧张,理解到“配置里的entry对应入口、output对应输出、loader负责文件转换、plugin负责整体扩展”这个粒度就够了,但loader和plugin的区别要能说清。loader是处理文件的转换器,plugin是作用于整个构建过程的功能扩展。

6.3 移动端适配方案

A卷末尾偶尔会出一道移动端点屏幕宽度的题目。核心适配方案有三类,meta viewport配合rem,flexible方案,以及viewport单位方案。

rem方案的核心是动态设置根节点的font-size,然后所有尺寸都用rem来写。假设设计稿是750px宽,那么把根字体大小设置为设备宽度除以7.5,在375px宽的设备上就是50px。这样设计稿里的100px换算成rem就是2rem。简单理解,就是屏幕变宽时,所有元素等比例放大。

viewport方案更直接,直接用vw和vh做单位。1vw等于屏幕宽度的百分之一。设计稿750px,那么1px约等于0.1333vw。缺点是需要处理字号太大的场景,通常配合min/max做边界限制。这道题不仅考CSS,还考你对“响应式”和“自适应”这两个概念的区分,笔试里答偏的人不少。

7. 实践复盘:把真题变成自己的知识地图

7.1 考后怎么复盘才不白做

做完一套题,复盘比刷题更重要。我给自己定过一个标准流程:先把错题按知识点归类,找出重复出错的知识点,比如“this指向”“Promise时序”“CSS居中”这些高频点,再去刻意练习同一知识点的不同变形题。

复盘时最好写“错题原因”,而不是“正确答案”。写下是因为概念不熟,还是因为看漏了条件,还是因为边界没处理。这三类原因对应的复习策略完全不同。概念不熟就回头看文档和书籍,看漏条件就训练读题习惯,边界没处理就去多写不同输入值的测试用例。

另外一个建议是:把每道题的代码在真实环境里跑一遍,不要只用眼睛看输出。事件循环和this指向这类题目,直接在浏览器控制台里实际执行,所见即所得,比背文章有用得多。跑完之后,再试着改改条件,比如把普通函数换成箭头函数,把setTimeout的延迟时间改成0,看看输出会不会变。这样一题变多题,一套卷子能吃透的知识量是原来的三倍。

7.2 笔试答题时间分配的经验

线上线下笔试一般给两个小时左右。以A卷的题量和结构来说,我的建议是前30分钟把比较简单的基础题和输出题一次性写完,中间40分钟处理重头的手写题,剩下30分钟做算法和综合题,最后20分钟检查。

检查的重点不是看答案对不对,而是看有没有漏条件。常见的情况是:排序题没有考虑空数组,数组去重题没有考虑NaN,防抖题没有传this。这些边界条件不会在示例输入里暴露,但判题系统有时候就会卡在这些奇怪的地方。

7.3 由一道题延伸到面试问答

笔试结束后,通常会有一个拿着你的笔试卷子追问的环节。这时候,如果你只写了一个正确答案,而没有想过背后的原理,会显得准备不足。比如你写出了数组去重的Set写法,面试官大概率会问:“Set去重和Map去重有什么差别?”“能不能不用Set写?”如果只背答案,这两个问题会当场卡住。

我的习惯是,每做一道笔试题,都顺手准备两个引申问题。比如完成深拷贝后,能回答:“如何处理循环引用?Set和WeakMap有什么差别?为什么用WeakMap?”完成防抖后,能回答:“防抖和节流的本质区别是什么?哪些场景用防抖,哪些场景用节流?”这样,笔试不只是笔试,它变成了一轮模拟面试。

我见过不少同学把题库刷了三遍,但成绩始终徘徊在及格线。问题往往不在于题量,而在于“只做题不复盘”。把每一道题当成一个知识锚点,连成一张网,比刷一百道题再忘掉九十九道要有效得多。

最后再分享一个小习惯:每次模拟笔试之后,我会把当时的错误代码单独存一个文件,持续积累。过两周再回来重新跑一遍,看自己能不能自行修复。用这种方式来检验知识掌握程度,比反复翻看正确答案更能暴露问题。这份欢聚时代2018校招A卷,我的建议不是做完就扔,而是留档,作为阶段性自测的标尺,每隔一段时间回头重做一次,你会看到自己的进步曲线。

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

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

立即咨询