ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

2019小米前端笔试题解析:从原型链到事件循环的核心考点

2019小米前端笔试题解析:从原型链到事件循环的核心考点 1. 整体题型与考点拆解这份笔试题在考什么2019年小米秋招前端笔试题A传出后很多准备面试的同学第一反应是“题目很多、范围很杂”但我刷完几遍之后反而觉得这份卷子的出题思路其实非常清晰——它想筛选的不是“背过多少API”的人而是“有没有真正写过前端、理解过浏览器、踩过兼容性坑”的人。整份卷子大约包含了选择、填空、简答和手写代码四类大题覆盖了JavaScript核心机制、HTML/CSS布局、浏览器原理、网络基础、框架使用和少量算法逻辑。从分值分布看JavaScript相关内容的占比达到了四成以上其次是CSS布局与浏览器渲染相关的题框架和工程化内容占比并不高这说明小米前端团队在2019年这个时间节点上更看重候选人的原生功底而不是“会哪个框架”。这一点和当时很多大厂的出题方向是一致的。2019年前端圈正处于Vue和React二分天下的阶段各种脚手架工具已经把工程化门槛降得很低很多候选人简历上写着“熟练掌握Vue全家桶”但一碰到“Vue的响应式原理到底是什么”“computed和watch的执行时机区别”这类问题就答不上来。小米这份卷子明显在刻意规避这种“框架熟练度幻觉”把大量题目压在语言本身和浏览器机制上目的就是筛掉那些只会用框架、不懂底层的人。说白了框架可以进公司再学但JavaScript和浏览器原理不行这是前端这个岗位的立身之本也是衡量一个人能不能解决线上疑难杂症的关键。从难度梯度来看这份卷子也做得比较合理。前面选择和填空题大多属于“知道就能答对”的基础题例如事件冒泡与捕获的顺序、和的区别、基本数据类型的判断等这部分是给候选人热身用的也是给面试官建立初步能力画像用的。中间部分的简答题开始考察理解深度比如“解释一下执行上下文”“描述从输入URL到页面展示的完整过程”这类题没有标准答案却最能区分“背过八股文”和“真正理解”的候选人。最后的手写代码题则是拉分项常见的有防抖节流、深拷贝、数组去重、Promise的简单实现等题目本身不偏不怪但要在有限时间内写得严谨、考虑边界情况没有扎实功底很容易翻车。我刷完这份卷子之后最大的感受是它和后来几年很多公司出的“八股文刷题库”有本质区别。八股文题库往往堆砌冷门API和偏题怪题候选人靠刷题量就能堆出高分。而小米这份卷子的题目几乎全部来自日常开发中真正会遇到的问题只是换了一层考察包装。换句话说如果你平时写代码时习惯“知其然也知其所以然”不用刻意准备也能拿到不错的分数反之如果只是机械地调框架API、复制粘贴解决方案即使刷了一百套题遇到这份卷子依然会露馅。2. JavaScript核心机制题详解语言功底是分水岭2.1 原型链与继承最常见的“送命题”小米这份卷子里原型链相关的题几乎是必考的而且考察方式很刁钻不是让你背“原型链是什么”而是给你一段代码让你写出输出结果。例如有这样一道高频题function Foo() { this.name foo; } Foo.prototype.say function() { console.log(I am this.name); }; function Bar() { Foo.call(this); this.age 18; } Bar.prototype Object.create(Foo.prototype); Bar.prototype.constructor Bar; var b new Bar(); b.say();这道题考察的核心是构造函数、原型对象、实例三者之间的关系以及组合继承的实现方式。正确答案是输出“I am foo”。但很多候选人会在这里犯迷糊原因在于对Object.create(Foo.prototype)这一步理解不到位以为这就完成了继承却没有意识到如果直接用Bar.prototype Foo.prototype两个构造函数的原型会指向同一个对象后面给Bar.prototype添加方法时Foo.prototype也会被污染。我当时在给候选人做模拟面试时至少有一半的人会踩这个坑。另外还有一道让我印象深刻的题目也是原型链里的经典变体var obj {}; console.log(obj.__proto__ Object.prototype); console.log(Object.prototype.__proto__); console.log(Function.prototype.__proto__ Object.prototype); console.log(Object instanceof Function); console.log(Function instanceof Object);这里面的前两个输出相对简单obj.__proto__指向Object.prototype而Object.prototype的__proto__是null因为它是原型链的终点。关键是后面两个Object instanceof Function是trueFunction instanceof Object也是true因为Object本身是构造函数而构造函数本质上是Function的实例同时Function.prototype的原型又指向Object.prototype。这种“鸡生蛋蛋生鸡”的关系很多候选人第一次接触时都会懵但真正理解了JavaScript的对象模型之后就非常清晰了。我的建议是画一张原型链图把Object、Function、Array、String这些内置构造函数和它们的prototype、__proto__关系画出来比死记硬背效果好得多。2.2 闭包与作用域输出题重灾区闭包这块小米的卷子基本不考概念题全部是代码输出题。有一道非常经典的题是这样的for (var i 0; i 5; i) { setTimeout(function() { console.log(i); }, 100); }输出结果是5个5这一点大多数人都知道所以它不算难点。真正的分水岭在于后面这问“如果我想输出0、1、2、3、4有几种改法”这就考察了候选人是不是真的理解闭包、块级作用域和立即执行函数之间的关系。常见的解法有三种把var改成let利用块级作用域绑定循环变量用IIFE包裹一层将i作为参数传入或者在setTimeout外面包一层函数并通过参数传递。这三种解法背后的原理并不完全相同let的方式依赖于词法环境IIFE的方式依赖于函数作用域但本质上都是在制造一个独立的变量环境来保存每次循环的i值。小米这份卷子还特别喜欢考一道和闭包相关的工厂函数题大意是function add(x) { return function(y) { return x y; }; } var add5 add(5); console.log(add5(3)); console.log(add(2)(4));这道题本身不复杂输出8和6但它后面经常跟着一道变体考察闭包中变量引用的陷阱function fn() { var arr []; for (var i 0; i 3; i) { arr.push(function() { return i; }); } return arr; } var result fn(); console.log(result[0]()); console.log(result[1]()); console.log(result[2]());这里三个输出全是3因为arr里的三个函数引用的都是同一个i当函数执行时i已经结束循环变成3了。这道题比前面的setTimeout更隐蔽因为很多候选人会以为数组里每个函数保存了当时i的副本实际上保存的是对同一个变量的引用。我在实际开发中也遇到过类似问题比如在循环里绑定事件处理器如果不注意闭包陷阱点击每个元素时拿到的都是最后一个索引所以这个考察点非常贴近实战。2.3 异步与事件循环必考且容易翻车事件循环是2019年前端面试的绝对热门小米的卷子里自然也少不了。一道典型的选择题是问下面代码的输出顺序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。这道题考察的是宏任务和微任务的概念Promise.then注册的微任务会在当前宏任务执行完毕后立即执行而setTimeout即使延迟为0也会被放入宏任务队列等到下一轮事件循环才会执行。很多候选人能答对这道题但一旦把题目改成嵌套的setTimeout和Promise混合体或者加上async/await就彻底乱了。有一道让我印象特别深的变体题把async也卷了进来async function async1() { console.log(async1 start); await async2(); console.log(async1 end); } async function async2() { console.log(async2); } console.log(script start); async1(); setTimeout(function() { console.log(setTimeout); }, 0); new Promise(function(resolve) { console.log(promise1); resolve(); }).then(function() { console.log(promise2); }); console.log(script end);这道题的输出顺序是script start、async1 start、async2、promise1、script end、async1 end、promise2、setTimeout。关键在于await表达式会先把后面的async2()同步执行完然后在await处让出线程将后续代码作为微任务注册。这里很多人会把async1 end和promise2的顺序搞反实际上await后面的代码比普通Promise.then注册得更早所以async1 end先于promise2输出。这类题一旦把async/await加进来考察的就不只是概念记忆而是对事件循环执行机制的真正理解。我的建议是不要死记输出顺序而是在浏览器控制台里多跑几遍配合debugger断点观察调用栈和任务队列的变化理解才会更深刻。3. HTML/CSS与浏览器原理布局和渲染是基本功3.1 盒模型与布局题看似基础实则陷阱重重CSS部分的考察重点首先是盒模型。小米这份卷子考了一道很经典的题一个宽度为200px的元素设置了padding: 20px和border: 5px solid在不设置box-sizing的情况下它实际占用的宽度是多少答案是250px因为默认的content-box模式下宽度只计算内容区padding和border会向外扩展。但如果设置了box-sizing: border-box则元素总宽度保持200px内容区自动压缩。这道题对熟悉CSS的人来说是送分题但我在实际评卷时发现有不少候选人会把box-sizing的默认值记反说明他们平时写样式时依赖UI框架的reset.css太多没有真正理解盒模型的计算逻辑。另一个常考的是垂直居中问题。在2019年的出题背景下答案其实已经比较成熟flex布局的align-items: center和justify-content: center组合是最推荐的方案position transform是兼容性更稳妥的古老方案display: table-cell和line-height方案只适用于特定场景。小米这份卷子考的是“请列举三种垂直居中的方式并说明各有什么优缺点”这其实是一道很实际的工程题因为在真实项目中不同场景下适合的居中方完全不同——如果父容器高度不固定line-height方案就行不通如果涉及到弹窗居中且需要兼容IE9flex可能就不太好使。候选人能否结合具体场景说清楚方案取舍远比背出三种方案更有价值。布局方面小米还考了一道BFC相关的简答题问“如何触发BFC以及BFC能解决什么问题”。我当时看到这道题时觉得很亲切因为BFC块级格式化上下文确实是CSS里最实用也最容易被忽视的概念之一。常用的触发方式包括overflow: hidden、float、position: absolute、display: inline-block、display: flex等。BFC能解决的典型问题有清除浮动带来的父容器高度塌陷、防止上下margin合并、阻止元素被浮动元素覆盖等。候选人如果能从“什么是格式化上下文”讲到“为什么BFC能隔离内部元素对外部的影响”再配合一两个自己在实战中遇到的案例这道题就能答得很出彩。3.2 浏览器渲染机制与性能优化题不止是背流程浏览器输入URL到页面展示的完整过程几乎是所有前端笔试的必考题小米这份卷子也不例外。完整答案包括DNS解析、建立TCP连接、发送HTTP请求、服务器响应、浏览器解析HTML构建DOM树、解析CSS构建CSSOM树、将两者合并为渲染树、计算布局、绘制页面等步骤。这道题看起来是背诵题但实际上如果候选人只是背流程而没有一个环节能展开讲面试官很快就能识破。比如问到“DOMContentLoaded和load事件有什么区别”如果只回答“前者是DOM解析完触发后者是图片等资源加载完触发”那只算及格如果能进一步说明“如果脚本使用defer或async加载DOMContentLoaded的触发时机会有不同表现”并解释defer保证执行顺序、async不保证这才算真正理解了浏览器解析机制。小米这份卷子里还有一道让我觉得出得很好的题给出一段包含script标签、样式表和内联脚本的HTML片段问页面的渲染会如何被阻塞以及如何优化。这道题表面考的是“脚本放在head里会阻塞渲染”这个基础点但往深了问就会涉及到async和defer的区别、preload和prefetch的适用场景、CSS是否会阻塞DOM解析等细节。我在实际优化过的项目中就踩过类似的坑一个首屏页面引了一个第三方统计脚本放在head里同步加载结果导致整个首屏白屏时间多了将近800毫秒。后来改成async加载首屏渲染速度立竿见影。这种经验如果能写进答题里面试官会非常认可。还有一道关于CSS性能优化的题也值得一提请说说重排reflow和重绘repaint的区别以及如何减少重排。这道题的答案要点在于重排是指布局引擎需要重新计算元素的位置和大小代价远高于只更新像素的重绘常见的触发重排的操作包括修改元素宽高、增删DOM节点、改变窗口尺寸、读取offsetHeight等属性优化手段包括使用transform代替top/left做动画、批量修改DOM后再统一插入、用DocumentFragment减少DOM操作、避免频繁读取布局属性等。我当时的建议是如果时间充裕可以现场写一段代码示例演示如何通过合并读写操作来减少强制同步布局这种实操展示比纯文字回答更有说服力。4. 算法与手写代码题实战考察编程思维和代码习惯4.1 算法基础题分析偏向逻辑思维而非竞赛难度小米这份试卷的算法题整体难度低于纯竞赛类题目更偏向于考察基础逻辑和编程思维这和大厂前端岗位的定位是一致的——前端工程师不需要会做红黑树手写但必须能把一个实际问题抽象成算法模型并落地实现。典型题目包括数组去重、字符串反转、斐波那契数列、二分查找等。比如数组去重这道题看起来简单到所有人都会写但考察点在于你能写出多少种解法以及是否知道各种解法的适用场景。最基础的解法是双重循环去重时间复杂度O(n²)只适用于小数组进阶一点的用indexOf或includes判断代码更简洁但依然是O(n²)再往上是利用Object或Map做哈希表去重时间复杂度降到O(n)但需要注意Object的键会被转成字符串最优雅的是利用Set的特性一行搞定。我当时在实际开发中遇到过一个大数组去重的性能问题数组长度接近十万里面包含大量对象用includes去重跑了近一秒。换成Map按对象的id字段去重后耗时降到了数十毫秒。这个案例放在笔试题的答案里会让人觉得你是真正处理过性能问题的人而不是只会背解法。小米的题还喜欢在去重基础上加条件比如“只对数字类型去重保留第一次出现的顺序”这就排除了Set和Object的一些便捷方案考察候选人能否灵活应对约束条件。还有一道字符串相关的题也值得说说实现一个函数判断一个字符串是不是回文串。基础解法是双指针从两端向中间遍历时间复杂度O(n)空间复杂度O(1)。这道题本身不难但小米后续的追问是“如果字符串里包含空格和标点符号忽略这些字符再判断应该怎么处理”这实际上是在考察字符串预处理和正则表达式的熟练度。候选人如果能在解题时主动考虑到这些边界情况说明代码思维比较成熟而不只是会写教科书答案。4.2 手写代码题防抖、深拷贝和Promise是重头戏手写代码题是整份试卷中最能拉差距的部分。小米2019年的笔试题中防抖和节流的实现几乎是必考的。这两个概念看似简单但很多候选人写着写着就暴露了对this指向和参数传递理解的不足。一个标准防抖函数的实现是function debounce(fn, delay) { let timer null; return function(...args) { const context this; if (timer) { clearTimeout(timer); } timer setTimeout(() { fn.apply(context, args); }, delay); }; }这里的关键点在于返回的函数必须保留this指向否则在作为对象方法使用时会出现this丢失...args用来透传原函数的参数timer保存在闭包中实现“下次调用会取消上次的定时器”的效果。很多候选人能写出主体逻辑但容易漏掉this的处理或参数的透传这两点恰恰是在真实业务中避免bug的关键。节流函数同理区别在于节流是保证在一段时间内只执行一次实现方式有定时器版和时间戳版两种各自在处理首尾触发时的行为不同候选人如果能把两种版本的差异说清楚得分会高很多。深拷贝也是小米卷子里的常客。这道题看着不难但“深搜到底”并不容易。一个合格的深拷贝需要处理基本类型直接返回、数组和对象的递归复制、循环引用导致爆栈、特殊对象如Date、RegExp、Map、Set的兼容等。我在评卷时看到过很多版本有的只针对普通对象做递归遇到数组就出问题有的完全不处理循环引用测试时直接栈溢出。一个相对完整的实现思路应该是先用WeakMap存储已拷贝的对象遇到循环引用时直接返回缓存引用然后判断属性类型分别处理。小米这道题还有一个有趣的加分点是提示候选人能不能用JSON.parse(JSON.stringify(obj))来实现深拷贝以及该方法的局限性是什么——不能拷贝函数、不能处理undefined和Symbol、不能处理循环引用、会丢失对象的constructor等。能答出这些局限性并说明自己实现方案时如何规避这道题就能拿到高分。Promise的手写实现是另一道重量级题目不过2019年小米的卷子没有要求完整实现Promise的整个规范而是考了一个简化版的链式调用和异步执行。但即使如此候选人需要对Promise的状态机pending、fulfilled、rejected和执行流程有清晰理解才能真正写对。我建议准备这道题时至少要实现resolve、reject、then的核心逻辑并且注意then返回的必须是一个新的Promise才能支持链式调用回调的执行需要放进微任务队列在浏览器环境下可以用Promise.resolve()来模拟微任务。5. 框架题与工程化方向考察深入但不偏门5.1 框架原理题Vue和React的底层对比2019年小米的笔试里框架相关题目占比不高但考察的方向很明确。Vue的题主要集中在响应式原理上比如“Vue 2.x是如何实现数据响应式的”标准答案是Object.defineProperty对数据对象的每个属性进行劫持在getter中收集依赖、在setter中触发更新。进阶追问是“为什么Vue 3要改用Proxy”答案要点包括Proxy可以拦截对象级别的操作而不需要遍历对象的每个属性、支持动态新增属性和删除属性的响应式、性能更好、可以拦截数组索引操作等。候选人如果平时只看文档不用源码很难把这道题讲得太深所以它能有效区分“用过Vue”和“理解Vue”的人。React的题目则更多集中在组件生命周期和状态管理上。2019年时React 16.8刚推出来不久Hooks是热门话题。小米考了一道“请对比useEffect和componentDidMount的差异”这其实是一道很有深度的题因为useEffect在每次渲染后都会执行可以传入依赖数组控制而componentDidMount只在组件挂载后执行一次useEffect的清理机制对应的是componentWillUnmountuseEffect的执行时机在浏览器绘制之后而componentDidMount在绘制之前触发。能把这些细节梳理清楚说明候选人不是只写过几个Demo而是踩过Hooks闭包陷阱、想过执行时机的真实问题。5.2 工程化题打包工具和代码规范离不开工程化方面的题目小米考得比较务实。有一道题是问“Webpack的loader和plugin有什么区别”这是前端构建领域最基础的考题。答案要点是loader是文件加载器负责将不同类型的文件转换成模块本质上是导出函数的处理器plugin是插件可以介入Webpack生命周期的任意阶段执行更复杂的构建优化任务。候选人如果答到这里就停只能算是及格如果能进一步结合自己使用过的babel-loader、file-loader、html-webpack-plugin、mini-css-extract-plugin等工具说明各自解决了什么问题并分享一个通过配置插件完成构建优化的实际案例这道题的得分会明显高出平均水平。代码规范也是小米考察的一个方向有一道题是关于ESLint配置的作用和必要性。这体现了公司对团队协作质量的要求——前端项目多人协作时如果没有统一的代码规范代码风格会变得杂乱无章code review的成本会直线上升。候选人如果能在答案中提到自己参与过团队ESLint规则配置的经验比如根据项目实际情况关闭某些过于严格的规则、集成prettier统一格式化风格、在CI流程中加入lint检查拦截不合格代码等会给面试官留下“有团队协作经验”的印象。我当时在准备这类题目时把平时在项目中顺手配置过的工具都翻出来复盘了一遍发现很多当时觉得“理所当然”的配置正是因为理解了背后的原理才能在笔试中举出真实案例。6. 高频失误与备考建议那些年我们一起踩过的坑6.1 笔试现场的高频错误清单根据我刷小米2019年秋招笔试题的经验结合周围朋友的反馈整理出几个在笔试现场最容易出现的典型失误第一类是时间分配失衡。前面选择和填空题占据了大量时间导致最后的手写代码题没时间仔细思考。小米的试卷题量不小前面的基础题确实相对简单但正因为简单很多候选人会反复斟酌、想着“确保万无一失”结果等到后面的简答题和代码题时已经剩下不到二十分钟。我的建议是遇到一眼能看出答案的选择题就快速跳过拿不准的先标记题号等全部写完后再回头思考千万不要在前面跟题目死磕。第二类是手写代码时忽略边界条件。很多候选人写深拷贝时忘了处理循环引用写防抖函数时忘了保留this指向写数组去重时没考虑到NaN的情况。这些细节在本地IDE里运行时编译器会提示错误但笔试时是白板或在线编辑器不会给你实时调试的机会只能靠平时的代码习惯来规避。我建议备考时尽量手写代码不要依赖IDE的自动补全和报错提示逼自己在写代码时就考虑所有边界情况。第三类是概念回答“只见树木不见森林”。比如问到“什么是闭包”如果回答“函数内部定义函数内部函数可以访问外部函数的变量”这只是给出了定义如果能进一步说明闭包的形成机制、内存泄漏风险和应用场景如防抖节流、私有变量、模块化才能展现真正的理解深度。小米的阅卷人大概率是前端团队的资深工程师他们评分时最看重的就是候选人能否把概念和实际开发经验结合起来。6.2 如何针对性准备小米这类笔试题结合小米这份卷子的出题风格我给准备大厂前端笔试的同学几条具体建议第一用“输出题”敲打基础概念。不要只看JavaScript教程而是找大量的代码输出题来练手尤其是闭包、事件循环、原型链这三个高频考点的变体题。每做一道题不仅要写出正确答案还要把每一步的执行过程口头讲清楚能用“这段代码为什么输出这个结果”来解释才算真正掌握。第二手写代码不能只看不练。防抖节流、深拷贝、数组去重、字符串模板解析、Promise简化实现、EventEmitter这些常见手写题建议至少完整地将代码在纸上敲三遍以上第一遍凭记忆写第二遍看答案纠错第三遍不看答案独立写并主动扩展边界情况。我在备考时还发现一个有用的技巧把自己写好的手写代码放到LeetCode或Codewars上跑一些测试用例因为笔试的在线评测系统通常会在用例里埋一些边界坑提前见识过会安心很多。第三CSS的重点要落在“能解释为什么”上。盒模型、BFC、垂直居中这些基础知识点大家都能说上一两句但面试官更想听到的是你在不同场景下的选择理由。准备时可以先列出常见布局场景再针对每种场景比较不同实现方案的优劣形成自己的决策逻辑而不是死记硬背标准答案。第四不要忽略浏览器原理和网络基础。小米的试卷里从URL输入到页面渲染的题目反复出现它本质上是在考察候选人对“前端代码如何在浏览器中变成用户看到的页面”这个完整链路是否有清晰的认知。建议把整个流程画成一张图把每一步涉及的关键概念DNS、TCP、HTTP缓存、DOM树、CSSOM、渲染树、重排重绘串联起来能用自己的话把整条链路讲清楚这类题目基本就不会失分了。6.3 笔试过后的复盘思维我见过太多候选人考完就把试卷丢到一边只关心“过没过”而不关心“错在哪”。这种做法浪费了笔试最有价值的部分——它其实是一次免费的、由大厂前端团队出题的能力体检报告。每道错题背后都对应着一个知识盲区或思维盲点。正确的复盘姿势是考完后把每道题重新做一遍标出哪些是“本来会但粗心错了”哪些是“根本不会纯靠蒙”哪些是“会但答得不完整”的。粗心错的需要在后续做题中提高警惕不会的题需要回归教材补基础答得不完整的题则说明理解深度不够需要多问几个“为什么”来深化。另外我建议大家养成整理错题集的习惯。前端知识点很零碎今天看过的原型链题明天可能就被事件循环的题挤到脑后了。用笔记工具把做错的题、踩过的坑、容易混淆的概念集中记录下来每隔两周回顾一遍效果比每天刷一百道新题要好得多。我在备考期间维护了一个“前端面试错题本”里面记录的全是自己做错的或理解不到位的题目每道题下面还有当时的错误原因分析和正确思路笔记。后来面试时我发现很多考点的出题角度和我错题本上记录的几乎一样当时内心真的感慨“果然所有大厂的坑都是相似的。”这份小米2019年的前端笔试题放到几年后的今天来看有些知识点已经过时了比如手写XMLHttpRequest的题现在已经被fetch取代了一大半Vue 2的响应式原理也被Vue 3的Proxy方案更新了但它的核心考察逻辑——不求偏、不求怪只求候选人真的理解前端最基础的那块基石——放到任何一届秋招都不过时。准备笔试题的过程本质上就是把过去几年写过的代码重新从底层审视一遍而这个审视的过程可能就是技术水平上一个台阶最好的契机。
返回列表