ARTICLE DETAIL

资讯详情

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

58同城2017前端笔试题全复盘:从JS基础到工程化核心考点

58同城2017前端笔试题全复盘:从JS基础到工程化核心考点 每年一到秋招季就有不少人翻出58同城2017年的前端笔试卷来对照复习。说实话这张卷子放在今天看知识点依然不过时甚至很多题目现在面试还在变着法考。我当时刷这张卷子的时候踩了不少坑有的题第一眼觉得简单结果一写就漏洞百出有的题当时完全没思路后来回头补基础才发现考的全是硬功夫。这篇文章就把这张卷子完整复盘一遍。我不打算只把题目和答案罗列出来而是结合2017年前后端分离逐渐成熟、框架刚普及的行业背景把每类题背后的考察意图拆开讲清楚再给出完整的解题思路和参考答案。不管你是准备校招、社招跳槽还是刚入行想系统补一遍基础这份复盘应该都能帮你在复习时少走弯路。1. 笔试全貌2017年前端笔试在考什么1.1 那个年代的前端生态与考察风向2017年前端这个岗位处在一个很特殊的节点。jQuery还在大量老项目里跑着但React已经火了两三年Vue 2.0也发布了前端工程化开始成为团队标配。与此同时HTML5、CSS3、ES6这些基础能力成了用人单位的硬性筛选条件。笔试命题人的思路非常统一既要筛掉只懂切图和套模板的选手也要提前看看候选人有没有接触过模块化、组件化的开发方式。所以你会发现58同城这张卷子几乎覆盖了“基础能力、框架认知、开发素养”三个层面。基础题集中在JS语法、DOM操作、CSS布局和浏览器原理上框架题不是考源码而是考“你为什么用它、它解决了什么问题”最后还有几道开放性的设计题考察代码组织能力和问题分析能力。我在准备时最大的感受是这套题很“实”。没有偏题怪题每一道都能在工作中找到对应场景。比如考事件冒泡本质上是在考察事件委托考闭包本质上是在考察模块化封装的能力。命题人不是想难倒你而是想通过笔试题判断“这个人能不能干活”。1.2 试卷结构还原与各模块分值逻辑结合多个渠道能找到的面试回忆和真题整理58同城2017秋招前端笔试卷大致包含以下几类题型题型题量与分值占比考察点单选与多选约15-20题占比30%基础语法、CSS属性、HTTP状态码、DOM API填空题约5-8题占比10%输出结果、代码补全、概念填空简答题约4-6题占比25%原理说明、方案对比、场景分析手写代码题约3-5题占比35%算法、工具函数、组件实现从分值逻辑就能看出手写代码题是拉开差距的关键。选择题和填空题更多是“热身”防止有人交白卷而真正决定是否进入面试的是你在手写题里展现出的编码习惯和思维深度。我当时给自己定的目标是选择题和填空题尽量不丢分简答题答到点子上手写题至少完整做出三道。按照这个目标去分配复习时间效果比漫无目的刷题好很多。2. 核心考点逐个拆解JS基础与手写题2.1 作用域、闭包与this指向这一块几乎是所有前端笔试的“钉子户”。58这张卷子里有一道很经典的题考察的是var声明与闭包的组合。原题大概是这样的循环里用var声明变量给按钮绑定点击事件问点击后输出什么。button按钮1/button button按钮2/button button按钮3/button script var buttons document.querySelectorAll(button); for (var i 0; i buttons.length; i) { buttons[i].onclick function() { console.log(i); }; } /script答案是连续输出3。原因在于var声明的i是函数级作用域循环结束后i已经变成3而事件回调里引用的i是同一个变量。考察的目的不是让你背答案而是看你能不能讲清楚“为什么”。我当时把三种解决方案都写了用let块级作用域、用闭包保存每次循环的i、用事件对象的dataset属性。笔试时时间充裕的话建议把多种解法都写上去阅卷人会认为你的思路比较开阔。this指向的题同样高频。经典的组合是普通函数调用、对象方法调用、call/apply/bind调用、箭头函数调用四种情况分别输出什么。这里有个不太容易说清的点是箭头函数它的this不是在调用时确定的而是在定义时从外层作用域继承的。我曾经在笔试里把箭头函数的this误判成“指向window”后来复盘时才发现关键在于它没有自己的this绑定。2.2 事件循环、setTimeout与Promise的执行顺序事件循环这道题放在2017年其实已经开始流行了因为Promise已经普及异步编程从回调时代跨入Promise时代。58这张卷子里的考察方式是给一段混合了setTimeout、Promise、普通同步代码的程序要求写出输出顺序。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。核心逻辑是微任务microtask优先于宏任务macrotask执行Promise的then属于微任务setTimeout属于宏任务。我当时栽过跟头原因是把“setTimeout 0毫秒”理解成了“立即执行”。实际上0毫秒只是表示延迟时间的下限浏览器最快也要到当前任务执行完后才去处理它。理解这一点之后这类题基本就能做对了。2.3 原型链与继承手写原型链是2017年笔试的必考内容那时候ES6的class虽然已经出现但笔试题还是倾向于让你用ES5实现继承。原因很简单ES5的继承能看出你对函数、原型、构造函数的理解是否透彻。比较有代表性的一道题是实现一个寄生组合式继承并说明它相比其他继承方式的优势。标准的实现思路是function Parent(name) { this.name name; this.colors [red, blue]; } Parent.prototype.getName function() { return this.name; }; function Child(name, age) { Parent.call(this, name); this.age age; } Child.prototype Object.create(Parent.prototype); Child.prototype.constructor Child; Child.prototype.getAge function() { return this.age; };关键点有两个。第一通过Parent.call在子构造函数里调用父构造函数把父类实例属性继承过来避免了共享引用类型属性带来的问题。第二通过Object.create(Parent.prototype)把子类原型指向父类原型的副本而不是直接赋值为Parent.prototype这样给Child.prototype添加方法时不会污染Parent.prototype。笔试现场经常有人把Child.prototype.constructor Child这一行漏掉。虽然很多场景下不影响功能但严格来说constructor指向不正确会导致instanceof判断异常也会让阅读代码的人觉得你基础不扎实。3. 浏览器与CSS布局笔试里的“送分题”与“陷阱题”3.1 CSS盒子模型、BFC与居中方案CSS题在笔试里看着简单实际上特别容易丢分。58这张卷子对CSS的考察集中在几个高频话题上标准盒子模型与IE盒子模型的区别、BFC是什么以及如何触发、垂直水平居中有哪些方案。盒子模型这块我在复习时给自己画了一张对照表笔试前过一遍非常有用对比项标准盒模型content-boxIE盒模型border-boxwidth含义只包含content包含content、padding、border最终元素宽度width padding border就是width本身设置方式box-sizing: content-boxbox-sizing: border-box适用场景老代码默认行为移动端布局、栅格系统更方便BFC块级格式化上下文的考察形式通常是问BFC是什么以及哪个属性可以触发BFC。常见触发方式有float不为none、position为absolute或fixed、display为inline-block或flex、overflow不为visible。问到BFC能解决什么问题我一般会举三个例子清除浮动、防止margin塌陷、阻止元素被浮动元素覆盖。居中方案这道题必须有条理地列举。水平居中可以用margin: 0 auto、text-align: center、flex的justify-content垂直居中可以用line-height、flex的align-items、绝对定位加transform。笔试阅卷人比较看重的是你知道每种方案的适用场景而不是单纯背代码。3.2 经典布局题两栏、三栏与圣杯布局2017年笔试特别喜欢考布局手写58这张卷子也不例外。常见考题是左侧固定宽度右侧自适应要求写出多种实现方案。最常见的两种写法是floatmargin和flex。float方案的代码结构是div classleft固定宽度200px/div div classright自适应/div.left { float: left; width: 200px; } .right { margin-left: 200px; }flex方案更简单父容器开启flex左侧给定宽度右侧flex: 1。这里有一个细节经常被忽视用float布局时right的margin-left必须大于等于left的宽度否则会出现重叠而用flex布局时不需要考虑这个问题。三栏布局的完整做法一般要求写圣杯布局或双飞翼布局。我在笔试中更倾向用flex实现因为代码更短、逻辑更清晰。但复习时还是要把圣杯布局的原理搞清楚因为有些面试官会问“为什么中间列要写在最前面”这类问题这涉及到HTML解析顺序和对SEO的考虑。3.3 HTTP缓存与跨域为什么笔试必考HTTP缓存和跨域几乎是每年笔试的压轴常识题。2017年前后端分离开始流行前端工程师要自己处理接口联调和缓存策略这两个知识点直接关系到线上性能和数据安全所以必考。HTTP缓存的考察形式通常是强缓存与协商缓存的区别、相关响应头有哪些、用户刷新页面时缓存策略如何变化。我比较习惯用一张流程表来理清思路场景强缓存命中协商缓存命中缓存失效请求头无网络请求发送请求带上If-Modified-Since或If-None-Match重新下载资源响应状态200from disk cache304 Not Modified200 OK相关头Expires / Cache-ControlLast-Modified / ETag无跨域题考察的则是什么是同源策略跨域有哪些解决方案。JSONP、CORS、postMessage、代理转发每种都要能说出一两句原理。我在笔试中遇到的一道开放题是“如果接口跨域了你怎么排查和解决”考的不只是知识点还有排错思路。4. 框架与工程化初探2017年的加分题4.1 数据双向绑定与虚拟DOM2017年的笔试卷里框架题基本不会让你手写React或Vue的源码而是考察你对核心机制的理解。58这张卷子有简答题问Vue的双向数据绑定原理以及React的虚拟DOM为什么能提升性能。Vue 2.x的双向绑定原理标准回答是通过Object.defineProperty重写数据的getter和setter在getter中收集依赖在setter中触发更新配合观察者模式完成视图更新。需要特别说明的是这种实现方式有局限性比如数组的索引变化和对象新增属性无法被检测到所以Vue才提供了$set方法。虚拟DOM的考察角度是性能优化。我在回答这类题时会强调虚拟DOM不一定比直接操作DOM快它真正解决的问题是“把多次DOM操作合并成一次”以及“通过diff算法最小化DOM操作范围”。这个说法在2017年算比较前沿的观点放到今天依然成立。4.2 构建工具的基础认知2017年的前端笔试已经开始涉及构建工具但很少考具体配置更多是让你解释webpack的核心概念。我在复习时总结了一套“一句话理解法”entry是入口文件output是打包输出位置loader让webpack能处理非JS文件plugin则用来做打包之外的各种事情。58这张卷子里有一道填空题问loader和plugin的区别。不少人在这个地方失分因为两者的边界容易模糊。我的记忆方法是loader是“翻译官”把scss编译成css、把ES6转成ES5plugin是“管家”负责代码压缩、热更新、环境变量注入这些杂活。一个管内容转换一个管流程扩展。4.3 手写发布订阅框架题的另一种考法是让你实现一个发布订阅模式因为Vue的$on、$emit就是基于这个模式。手写这个类在笔试里是个性价比很高的题代码不长但能考察你的事件管理能力。我当时的参考实现是这样class EventEmitter { constructor() { this.events {}; } on(event, callback) { if (!this.events[event]) { this.events[event] []; } this.events[event].push(callback); return this; } emit(event, ...args) { const cbs this.events[event]; if (cbs) { cbs.forEach(cb cb(...args)); } return this; } off(event, callback) { if (!callback) { delete this.events[event]; return this; } const cbs this.events[event]; if (cbs) { this.events[event] cbs.filter(cb cb ! callback); } return this; } }笔试时如果能顺便写上once只触发一次的方法会是一个明显的加分项。现在不少面试题就是从这些笔试题演变而来的实现思路没变只是要求提高了。5. 笔试题里的代码实战典型题完整解答5.1 数组去重与排序手写代码题里数组去重可以说是出镜率最高的题了。2017年的时候ES6的Set已经被接受但笔试题仍然会要求你用ES5的方式实现考察你对indexOf、对象属性名、双重循环等方案的掌握程度。几种常见实现的对比方案思路优点缺点双重循环遍历数组逐个比较删除重复项兼容性好时间复杂度过高indexOf用新数组判断是否存在代码简单无法区分NaN对象键值对用对象属性做标记速度较快需处理对象键名冲突SetArray.from(new Set(arr))最简洁环境需支持ES6如果笔试要求写“通用去重”我更推荐对象键值对方案加一层类型判断。要注意的是直接用对象做标记时数字1和字符串1会被当成同一个键所以很多答案会用typeof加上value拼接成标记避免类型混淆。排序题通常是让你手写快速排序或归并排序。笔试现场写快排最容易出错的点有两个一是递归的退出条件没写对二是基准值选择方式导致性能退化。我习惯的写法是function quickSort(arr) { if (arr.length 1) return arr; const pivot arr[0]; const left []; const right []; for (let i 1; i arr.length; i) { if (arr[i] pivot) { left.push(arr[i]); } else { right.push(arr[i]); } } return [...quickSort(left), pivot, ...quickSort(right)]; }虽然这种写法不是最优的原地快排但在笔试场景下它最容易保证正确性。我当时的想法是先保证能跑通再在注释里补充优化思路。5.2 防抖与节流防抖和节流在2017年已经属于“必会工具函数”级别了。58这张卷子里的考题是解释防抖和节流的区别并手写实现。防抖的核心思想是“在事件触发后等待一段时间再执行如果等待期间又触发了就重新计时”。典型的应用场景是搜索框输入。节流的核心思想是“单位时间内只执行一次”典型场景是滚动事件。我的防抖实现function debounce(fn, delay) { let timer null; return function(...args) { if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, delay); }; }节流实现function throttle(fn, interval) { let last 0; return function(...args) { const now Date.now(); if (now - last interval) { last now; fn.apply(this, args); } }; }有一个细节在笔试现场容易被忽略定时器版的节流和防抖都涉及this指向问题。如果直接写setTimeout(() fn(...args), delay)在严格模式下this就是undefined。我当时意识到了这一点所以在实现里特意用了apply来保留this这一点在很多参考答案里都没有体现。5.3 深浅拷贝深拷贝和浅拷贝的考察频率同样很高。主要原因是Vue和React中数据不可变性的概念越来越重要如果不理解拷贝语义就很容易写出一堆隐晦的bug。浅拷贝常用方式有Object.assign、扩展运算符、Array.prototype.slice。它们的共同点是只拷贝第一层属性嵌套对象仍然是同一个引用。深拷贝的实现笔试题一般不允许你直接写JSON.parse(JSON.stringify(obj))因为这种方法无法处理函数、undefined、Symbol、循环引用。我当时的写法是递归实现function deepClone(obj, hash new WeakMap()) { if (obj null || typeof obj ! object) return obj; if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags); if (hash.has(obj)) return hash.get(obj); const result Array.isArray(obj) ? [] : {}; hash.set(obj, result); Reflect.ownKeys(obj).forEach(key { result[key] deepClone(obj[key], hash); }); return result; }这个版本使用WeakMap处理循环引用是比较完善的答案。笔试时如果时间紧张至少要把递归拷贝和循环引用的问题答出来会让阅卷人对你的水平评价上一个台阶。6. 现场答题策略与时间分配6.1 拿到卷子先做三件事笔试和面试不一样面试拼的是临场反应笔试拼的是时间管理和查漏补缺的能力。我当时拿到卷子后不会立刻动笔而是先花三分钟做三件事扫一遍所有题目标记出确定能做对的题、有思路但需要时间的题、完全没头绪的题看一眼大题的分值比例确定手写题要留多少时间把试卷上出现的考点关键词在脑海里过一遍相当于给自己做一个“知识激活”。这里有个实用的小经验简答题尽量写在最前面或者最后面不要插在手写题中间。我踩过一次坑原因是先做了手写题耗费了大量精力等到写简答题时大脑已经很疲惫一些本来背得很熟的知识点反而组织不清楚了。后来调整了顺序先解决记忆性的简答题再集中精力攻手写题效率明显更高。6.2 每类题的时间红线按照60分钟的笔试时长来算我给自己划定过一条时间线选择题和填空题控制在15分钟以内简答题控制在20分钟以内剩下25分钟全部给手写题。选择题和填空题的“时间红线”尤其重要因为这类题单个分值不高一旦在某一道题上纠结太久就会挤占后面大题的发挥时间。我的原则是单选题超过1分钟没思路先凭第一印象选一个在题号旁边做个标记等所有会做的题做完后再回来思考。手写题的时间分配可以按照“先易后难”的顺序。一般来说数组去重、防抖节流、简单继承是相对容易拿分的题发布订阅和深拷贝难度略高。先拿稳容易的分再挑战难题心态上也不会崩。6.3 草稿与卷面习惯笔试的卷面习惯看似不重要实际上很影响阅卷人的印象分。我在复盘时发现字迹工整、步骤清晰的答案即使不够完整也更容易拿到步骤分。写代码题时我习惯先在草稿纸上写一遍确认没有明显语法错误后再誊写到答题卡上。草稿阶段要注意变量命名和缩进统一。很多人在笔试现场一紧张代码缩进就会乱掉阅卷人很难读下去。另外如果手写代码的空间不够可以在题目旁边用箭头加注释但务必保证标注清楚。7. 复盘清单我做错的那些题与反思7.1 高频失分点我在刷58这张卷子以及同类笔试卷时整理出一份高频失分点清单每一次对着它检查都能发现自己忽视了某个细节。失分点典型错误正确思路var循环闭包以为输出0、1、2输出3需要用let或闭包保存事件循环顺序以为setTimeout先行微任务优先于宏任务this指向箭头函数this误判箭头函数的this由外层作用域决定盒模型width理解错误区分content-box与border-box深拷贝循环引用导致栈溢出用WeakMap记录已克隆对象跨域方案只答JSONP至少补充CORS和代理方案每次复盘时我会把错题对应的知识点在笔记里单独标记一遍复习时优先看这些标记。原因是笔试卷覆盖的知识点其实有限真正让你丢分的往往就是那几个薄弱点针对性补比盲目刷题效率高得多。7.2 如何利用一张旧卷子构建复习体系一张2017年的笔试卷放到今天看内容已经不完全覆盖前端知识体系了但它的复习价值在于帮你搭建“笔试答题框架”。我的用法是把卷子里的每道题归入“基础语法、浏览器原理、CSS布局、框架理解、工程化、算法能力、场景分析”这七个维度然后针对每个维度补充10到20道同类题目。比如卷子里考了事件循环我就把微任务、宏任务、async/await的各种组合都刷一遍考了圣杯布局我就把flex、grid、float三种布局方式全部重写一遍。还有一个很关键的动作把笔试题转成面试题。卷子里问“什么是BFC”我就会追问自己“BFC在实际项目里解决过什么问题”卷子里让写防抖我就继续思考“如果需要在首次触发时立即执行应该怎么改”。这种思维转换能让你从“会刷题”升级成“会做事”到面试环节会非常占优势。根据我个人经验准备笔试最忌讳的就是只看不写。2017年备考时我一度觉得自己知识点都会了结果一上手写代码就漏洞百出。后来把每一道手写题都当作面试现场来练规定时间、手写草稿、当场检查半个多月后写代码的速度和准确度都提升了一大截。如果你现在也在准备前端岗位的笔试不妨把这份卷子里的题目找出来在纸上老老实实写一遍你的体感会告诉你哪些地方还需要补。
返回列表