ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

58同城前端秋招笔试题复盘:核心考点与面试避坑指南

58同城前端秋招笔试题复盘:核心考点与面试避坑指南 2017年秋天我在北京参加了58同城的前端秋招笔试。那会儿“前端工程化”刚刚成为行业热词Vue 2还没彻底统治江湖React 16刚发布不久很多团队还在用jQuery写业务。三年后回头看这份笔试卷里的不少题目到现在依然是面试高频题甚至有题目直接成了我后来带新人时的必考内容。这篇文章就围绕这份笔试卷本身从题型设计、核心考点、编程题拆解到避坑经验完整复盘一遍。顺便结合这些年的面试经验聊聊哪些知识点是值得花时间死磕的哪些是背再多八股文也没用的。不管你是准备校招还是打算跳槽这份复盘应该都能帮到你。1. 试卷整体设计与考察思路拆解1.1 题型分布与考察维度58同城这套卷子的结构大概分三块选择题、简答题、编程题。我记得选择题大概是20道左右覆盖HTML、CSS、JavaScript基础和少量网络知识简答题3到4道重点是闭包、原型链、事件循环这类概念编程题一般3道难度递进最后一道通常有点算法味道。从考察维度上看这套卷子的思路非常清晰先筛掉基础不牢的再筛掉只会用不会写的最后留下能直接上手干活儿的。选择题考察的是“你知不知道”比如标签语义化、盒模型、HTTP状态码这些简答题考察的是“你能不能讲明白”比如让你解释什么是事件冒泡和事件委托编程题考察的是“你能不能写出来”比如手写防抖节流、实现深拷贝这类前端天天要用的工具函数。这么设计其实很符合58同城当时的用人需求。分类信息网站页面多、交互轻、迭代快前端要处理大量列表渲染、数据筛选、搜索请求所以基础扎实、能写原生JS、懂性能优化的人比只会套框架的人更吃香。1.2 58同城出题风格的深层逻辑有些同学刷完一套字节的题再去做58的题会觉得“太简单了”。但我的感受是58这套卷子考查的重点不在“难”而在“全”。它不太喜欢出偏题怪题反而特别爱考那些平时写代码天天遇到、但未必想得清楚原理的东西。比如有一道选择题考了和的区别看上去很简单但实际上它考的是隐式类型转换的规则null undefined是truenull 0是false[] false是true。如果你只背过“全等不会做类型转换”遇到[] false这种题目就会懵。再比如简答题让解释“setTimeout的this指向”这本质上考的是“谁调用就指向谁”的规则。如果只是背死结论换个场景问就抓瞎了。但如果你理解了函数调用点call-site这个概念所有this题目都能迎刃而解。这套卷子还有一个特点爱考兼容性和边界处理。毕竟58同城当年的用户群跨度很大低版本浏览器占了不少比例所以笔试试卷里出现了关于IE事件处理、addEventListener和attachEvent区别的题目。放到今天虽然不太考兼容性了但“边界处理”这个考察思路一直没变——候选人在写代码的时候有没有考虑空值、异常、边界条件从笔试答案里一眼就能看出来。2. 核心题型解析与经典真题实操2.1 JavaScript基础原型链、闭包与this指向先说说这套卷子里的JS基础题。原型链相关的题目几乎是必考的而且58喜欢把手写继承和原型链理解结合起来考。我印象最深的是一道组合继承的题代码大概是这样的function Animal(name) { this.name name; } Animal.prototype.say function() { console.log(Im this.name); }; function Dog(name) { Animal.call(this, name); } Dog.prototype new Animal(); Dog.prototype.constructor Dog; var dog new Dog(旺财); dog.say();题目问两件事第一行输出什么这个继承方式有什么缺点输出本身很简单Im 旺财。但第二个问题就把很多人卡住了。这道题的考点是组合继承会调用两次父类构造函数一次在Dog.prototype new Animal()时一次在new Dog()时。虽然因为Animal.call(this, name)的存在dog对象自身的name属性会覆盖掉原型上的name功能上没问题但原型上会残留一份无用的name属性。当时我特意写了寄生组合继承来答题后面面试官还追问了一句“为什么寄生组合继承更优”这其实就是把“继承的本质是链接原型而不是复制属性”这句话理解透就能答上来。用ES5的方式实现function inherit(Child, Parent) { var prototype Object.create(Parent.prototype); prototype.constructor Child; Child.prototype prototype; } function Dog(name) { Animal.call(this, name); } inherit(Dog, Animal); Dog.prototype.bark function() { console.log(汪); };闭包和this指向的题目这套卷子也出了。记得有一道简答题让说明代码的输出结果var name window; var obj { name: obj, getName: function() { return this.name; } }; var fn obj.getName; fn(); // 输出什么 obj.getName(); // 输出什么答案是fn()输出windowobj.getName()输出obj。这道题就是典型的“调用点决定this指向”。直接调用时this指向全局作为对象方法调用时this指向该对象。但这里有个坑如果题目改成(obj.getName)()呢还是输出obj因为括号不影响调用点。但如果改成(obj.getName obj.getName)()呢输出window因为赋值表达式返回的是函数本身调用点变成了全局。这类题现在依然是面试热点但形式有了变化。现在的面试官不太直接问this指向了他会让你实现一个Vue的$on、$emit然后在回调里用this的时候问你为什么指向变了归根结底还是在考这个概念只不过考得更隐晦。2.2 异步编程事件循环、Promise与async/await2017年那年async/await刚开始普及Promise已经成了日常写法但还有不少代码用回调函数。58的卷子考了一道事件循环的题直到现在我还经常拿这道题去考新人console.log(1); setTimeout(() console.log(2), 0); Promise.resolve().then(() console.log(3)); console.log(4);输出顺序是1、4、3、2。这里面的知识点有两层宏任务和微任务的执行顺序以及setTimeout定时器回调相对于Promise的优先级。微任务会在当前宏任务执行完后、下一个宏任务开始前执行。Promise的then回调属于微任务setTimeout的回调属于宏任务。所以即使setTimeout延时是0它也得等到微任务清空之后才会执行。笔试后和同学复盘发现不少人在解释原理的时候卡壳了。很多同学能答出顺序但解释不了“为什么”。我当时画了一张事件循环的图来辅助说明一轮循环里先执行一个宏任务然后清空所有微任务再取下一个宏任务执行。setTimeout添加到宏任务队列Promise.then添加到微任务队列。这套卷子还有一道题问如何用Promise串行执行多个异步操作本质上就是考察Promise链式调用。那时候还没有Promise.allSettled所以写起来要麻烦一些。我的答案是function runTasks(tasks) { return tasks.reduce((promise, task) { return promise.then(() task()); }, Promise.resolve()); }这个写法放到现在依然通用而且它还考察了reduce的用法和Promise链式调用的理解。2.3 浏览器与性能优化考点58同城的业务以列表页为主列表页最怕什么图片多、DOM节点多、渲染卡顿。所以卷子里出了好几道关于浏览器渲染性能和网络优化的题目。有一道简答题是合并HTTP请求的方式有哪些标准答案包括CSS Sprites雪碧图、JS/CSS文件合并、图片Base64内联、CDN合并请求等。但如果只答这些分数不会太高。我在这道题后面补了一句“合并请求的本质是减少RTT往返时延但文件合并后体积变大需要在请求数和缓存利用率之间做权衡”后面发现这个补充反而成了加分项。另一道题我印象很深什么情况下会触发回流reflow列举至少3种。我当时的答案包括改变窗口大小修改DOM元素的几何属性宽、高、边距等增删可见DOM元素查询某些属性offsetWidth、offsetHeight等最后一条是很多同学容易漏的。你在读取offsetWidth的时候浏览器为了拿到准确的值不得不强制进行一次回流。如果你在循环里反复读取这个值性能会非常差。所以我在笔试的时候特意提到连续读取多个布局属性之前最好缓存一次结果避免多次回流。这一块内容对现在做前端的人来说依然是核心技能。虽说浏览器性能越来越好了但页面复杂度也在翻倍地涨优化手段的核心逻辑完全没变。3. 编程题实战从思路到实现的完整拆解这套卷子的编程题我印象比较深因为都是那种“平时写过但真要你手写还是容易翻车”的题。我挑三道有代表性的题目从思路到实现完整拆一遍。3.1 手写防抖与节流第一道编程题是写一个防抖debounce函数。题目描述大概是这样的实现一个函数传入一个方法和一个等待时间连续触发时只在最后一次触发后等待指定时间再执行。这个题考察的核心是闭包和定时器。闭包用来保存timer每次触发时先清理上一次的定时器再创建一个新的定时器。标准实现function debounce(fn, wait) { let timer null; return function() { const context this; const args arguments; clearTimeout(timer); timer setTimeout(() { fn.apply(context, args); }, wait); }; }这道题值得注意的点就是this的处理。很多同学写的时候会直接fn()调用这样this指向就丢了。我记得当时在答题时特意加了const context this后面面试官还问了为什么我就把“调用点决定this指向”这个概念又讲了一遍。节流throttle的写法稍微不同。我第一次写的时候用的是时间戳版本function throttle(fn, wait) { let previous 0; return function() { const context this; const args arguments; const now Date.now(); if (now - previous wait) { fn.apply(context, args); previous now; } }; }这套写法有一个问题最后一次触发如果发生在wait时间之外会丢失。更好的版本是用定时器实现的尾调用版本。我当时把两个版本都写了然后对比说明了各自的使用场景时间戳版本适合“立即执行”的场景定时器版本适合“停止后执行最后一次”的场景。3.2 深拷贝与数组去重的多种解法第二道编程题是数组去重。这道题本身不难但58笔试要求至少写出两种方法这就有点意思了。第一种是Set方法这几乎是现在的最优解function unique(arr) { return [...new Set(arr)]; }第二种是indexOf方法适合不支持Set的老环境function unique(arr) { return arr.filter((item, index) arr.indexOf(item) index); }但这两种方法都有坑。Set方法无法区分{}和{}因为对象引用不同indexOf内部使用严格相等比较对于NaN会失效。如果你能在答案里提到这些边界情况面试官会高看你一眼。我记得这道题后面还有一道延伸题深拷贝。我当时写的是基础版function deepClone(obj) { if (obj null || typeof obj ! object) return obj; const result Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { result[key] deepClone(obj[key]); } } return result; }这个版本有两个缺陷一是没有处理循环引用二是没有处理Date、RegExp等特殊对象。笔试时我时间有限没有写全但后来面试中我把这些问题补上了写了一个能处理循环引用和特殊对象的版本。这也算是笔试之后的成长。3.3 实现一个简版Promise最后一道编程题是压轴题实现一个简化版的Promise要求支持then方法和resolve的异步回调。说实话这道题在2017年的校招笔试里算是偏难的。当时我对Promise的源码实现研究过一段时间所以写起来还算顺利。核心思路就是用状态机来管理状态变化回调队列来收集then中注册的函数resolve之后依次执行。我当时写了一个简化版function Promise(executor) { this.state pending; this.value undefined; this.reason undefined; this.onFulfilled []; this.onRejected []; const resolve (value) { if (this.state pending) { this.state fulfilled; this.value value; this.onFulfilled.forEach(fn fn()); } }; const reject (reason) { if (this.state pending) { this.state rejected; this.reason reason; this.onRejected.forEach(fn fn()); } }; try { executor(resolve, reject); } catch (e) { reject(e); } } Promise.prototype.then function(onFulfilled, onRejected) { if (this.state fulfilled) { onFulfilled(this.value); } if (this.state rejected) { onRejected(this.reason); } if (this.state pending) { this.onFulfilled.push(() onFulfilled(this.value)); this.onRejected.push(() onRejected(this.reason)); } };这个版本最大的问题是没有实现链式调用也就是then返回的不是新的Promise。真正的Promise A规范实现起来要复杂得多。写到这里的时候我觉得自己理解了什么是“知道”和“能写出来”的差距。如果你能把这个简版Promise手写出来面试官基本就能确认你是真的有动手能力。4. 高频考点延伸与避坑指南4.1 从2017到2026前端面试考点变迁2017年的笔试和现在的面试相比考点变化还是很大的。2017年重点考察的是原生JS功底和浏览器兼容性现在则变成了框架原理、工程化方案、性能优化实战和TypeScript。但有一类考点始终没变事件循环、闭包、原型链、this指向、手写代码。这些东西是JavaScript这门语言的底层机制跟框架无关跟时代无关。我2023年面试一个候选人让他解释为什么setTimeout回调里的let变量能保存住每次循环的值他支支吾吾答不上来。这道题如果放在2017年的58笔试卷里绝对是送分题。所以我的建议是如果你还在准备面试不要被“2026年最新面试题”这种标题带偏了。先把JS语言本身的原理吃透再去追新框架。基础不牢的话学什么新框架都是空中楼阁。另外现在的笔试题大量转向了“场景题”。比如如何设计一个tab组件的API如何优化一个长列表页面的首屏渲染如何设计一个前端监控方案这类题目没有标准答案考的是思路和架构能力。如果你能结合自己的项目经验来回答会比背八股文有效得多。4.2 笔试中的常见丢分点与避坑技巧结合我自己参加笔试和后来面试别人的经验我总结了几个笔试中特别容易丢分的地方第一不审题。很多笔试题目都会说“至少写出两种方法”或者“说明原因”但有些同学只写了一种方法或者只给出答案不写原因。笔试不是简答题你的思考过程就是得分点。第二变量命名随意。我在批改试卷的时候看到很多function a(b, c)这种代码哪怕逻辑是对的印象分也会打折扣。合格的前端代码应该语义清晰变量名能说明用途。第三边界处理缺失。写深拷贝不处理null写防抖不处理this写数组去重不处理NaN这些都说明你平时的代码习惯还不够好。第四不会展示自己的思考过程。笔试编程题大多要求在线提交但如果你是在纸笔面试中做题一定要把注释写清楚。我在58笔试的时候每道编程题都写了思路注释比如“使用闭包保存timerclearTimeout清除上一次调用”这样即使代码有小bug面试官也能看出你是懂原理的。还有一个非常重要的避坑技巧不要为了炫技写过于复杂的代码。笔试看的是正确性和可读性不是代码越短越好。尤其是使用reduce、递归的时候如果你不确定能一次写对就用简单直观的写法。5. 常见问题与面试经验实录5.1 FAQ速查表我把这份卷子对应的知识点和常见问题整理成一个速查表方便大家复习时快速定位考点常见问题易错点解决思路原型链实现继承的方式有哪些忘记设置constructor指向用Object.create连接原型this指向箭头函数和普通函数的this区别不知道箭头函数没有自己的this看定义时所在作用域事件循环宏任务和微任务的执行时机把Promise误当宏任务记住微任务优先级高于宏任务闭包闭包的内存泄漏问题不知道何时释放引用用完后手动置null防抖节流两者区别和适用场景混淆执行时机防抖是只执行最后一次节流是控制频率深拷贝如何处理循环引用递归死循环用WeakMap记录已拷贝对象HTTP缓存策略有哪些混淆强缓存和协商缓存记住字段名和返回状态码性能优化回流和重绘的触发方式忽略读取属性的强制回流缓存布局属性值5.2 面试准备清单如果你准备的是校招我建议按照以下顺序来准备先做基础扫盲。把JavaScript高级程序设计第三版和第四版选一章过一遍重点的章节是变量、作用域、闭包、面向对象、DOM事件、Ajax。这个阶段不追求速度追求理解。然后刷题验证。牛客网和LeetCode上的前端题库基本覆盖了所有校招考点。刷题的时候不要只对答案要写自己的解法然后去看讨论区里别人的解法对比差异。接下来是手写代码训练。防抖节流、深拷贝、数组去重、事件委托、Promise、原生Ajax封装这些题每天写一遍写到不用想就能完整写出来的程度。2017年为了备考58我连续写了两周防抖节流现在闭着眼都能写出来。最后是项目复盘。准备一个自己做过的最有代表性的项目把技术选型、遇到的技术难点、性能优化方案、代码架构设计都整理一遍。面试官问项目的时候用STAR法则来表达情境背景、任务目标、行动方案、结果反馈。技术博客和社区精华帖也要看但要有的放矢。比如你看到一篇讲Vue响应式原理的文章可以自己动手写一个简单版的响应式系统写完之后对ViewModel的理解会上升一个层次。我在准备58笔试的时候花了两个晚上手写了Vue 2的Object.defineProperty版本响应式虽然代码很粗糙但面试时被问到响应式原理时特别自信。最后再说一点笔试考的是下限面试和项目经历决定上限。笔试分数再高如果项目经验是抄的或者是编的面试官问几个细节问题就会露馅。反过来笔试分数稍微差点但项目经验扎实、思路清晰照样能拿到offer。5.3 那些年我踩过的坑准备58笔试的时候我也犯过不少低级错误这里分享几个最典型的。第一个坑是关于事件循环的。我当时以为setTimeout的最小延迟时间是4ms所以认为setTimeout(fn, 0)至少要等4ms才执行。后来查了规范才发现0并不是最小值而是clamping后的值。在Chrome里4ms的限制只在特定情况才生效。这个认知纠正了我对事件循环的理解。第二个坑是闭包和变量提升的组合题。有一道题是这样的for (var i 0; i 3; i) { setTimeout(function() { console.log(i); }, 100); }我以为输出0、1、2因为觉得for循环每次迭代都执行了一遍setTimeout。后来才发现var声明的i是函数作用域的循环结束后i变成了3三个定时器回调共用同一个i变量所以输出3、3、3。这个坑让我明白了作用域和闭包的关系也理解了对策用let声明i或者用立即执行函数包一层。第三个坑是数组方法混淆。有一次笔试让实现数组去重我用了Array.from(new Set(arr))但因为当时对Set的兼容性不了解被面试官问“如果浏览器不支持Set怎么办”时答不上来。这个经历让我养成了一个习惯写完代码之后思考一下它在不同环境下的兼容性以及有没有更底层的实现方式。这些坑看似是小问题但实际工作中影响很大。2017年58同城还不需要处理太多低版本浏览器的情况但三年后我去做to B项目客户环境是IE11之前的很多新特性写法完全不能用。要不是当年笔试时对兼容性做过功课第一次接这种项目的时候肯定要手忙脚乱。如果你现在也在准备笔面试我的建议是不要只背题要多问自己几个为什么。为什么防抖要保存this为什么Set去重能去掉NaN为什么async/await是语法糖而不是魔法这些问题没有人会直接问你但答得上来的候选人和答不上来的候选人在面试官心中的份量是完全不同的。最后58同城2017秋招前端笔试卷里的那些知识点放到今天依然是前端面试的基石。能把这份卷子吃透的人不管在哪个年代去面试都不会太差。希望这份复盘能帮你整理出自己的知识体系拿到心仪的offer。
返回列表