news 2026/8/29 23:39:50

小满秋招前端笔试复盘:考点、坑点与备考攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
小满秋招前端笔试复盘:考点、坑点与备考攻略

2023年小满秋招Web前端岗第二批笔试,我是在一个周六上午参加的。当时人在家里,用的是在线笔试平台,摄像头开着,整个考试过程不能切屏,时间一到自动交卷。说实话,我一开始是把它当成一场普通笔试来准备的,刷了不少面经,结果拿到卷子才发现,光靠背题根本扛不住——这套卷子更看重的,是你有没有真正理解前端的底层逻辑。

考完之后我把整套题从头到尾回忆了一遍,发现大部分题目都不超纲,但陷阱埋得很深。这篇文章就把我记得的考点、解题思路、踩过的坑,以及备考方向完整拆一遍。无论你是准备下一批笔试,还是打算把基础补扎实,都值得花几分钟看完。

1. 整体考情复盘:这套卷子到底在考什么

1.1 题型分布与时间分配

整套卷子大致分成四大块,总时长120分钟,题量不算大,但信息密度很高。我记得试卷结构大概是这样的:

题型分值占比覆盖内容
选择题(单选+多选)约30%HTML、CSS、JavaScript基础、浏览器原理、网络协议
填空题约10%作用域、原型链、事件循环的输出结果
简答题约20%CSS布局方案、跨域方案、性能优化思路
编程题约40%手写函数、算法题、逻辑题

我的建议是开考后先快速扫一遍全卷,别急着做题。选择题和填空题会就会、不会就不会,卡住没意义;编程题分值最高,但需要完整思路,适合放在大脑最清醒的前半段。我个人的时间分配是:选择和填空40分钟,简答20分钟,编程题40分钟,最后20分钟检查。这套节奏比较保守,能保证每道题都有时间碰一遍。

1.2 难度分层:送分题、基础题、拉分题

从实际体验来看,这套卷子明显分为三个层次:

  • 送分题:标签语义、CSS属性作用、HTTP状态码含义、Promise基础用法,这类题基本是看一眼就该出答案的。
  • 基础题:闭包输出、事件循环顺序、三栏布局实现,这类题要求你真正写过代码,不能只看过概念。
  • 拉分题:手写深拷贝、手写Promise.all、实现带并发限制的调度器,这些题没有标准答案,完全靠平时的工程积累和代码功底。

第二批的拉分题比第一批更灵活。我当时看到有一道简答题甚至允许你写出多种方案,但要说明各自的优缺点。这种题根本没有"背"的可能性,如果平时只是照着教程敲代码、没有自己思考过方案的取舍,到这里就会明显露怯。

2. 基础考点盘点:CSS、HTML与网络基础

2.1 三栏布局:圣杯、Flex、Grid怎么选

选择题和简答题都出现了三栏布局,题干是"实现左右各200px、中间自适应的三栏布局,要求中间部分优先加载"。这题看着经典,但很多人会忽略"中间部分优先加载"这个条件。如果你直接把左侧div写在中间div前面,那中间部分就不是第一个加载的DOM节点,这题至少扣一半分。

我当时写的是flex方案,代码是这样:

<div class="container"> <div class="main">中间内容</div> <div class="left">左侧</div> <div class="right">右侧</div> </div>
.container { display: flex; } .main { flex: 1; order: 1; } .left { width: 200px; order: 0; } .right { width: 200px; order: 2; }

这里的关键是,HTML结构里把中间内容放在最前面,但通过order属性调整视觉顺序。这样中间部分确实是先加载的,同时页面显示上还是左、中、右。可能有人会觉得order是万能的,但实际上order只改变视觉顺序,不改变DOM的解析顺序,这一点在面试官追问时一定要能说出来。

我又补充了grid方案:

.container { display: grid; grid-template-columns: 200px 1fr 200px; } .main { grid-column: 2; } .left { grid-column: 1; } .right { grid-column: 3; }

grid写法更简洁,但在老浏览器兼容性上不如flex。如果题目没有说明对浏览器兼容有硬性要求,我一般会优先推荐flex,因为生产环境中大部分中后台项目都要求兼容到Chrome 60以上,grid在当时还不够保险。这道题真正考查的不是你会不会写fiex布局,而是你脑子里有没有"多方案横向对比"的意识。

2.2 选择器优先级和BFC

选择题里有一道是"以下选择器优先级从高到低排列",选项类似#app .name.wrap .namediv p.namediv p。如果你只背口诀"important > 内联 > id > class > 标签",这道题很容易掉坑。

正确理解是选择器优先级按三元组逐位比较:id选择器个数、class/属性/伪类选择器个数、标签/伪元素选择器个数,从高到低逐位比较。#app .name对应(1,1,0).wrap .name对应(0,2,0),比较时先看id数,所以(1,1,0)更高。后面接着考了!important和行内样式的比较,这个在笔试里就是送分题,但前提是你别把!importantdisplay:none的优先级混淆。

BFC简答题是这套卷子里我印象比较深的一道,题干是"什么是BFC?如何触发?解决了什么问题?"。BFC全称Block Formatting Context,块级格式化上下文,我习惯把它理解成一个独立的渲染区域,区域内外的元素不互相干扰。触发条件我写了overflow不为visible、float不为none、position为absolute或fixed、display为inline-block,以及flex/grid容器的子项。解决场景经常考的是三个:防止margin塌陷、清除浮动、避免元素被浮动元素覆盖。如果能写出"两栏布局中让右侧盒子形成BFC,可以避免被左侧浮动元素遮挡"这个实际案例,比只罗列定义高一个档次。

2.3 跨域方案与缓存机制简答

简答题里有跨域和缓存,这两个都是高频考点。跨域那题问的是"如何解决跨域",我写了三种方案:CORS、JSONP、反向代理。重点写了CORS,因为它是服务端配置的,需要在响应头里带上Access-Control-Allow-Origin,并且要区分简单请求和预检请求。JSONP只能支持GET请求,而且依赖动态创建script标签,适合老项目,但工程上现在已经很少用了。

缓存那题问的是"强缓存和协商缓存的区别"。强缓存是浏览器直接用本地缓存,不发请求,对应的响应头是Cache-Controlmax-ageExpires;协商缓存是浏览器带着条件请求头去问服务器,服务器返回304就走缓存,对应的响应头是Last-Modified/If-Modified-SinceETag/If-None-Match。我当时补了一句"ETag的优先级高于Last-Modified",因为Last-Modified只能精确到秒,同一秒内文件变化判断不到,这种细节很容易成为加分项。

3. JavaScript运行机制:闭包、原型链、事件循环

3.1 闭包输出题:var、let与"5个5"

填空题考了一道闭包经典题,代码我给个近似的版本:

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

答案是连续输出5个5。很多人背过这个结论,但不理解为什么。真正原因是var声明的i是函数作用域,循环结束后i已经是5,所有setTimeout回调在1秒后执行时,访问到的都是同一个i,也就是5。如果你只背了答案,题目一变就废了。

但如果把var改成let

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

输出就是0、1、2、3、4。因为let在每次迭代时会创建新的词法环境,每个回调捕获的是当次迭代的i副本。

后面还加了一问:"不使用let,如何用var加闭包实现正确输出"。我写的是IIFE版本:

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

这道题真正想测的是你对作用域和变量生命周期的基础是否扎实。凡是靠背答案的同学,一旦题目改成循环次数是动态的,或者两个for循环嵌套,就很容易暴露。

3.2 原型链与手写new

选择题里有一段代码:

function Foo() {} Foo.prototype.a = 1 const foo = new Foo() console.log(foo.a) console.log(foo.__proto__.a) console.log(Foo.prototype.constructor === Foo)

答案分别是1、1、true。理解只有一句话:实例通过__proto__访问到构造函数的prototypeprototype默认有一个constructor属性指回构造函数本身。

但真正拉分的是后面让你手写new。我写的是:

function myNew(Constructor, ...args) { const obj = Object.create(Constructor.prototype) const result = Constructor.apply(obj, args) return result !== null && (typeof result === 'object' || typeof result === 'function') ? result : obj }

两个关键点必须体现出来:一是用Object.create(Constructor.prototype)让新对象的原型链正确;二是如果构造函数显式返回了一个对象,new操作符会返回那个对象,而不是我们自己创建的obj。如果构造函数返回的是原始类型,则忽略并返回obj。这个边界情况很多人写不出来,实际项目里也很少遇到,但笔试就是靠这种区分度筛人。

3.3 宏任务与微任务的执行顺序

简答题给了一段混合Promise和setTimeout的代码,要求写出输出顺序。我回忆一下大致类似这样:

console.log('start') setTimeout(() => { console.log('timeout1') Promise.resolve().then(() => { console.log('promise in timeout') }) }, 0) new Promise((resolve) => { console.log('promise1') resolve() }).then(() => { console.log('then1') }) console.log('end')

我在考场上的推理过程是:先执行所有同步代码,所以startpromise1end依次输出;然后清空微任务队列,then1输出;接着执行宏任务setTimeout回调,输出timeout1,回调内部又注册了一个微任务,所以在输出timeout1之后还要补一个promise in timeout。最终顺序是startpromise1endthen1timeout1promise in timeout

这里特别容易错的一点是,很多人把Promise构造函数里的代码当成异步。它其实是同步执行的,只有then回调才是微任务。这个理解如果错了,后面所有输出顺序的题都做不对。

还有个扩展方向是Node环境下的差异。不同版本Node的事件循环机制不一样,Node 11之后微任务执行时机和浏览器趋于一致,但老版本可能存在差异。笔试里一般不用展开太多,但写一句"以上结论基于浏览器环境"会更严谨,展示出你考虑过运行环境。

4. 手写代码题:深拷贝、防抖节流、Promise.all

4.1 深拷贝:别只写JSON方法

编程题第一道是"实现一个深拷贝函数,要求能够拷贝数组、普通对象以及处理循环引用"。拿到这题,我看到很多人第一反应是写JSON.parse(JSON.stringify(obj)),然后就被扣分了。原因有两个:一是JSON方法会忽略值为undefined的字段、函数、Symbol,二是无法处理循环引用,对象互相引用时会直接抛错。题目里明确写了"处理循环引用",所以JSON方案从一开始就出局了。

我写的是递归版本,用WeakMap记录已经拷贝过的对象:

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

这个版本能覆盖绝大多数场景。解释一下为什么用WeakMap:WeakMap的键是弱引用,意味着当原对象不再被引用时,它可以被垃圾回收;如果用普通Map,拷贝完成后整个Map一直握着原对象的引用,会导致内存泄露。能写出WeakMap并解释清楚这一点,面试官基本能判断你写过真实项目,而不是背的。

还有一个容易忽略的点是for...in会遍历到原型链上可枚举的属性,所以必须加hasOwnProperty判断。否则拷贝出来的对象会带上原型上的扩展属性,这在实际工程中会引发bug。

4.2 防抖与节流:this是最大坑

编程题第二道要求"实现一个防抖函数和一个节流函数,并说明两者的区别及使用场景"。我的防抖实现:

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

这里最大的坑是this指向。如果setTimeout回调里用普通function,那么执行时this会指向window(严格模式下是undefined),而我们需要的是返回函数被调用时的this。所以回调必须用箭头函数,让this从外层词法作用域继承,再通过fn.apply(this, args)把正确的this传给原函数。这个细节我身边不少同事都踩过,笔试时写不上来非常可惜。

节流的实现:

function throttle(fn, interval = 300) { let last = 0 return function(...args) { const now = Date.now() if (now - last >= interval) { last = now fn.apply(this, args) } } }

场景差异我说得很直白:防抖适合输入框搜索,用户连续输入时只发最后一次请求;节流适合滚动事件监听,保证固定时间内至少执行一次,避免计算太频繁。如果能补充"防抖会把一个高频操作合并成一次,节流则是限制高频操作的执行频率",这道题就答全了。

4.3 手写Promise.all的核心要点

最后一道编程题是实现Promise.myAll。这个题我写过太多次了,但考试时还是提醒自己不要漏边界情况。我的实现:

Promise.myAll = function(promises) { return new Promise((resolve, reject) => { const result = [] let count = 0 if (promises.length === 0) { resolve(result) return } promises.forEach((p, index) => { Promise.resolve(p).then((value) => { result[index] = value count++ if (count === promises.length) { resolve(result) } }).catch(reject) }) }) }

必须强调三个点:

  1. Promise.resolve(p)包裹一遍,这样就算传入的是普通值,也能正常挂在then链上。
  2. 存储结果时按索引存,不按完成顺序存。比如第一个Promise最后才完成,它的值也必须放在result[0],这样才能保证输出顺序和入参顺序一致。
  3. 用计数器count来判断是否全部完成,而不能用result.length === promises.length。因为数组是稀疏赋值,先给result[2]赋值后,result.length就变成3了,会提前触发resolve,逻辑就错了。

第三点是我觉得全网很多教程都没仔细讲的地方,但笔试里一旦写错,代码跑出来的结果会非常诡异。

5. 算法与逻辑题:思路比背模板更重要

5.1 数组扁平化与去重排序

逻辑题里有一道将任意深度数组扁平化并去重排序的题,类似输入[1, [2, [3, 3, [4, 5]]], 6],要求输出[1, 2, 3, 4, 5, 6]

扁平化我写了reduce递归:

const flat = (arr) => arr.reduce( (acc, cur) => acc.concat(Array.isArray(cur) ? flat(cur) : cur), [] )

去重排序用Set和sort,但这里有个大坑:sort默认按字符串的字典序排,[1, 10, 2]直接sort()会变成[1, 10, 2],因为"10"在字典序里排在"2"前面。所以必须写成sort((a, b) => a - b)。这个坑在笔试里出现频率特别高,我写的时候特意留了个心眼。

5.2 二叉树的还原遍历

还有一道二叉树相关的题,给出前序遍历和中序遍历,要求还原二叉树并输出后序遍历。我记得前序大概是[3, 9, 20, 15, 7],中序是[9, 3, 15, 20, 7]

思路是固定的:前序第一个元素一定是根节点,去中序里找到根节点的位置,左边是左子树,右边是右子树,递归处理。为了降低复杂度,可以先用Map把中序数组每个值的索引存起来,这样每次查位置就是O(1),而不是O(n)。

这类题的唯一建议是多练。递归的边界条件很容易出错,光看解析全会,一写就废。LeetCode上"从前序与中序遍历序列构造二叉树"这道题,吃透它比临时背十道模板都管用。如果你能把边界条件用注释标出来,比如"左子树为空"和"右子树为空"各自返回什么,面试官会觉得你很专业。

6. 失分点复盘与避坑经验

6.1 时间分配与检查策略

这次笔试我最大的失误是在填空题上花太久。当时遇到一道关于Set和Map的题,我有点拿不准,愣是想了10分钟,结果后面的编程题时间被砍掉了一大截。考完复盘,正确的策略应该是:判断题超过2分钟还没有明确思路,先标记跳过,把后面分值高的题写完再来抠。一道填空撑死2分,一道编程题15分起步,为了2分牺牲15分,这个账怎么算都不划算。

检查阶段也很重要。我通常先看编程题有没有明显语法错误、能不能跑通示例,再看填空和选择题有没有写了但选项没填对的情况。有一次我发现自己把B选成A,纯粹是因为鼠标点快了,这种低级失误完全可以通过检查避免。

6.2 易错知识点速查表

我把这套卷子反复回忆后,整理出一个易错知识点速查表,备考时过一遍很管用:

考点常见错误正确理解
选择器优先级只记id/class/标签按(id, class, 标签)三元组逐位比较
var与let以为只是作用域不同本质是每次迭代是否创建新的词法环境
Promise执行时机以为整个都是异步构造器内代码同步执行,then才进微任务
深拷贝直接用JSONJSON丢失undefined/函数/Symbol,不能处理循环引用
防抖this回调里用普通function必须用箭头函数继承this或显式绑定this
Promise.all用result.length判断完成稀疏数组会让length提前达标,必须用计数器
sort排序默认字典序不传比较函数时按字符串排序,数字需传(a,b)=>a-b
BFC触发方式只记得overflow和floatposition/display/flex容器子项等也常见
order属性以为改变DOM解析顺序只改变视觉顺序,DOM加载顺序仍由HTML决定
二叉树还原记不住边界前序定根,中序分割左右,递归构建

这张表我建议准备笔试的朋友打印出来,考前一天过一遍,能避掉很多非技术性失分。

6.3 考试环境与答题细节

在线笔试的纪律性一定要重视。我当时考试平台要求打开摄像头,浏览器有切屏检测,切屏超过一定次数会被系统判违规。所以开考前要保证网络稳定、关闭无关浏览器标签页、手机静音放到一边。输入法务必切成英文半角,否则代码里的标点很容易出问题,一旦提交运行全是语法错误。

编程题的答题区一般有固定模板,别上来就删模板,先看题目要求你导出什么、函数参数名是什么,尽量在模板基础上改。写完代码一定要点一次运行,确认能通过示例用例再交卷。我不止一次发现,代码本地没问题,粘到答题区就出现缩进混乱或引号被改成全角的情况,提交前不运行一遍很容易挂。

7. 考后复盘与备战方向

7.1 从笔试反推学习优先级

经历这次笔试,我最大的感受是:前端笔试早就不是"你会用API"就能过关的了,现在出的题几乎都是围绕"你理解它为什么这样设计吗""你写过边界情况吗"来展开的。如果你还在准备后续批次或补录面试,建议把精力按这个优先级放:

JavaScript核心机制(闭包、原型链、事件循环、异步)大于手写代码能力(深拷贝、防抖节流、Promise系列)大于CSS布局与浏览器渲染原理,最后才是框架源码。框架全家桶会问,但笔试阶段真的更看重基础功底。Vue或React的响应式原理、diff算法可以过一遍,但不用背源码,能讲清核心思想就够了。

7.2 复习资料推荐

说实话我不太喜欢列一堆书单,最后看不完反而焦虑。我实际用下来最有效的就三个:MDN的JavaScript指南,用来把基础概念查清楚;LeetCode上与二叉树构造、数组扁平化相关的题目,用来练手写能力;再就是自己之前做过的项目代码,把里面用到的防抖、节流、深拷贝、跨域配置拿出来重新理解一遍,比背任何面经都有用。

7.3 心态与节奏

最后说句实在话,笔试没过不代表你水平不行,有时候就是差那么一点熟练度和临场经验。我前面因为一道填空题浪费时间,导致编程题差点没写完,那种被动感太难受了。后面再考其他公司,我学乖了:先扫全部题目,按分值排序,把编程题放在优先位置,整套做下来反而更从容。这是我的真实体会,希望你下次坐在笔试电脑前的时候,能避开我踩过的这些坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 23:37:45

贝壳前端秋招笔试题复盘:JS基础考点与编程题实战解析

贝壳这套秋招笔试题&#xff0c;我是在第二批做的。整体感受是&#xff1a;题量不小&#xff0c;覆盖面偏基础但坑点多&#xff0c;选择题考得细&#xff0c;编程题不考偏题怪题&#xff0c;但非常考验编码熟练度和边界意识。如果你正准备贝壳或其他大厂的前端秋招&#xff0c;…

作者头像 李华
网站建设 2026/8/29 23:36:56

不止写代码:LLM非编码场景实战指南

打开 Hacker News 的时候&#xff0c;经常能看到“Ask HN”系列帖子里有人讨论 LLM 的各种用法。其中一条提问让我印象很深&#xff1a;“Do you use LLMs for non-coding related work?”也就是——除了写代码&#xff0c;你真的用大语言模型处理过日常工作吗&#xff1f;这个…

作者头像 李华
网站建设 2026/8/29 23:36:13

百度2020校招前端笔试题全解析:从JS基础到框架原理

1. 试卷整体观察&#xff1a;一场典型的“大厂海选”式考察 拿到这份百度2020校招Web前端工程师笔试卷&#xff08;第一批&#xff09;&#xff0c;第一反应是熟悉。如果你经历过那个年代的校招季&#xff0c;应该对这种卷子有印象——它不像社招那样深挖某个方向的细节&#x…

作者头像 李华
网站建设 2026/8/29 23:34:31

STM32H563/573 Debug Port调试接入:SWD与TrustZone安全机制详解

做 STM32H563/573 调试端口接入&#xff0c;或者叫“Access via Debug Port”&#xff0c;确实是一块容易让人踩坑的工作。别看 SWD 就是四根线的事情&#xff0c;到了带 TrustZone 的 Cortex-M33 平台上&#xff0c;Debug Port 的访问牵扯到芯片安全状态、调试认证、选项字节、…

作者头像 李华
网站建设 2026/8/29 23:33:32

MATLAB J-B检验实战:洪水受灾面积正态性分析与数据建模策略

1. 项目缘起&#xff1a;为什么需要检验洪水受灾面积的正态性&#xff1f;在水利工程、灾害评估和保险精算领域&#xff0c;洪水受灾面积是一个核心的评估指标。我们拿到一批历史洪水事件的受灾面积数据&#xff0c;第一反应往往是计算它的平均值、标准差&#xff0c;或者用这些…

作者头像 李华
网站建设 2026/8/29 23:27:50

金山办公2020校招前端笔试题解析:从JS基础到框架底层

每年七八月份都是校招笔试高峰&#xff0c;前端开发的岗位尤其卷。群里经常有学弟学妹拿着各类公司真题来问我&#xff0c;其中金山办公这套2020校招前端开发笔试题&#xff08;一&#xff09;&#xff0c;被问到的频率意外地高。按理说年份也不算近&#xff0c;但这套题的考察…

作者头像 李华