ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

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

2026/8/29 23:40:21 拓冰建站 浏览量
小满秋招前端笔试复盘:考点、坑点与备考攻略 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 classcontainer div classmain中间内容/div div classleft左侧/div div classright右侧/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 .name、div p.name、div p。如果你只背口诀important 内联 id class 标签这道题很容易掉坑。正确理解是选择器优先级按三元组逐位比较id选择器个数、class/属性/伪类选择器个数、标签/伪元素选择器个数从高到低逐位比较。#app .name对应(1,1,0).wrap .name对应(0,2,0)比较时先看id数所以(1,1,0)更高。后面接着考了!important和行内样式的比较这个在笔试里就是送分题但前提是你别把!important和display: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-Control的max-age和Expires协商缓存是浏览器带着条件请求头去问服务器服务器返回304就走缓存对应的响应头是Last-Modified/If-Modified-Since和ETag/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改成letfor (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)我在考场上的推理过程是先执行所有同步代码所以start、promise1、end依次输出然后清空微任务队列then1输出接着执行宏任务setTimeout回调输出timeout1回调内部又注册了一个微任务所以在输出timeout1之后还要补一个promise in timeout。最终顺序是start、promise1、end、then1、timeout1、promise in timeout。这里特别容易错的一点是很多人把Promise构造函数里的代码当成异步。它其实是同步执行的只有then回调才是微任务。这个理解如果错了后面所有输出顺序的题都做不对。还有个扩展方向是Node环境下的差异。不同版本Node的事件循环机制不一样Node 11之后微任务执行时机和浏览器趋于一致但老版本可能存在差异。笔试里一般不用展开太多但写一句以上结论基于浏览器环境会更严谨展示出你考虑过运行环境。4. 手写代码题深拷贝、防抖节流、Promise.all4.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 }这个版本能覆盖绝大多数场景。解释一下为什么用WeakMapWeakMap的键是弱引用意味着当原对象不再被引用时它可以被垃圾回收如果用普通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) }) }) }必须强调三个点用Promise.resolve(p)包裹一遍这样就算传入的是普通值也能正常挂在then链上。存储结果时按索引存不按完成顺序存。比如第一个Promise最后才完成它的值也必须放在result[0]这样才能保证输出顺序和入参顺序一致。用计数器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或显式绑定thisPromise.all用result.length判断完成稀疏数组会让length提前达标必须用计数器sort排序默认字典序不传比较函数时按字符串排序数字需传(a,b)a-bBFC触发方式只记得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 心态与节奏最后说句实在话笔试没过不代表你水平不行有时候就是差那么一点熟练度和临场经验。我前面因为一道填空题浪费时间导致编程题差点没写完那种被动感太难受了。后面再考其他公司我学乖了先扫全部题目按分值排序把编程题放在优先位置整套做下来反而更从容。这是我的真实体会希望你下次坐在笔试电脑前的时候能避开我踩过的这些坑。