尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

小米2019秋招前端笔试题B解析:JavaScript到浏览器原理全覆盖

小米2019秋招前端笔试题B解析:JavaScript到浏览器原理全覆盖 小米2019秋招前端开发笔试题B考查的核心内容放到现在来看依然没有过时——JavaScript基础、浏览器原理、网络协议、框架与工程化再加上一道或几道编程题。这些题目看似零散实际都在反复敲打同一个东西你到底是背过八股文还是真的理解前端这门学科的运行逻辑。这篇文章我把整套试卷按考察方向拆开来讲每一类题目都会还原当时的出题思路、正确解法以及我在复盘时补上的延伸思考。无论你是准备面试还是想查漏补缺按这个脉络走一遍收获会比裸刷几套题扎实得多。1. 笔试题型分布与考察逻辑这套卷子到底在考什么2019年秋招的前端笔试和现在有个很大的区别当时还没有那么多AI辅助工具面试官想判断一个候选人的水平只能靠你在纸上写代码、写思路。小米这套B卷整体题量不小题型覆盖了单选、多选、填空、简答和编程题考察面横跨JavaScript语言特性、浏览器工作机制、前端性能优化、网络协议、框架应用和算法基础。先说一个很多人忽略的点这套卷子的题型分布本身就是一种暗示。JavaScript语言特性题占比最高其次是浏览器和网络框架题反而不多。这说明大厂前端笔试的思路很清晰——框架可以进公司再学但语言功底和计算机基础是硬门槛。当年不少候选人栽在熟悉Vue源码这种自我标榜上却在原型链和事件循环的题目上丢了分这就是复习方向偏了的典型症状。我梳理了一下B卷各个考察方向的大致占比虽然不是官方数据但按当时多个渠道的考生回忆汇总基本能还原出题人的侧重点考察方向大致占比典型题目形式JavaScript基础30%-35%原型链、作用域、闭包、this指向浏览器与网络20%-25%HTTP缓存、渲染过程、跨域CSS与布局10%-15%盒模型、flex布局、BFC框架与工程化10%-15%Vue/React基础、模块化、构建工具算法与编程题15%-20%手写函数、算法实现这个分布透露的另一个信息是笔试不是要你拿满分而是要你在有限时间内把基础分稳稳拿住。遇到不会的题先跳过把会做的做对这是笔试的第一生存法则。后面我会逐一拆解这些题目背后的知识点和答题策略。2. JavaScript基础题逐题拆解原型、闭包、this一个都不能含糊2.1 原型链题目从一道输出什么说起B卷里有这样一道典型的原型链题目大概是这个形式function Foo() { this.name foo; } Foo.prototype.getName function() { return this.name; }; var foo new Foo(); console.log(foo.getName()); console.log(foo.hasOwnProperty(name)); console.log(foo.hasOwnProperty(getName)); console.log(foo.__proto__ Foo.prototype);答案是依次输出foo、true、false、true。前三行代码都不难理解关键是第四行的判断。new Foo()创建出来的对象它的隐式原型__proto__指向构造函数的显式原型Foo.prototype这是JavaScript对象系统的核心规则。很多候选人在这里会犹豫——因为平时大家用class语法糖写代码写多了已经把原型机制抛在脑后了。这道题的深层考点其实是对new操作符完整流程的掌握。我在面试别人时经常问这个问题new到底干了什么完整答案是四步创建一个新对象并让这个对象的__proto__指向构造函数的prototype属性将构造函数的this绑定到这个新对象上执行构造函数体内的代码给新对象添加属性和方法如果构造函数返回了一个对象类型的值则返回该值否则返回新对象。当年B卷里还有一道扩展题问手写一个new的实现这两道题其实是同一件事。手写实现如下function myNew(constructor, ...args) { var obj Object.create(constructor.prototype); var result constructor.apply(obj, args); return (typeof result object result ! null) || typeof result function ? result : obj; }Object.create(constructor.prototype)这一步本质上就是完成第四行的原型连接。理解了这个脉络原型链相关的题目基本就能举一反三了。2.2 作用域与闭包笔试最爱出的“隐藏考点”B卷里关于作用域的题有一道让我印象很深它考察的是var与let在循环中的区别for (var i 0; i 3; i) { setTimeout(function() { console.log(i); }, 100); } for (let j 0; j 3; j) { setTimeout(function() { console.log(j); }, 100); }第一段输出3 3 3第二段输出0 1 2。这个考点太经典了但真正理解背后原理的人不多。var声明的i属于函数作用域整个循环共享同一个变量。循环结束后i变成了3定时器回调执行时读取到的自然都是3。而let声明带有块级作用域每次循环都会创建一个新的绑定所以定时器回调能捕获到每一次循环时的值。当年这道题的升级版是如果不用let怎么让第一段代码输出0 1 2答案是利用闭包或者立即执行函数for (var i 0; i 3; i) { (function(num) { setTimeout(function() { console.log(num); }, 100); })(i); }这里利用IIFE创建了一个新的函数作用域把每次的i值作为参数num传进去闭包捕获的是这个参数互不干扰。这道题同时考察了块级作用域和闭包两个核心概念属于一道题串联多个考点的典型设计。2.3 this指向问题生产环境里的真实麻烦this指向是B卷选择题里的高频题。题目给出一段代码让你判断this指向谁或者输出什么。我见过的最典型的考法是var obj { name: obj, method: function() { console.log(this.name); } }; var fn obj.method; fn();答案是输出undefined非严格模式下指向全局对象而不是obj。原因在于this的绑定规则是看调用时的执行上下文而不是定义时的上下文。fn()是普通函数调用this指向全局对象而全局对象上没有name属性。这里的规律总结成一句话就是谁调用this指向谁。但这句话在箭头函数面前就不成立了因为箭头函数不绑定this它会捕获定义时所在上下文的this值。B卷如果考到箭头函数一定要注意它在对象方法里的表现var obj { name: obj, method: () { console.log(this.name); } }; obj.method();这里输出依然是undefined因为箭头函数定义时它所在的上下文是全局作用域this指向全局对象。这与普通函数谁调用指向谁的规则完全不同。2.4 手写数组去重与深拷贝算法与语言特性的结合B卷的编程题里通常会有一道关于数组或者对象的操作题最常见的就是手写数组去重和深拷贝。数组去重看起来简单但考察的是你能否写出多种方案并能说明各自的优劣。基础方案是利用Setfunction unique(arr) { return [...new Set(arr)]; }这个方案代码量最少但有个问题Set的去重是基于SameValueZero算法的对于NaN和-0有一套特殊处理而在面试场景下这些不是重点重点是你得知道去重只能处理基本类型包含对象的数组用Set是无效的。如果要处理对象数组去重比如根据某个属性去重就需要结合Map或者reducefunction uniqueByKey(arr, key) { var map new Map(); return arr.filter(function(item) { if (!map.has(item[key])) { map.set(item[key], true); return true; } return false; }); }深拷贝那道题就更经典了。简单的JSON.parse(JSON.stringify(obj))只能处理可序列化的纯数据对象遇到undefined、函数、Date、RegExp、循环引用就会出问题。完整的深拷贝实现至少要处理这些边界场景这也是B卷里一道看似会做实际上写不完整的典型题目。3. 浏览器与网络缓存、渲染、跨域前端面试的三大硬菜3.1 HTTP缓存机制从一道网络题到生产配置实战B卷网络方向的高频题是HTTP缓存。当年考的是描述浏览器缓存的过程以及Cache-Control与Expires的区别。这道题现在依然是面试必问因为缓存策略直接影响页面性能和用户体验。HTTP缓存分为强缓存和协商缓存两层。强缓存是浏览器直接使用本地副本不发请求到服务器由Cache-Control或Expires控制协商缓存是浏览器携带缓存标识向服务器发起请求由服务器判断资源是否发生变更由Last-Modified/If-Modified-Since和ETag/If-None-Match控制。生产环境里最常见的配置是这样组合的Cache-Control: max-age31536000, immutable这个配置通常用于带哈希指纹的静态资源文件比如app.a1b2c3.js因为文件名变了就相当于新资源可以用一年期的强缓存。而HTML文件本身通常设置成Cache-Control: no-cacheno-cache的意思是每次使用前都要先验证资源是否新鲜如果服务器返回304继续用本地缓存如果返回200就更新缓存。很多候选人分不清no-cache和no-store前者是会使用缓存但要验证后者是根本不允许缓存。这个细微区别就是拉开差距的地方。3.2 浏览器渲染流程从URL输入到页面展示中间发生了什么这道题在B卷里以简答题形式出现属于必拿分的基础题但想答得完整、答得出彩还是需要系统的知识框架。完整的流程是输入URL后浏览器进程首先解析URL判断是搜索关键词还是合法链接如果是合法链接先查浏览器缓存Service Worker缓存、内存缓存、磁盘缓存缓存没有命中就发起网络请求经过DNS解析、TCP连接、TLS协商拿到HTML文档然后进入渲染阶段。渲染阶段可以细分为以下步骤解析HTML构建DOM树解析CSS构建CSSOM树将DOM树和CSSOM树合并成渲染树Render Tree布局Layout计算每个节点的几何位置绘制Paint将样式和位置信息绘制成屏幕上的实际像素合成Composite将不同图层合成为最终页面。这套流程在2019年是标准答案到了现在你还可以补充一个重要的细节浏览器在解析HTML时如果遇到script标签会暂停DOM解析先下载并执行脚本。下载脚本期间页面渲染会被阻塞这就是为什么推荐把脚本放在body底部或者给脚本加上defer/async属性。关于defer和async的区别B卷里也考过defer是延迟到HTML解析完毕后再执行多个defer脚本按顺序执行async是下载完成后立即执行不保证执行顺序。日常开发里模块化脚本推荐用defer独立无依赖的脚本比如统计代码可以用async。3.3 跨域问题JSONP、CORS与代理的取舍跨域是B卷选择题里必出现的考点也是前端开发日常场景里绕不开的环节。题目大概是问以下哪种方式可以解决跨域问题选项包括JSONP、CORS、postMessage、WebSocket等。JSONP的原理是利用script标签不受同源策略限制的特性动态创建script标签发起GET请求服务器返回一段JavaScript代码通常是函数调用从而实现跨域取数。它有一个明显局限只支持GET请求无法处理POST、PUT等其它方法。CORS跨域资源共享是更正规、更通用的方案。它需要服务器在响应头里设置Access-Control-Allow-Origin来声明允许哪些源访问。这里有个容易忽略的细节当请求包含自定义头或者使用非简单请求比如Content-Type为application/json时浏览器会先发送一个OPTIONS预检请求服务器要在预检请求里返回Access-Control-Allow-Methods和Access-Control-Allow-Headers来确认允许的操作。实际开发里前后端联调阶段更多用的是Vite或Webpack开发服务器上的代理配置。因为开发服务器与页面同源代理转发是服务端的转发不受浏览器同源策略限制。Vite里配置代理非常方便// vite.config.js export default { server: { proxy: { /api: { target: http://backend.example.com, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } }这三种方案各有用武之地老项目或第三方接口用JSONP自有后端服务用CORS本地开发调试用代理转发。理解每种方案的适用场景比单纯背方案列表要重要得多因为面试官追问的都是你看过实际场景下的坑吗。3.4 前端性能优化从B卷简答题到真实项目的优化清单B卷简答题里有一道列举前端性能优化手段这道题看起来开放其实有非常强的答题逻辑按不同阶段分类会显得很有条理。网络层的优化减少HTTP请求数合并文件、使用雪碧图、内联小体积资源、压缩传输体积Gzip/Brotli、图片压缩、合理利用缓存强缓存协商缓存、使用CDN加速静态资源分发。渲染层的优化CSS放头部、JS放底部、减少重排和重绘操作DOM时使用DocumentFragment批量操作、懒加载图片和路由组件、使用requestAnimationFrame处理动画。代码层的优化代码分割Code Splitting、Tree Shaking移除无用代码、使用虚拟列表处理长列表渲染、合理使用防抖和节流。这道题的提分关键是不要只罗列手段要能说出手段背后的原理指标。比如懒加载的深层原因是通过减少初始下载体积来缩短首屏时间First Contentful Paint代码分割是为了减少主Bundle体积、提升首屏可交互时间Time to Interactive。把优化手段和性能指标关联上答案质量会立刻上一个档次。4. CSS与布局盒模型、BFC、flex和居中问题4.1 盒模型与box-sizing一像素都不能差B卷里CSS部分的必考题是盒模型。当年的题目是标准盒模型和IE盒模型的区别是什么box-sizing属性取不同值时的宽度计算方式。标准盒模型中width属性只包含内容区content的宽度不包含padding和borderIE盒模型怪异盒模型中width包含了content、padding和border。box-sizing: border-box就是把元素设置为IE盒模型的计算方式。这道题在实际布局里的意义远超笔试本身。比如你要实现一个两栏布局左边固定200px右边自适应如果右边直接设置width: calc(100% - 200px)同时又有padding: 20px不用border-box时总宽度就会变成calc(100% - 200px) 40px从而溢出容器。这就是为什么现代CSS重置样式表reset里通常会有一条*, *::before, *::after { box-sizing: border-box; }4.2 BFC一个被低估的隐形高手BFC块级格式化上下文在B卷里以选择题形式出现考察的是它能够解决什么问题。BFC是一个独立的渲染区域内部元素的布局不会影响外部元素。触发BFC的条件包括html根元素、float值不为none、position为absolute或fixed、display为inline-block/flex/grid、overflow不为visible。BFC最常见的三个应用场景清除浮动父元素创建BFC后可以包含浮动子元素避免高度塌陷阻止元素被浮动元素覆盖兄弟元素创建BFC后不会与浮动元素重叠阻止外边距折叠同一个BFC内相邻元素的外边距会折叠而BFC之间不会。很多候选人知道BFC是什么但一到实际场景就忘了用。比如清除浮动现在很多人第一反应是给父元素加overflow: hidden但如果你理解BFC原理就会明白overflow: hidden能生效的原因正是它为父元素创建了一个新的BFC从而把浮动的子元素包了进去。4.3 flex布局与水平垂直居中手写频率最高的CSS题flex布局是B卷高频考点几乎三题必有一题涉及。其中最常见的就是水平垂直居中。最简单的flex居中方案是.parent { display: flex; justify-content: center; align-items: center; }但面试官通常会追问还有哪些方式实现水平垂直居中答案至少应该有三种绝对定位负margin适用于宽高固定的元素绝对定位transform: translate(-50%, -50%)不要求固定宽高display: grid配合place-items: center现代布局方案。绝对定位transform的方案有一个细节要注意如果父容器没有设置position: relative绝对定位的基准会变成最近的定位祖先元素甚至是根元素。笔试时如果给定了结构一定要先确认父元素的定位属性。B卷里关于flex还有一种考法让一个元素等分剩余空间并自动换行。这用的是flex-grow、flex-shrink和flex-basis的组合简写值flex: 1就等价于flex: 1 1 0%意思是基准尺寸为0空间不足时均匀压缩空间富余时均匀伸展。理解了这三个属性值的含义flex布局的绝大多数问题都能迎刃而解。5. 框架与工程化Vue、React和模块化的实战向考察5.1 Vue响应式原理2019年考的是Object.definePropertyB卷的框架题涉及Vue的响应式原理当时Vue 2.x还是主流答案自然围绕Object.defineProperty展开。核心逻辑是遍历data对象的所有属性通过Object.defineProperty将它们转成getter/setter组件实例在初始化时会执行一次依赖收集把用到的数据与对应的Watcher关联起来当数据发生变化时setter被触发通知依赖列表中的Watcher更新视图。原理本身不复杂但有几个边界问题是当年无数候选人踩过的坑也是面试官最爱的追问点新增属性不会触发视图更新因为Object.defineProperty只在初始化时对已存在的属性做了劫持。解决办法是使用Vue.set2.x或直接给对象重新赋值一个包含新属性的对象通过索引修改数组元素同样不会触发更新因为数组的索引监听没有像对象属性那样做响应式绑定。这也是为什么Vue 2.x的文档强调push、splice等方法才能触发视图更新。到了Vue 3时代这套原理被Proxy完全替代。如果你现在去面试谈到响应式时最好主动提及这个演进Proxy可以直接监听对象本身和数组索引的变化不需要像Vue 2那样做那么多补偿处理性能也更好因为它不需要递归遍历整个对象而是在访问属性时才进行依赖收集。5.2 React与Vue的对比这类题答的是取舍B卷里还有一道对比React和Vue的开放题。这种题没有唯一正确答案但考察的是你是否理解两种框架在核心设计上的取舍。可以从以下几个维度展开数据流两者都遵循单向数据流但Vue通过v-model提供了更便捷的表单数据双向绑定语法糖React则需要开发者自己管理value和onChange组件通信Vue有props、emit、provide/inject、event busReact有props、context和第三方状态管理库Redux、MobX、Zustand更新机制Vue通过依赖追踪精确知道哪些组件依赖了哪些数据可以实现更精确的更新React需要通过shouldComponentUpdate、React.memo等手段来手动优化不必要的渲染学习曲线Vue的模板语法和API设计对新手更友好React的JSX和函数式风格更强调JavaScript基础灵活度高但需要更好的工程素养。回答这类对比题的加分策略是不只讲区别要给出选型建议。比如如果你所在团队的后端同学很多Vue的模板更接近HTML上手成本更低如果团队强调跨端和生态统一React生态的React Native、React Three Fiber等扩展会更占优势。5.3 模块化与构建工具从CommonJS到ES Module工程化方向B卷考了模块化规范的区别CommonJS、AMD和ES Module。到现在ES Module已经是标准但CommonJS在Node.js环境里依然大量存在理解它们的差异对做跨端项目至关重要。CommonJS是同步加载模块模块代码在运行时执行require和module.exports是Node.js的模块方案ES Module是静态加载import和export是JavaScritpt标准编译时就能确定模块依赖关系因此可以做Tree Shaking摇树优化——将没有被实际引用的导出代码从最终打包产物中移除。构建工具方面2019年的答案是Webpack。关键词包括entry、output、loader、plugin、code splitting、tree shaking。现在的面试如果考到工程化出题人会更倾向让你说Vite的底层原理开发环境利用浏览器原生ES Module能力使用esbuild做预构建依赖启动速度比Webpack快好几个数量级生产环境默认打包工具是Rollup。如果你准备的是2024年以后的前端面试Vite相关的问题几乎必考。6. 编程题实战思路两道B卷原型的完整解题过程6.1 实现Promise.all从能用到可靠的完整演进B卷编程题里有一道很经典的手写Promise.all。这道题完美地串联了Promise基础、错误处理和边界条件是一道含金量很高的题。下面是逐步优化的完整实现过程。第一版先实现一个能用的版本function promiseAll(promises) { return new Promise((resolve, reject) { const results []; let count 0; promises.forEach((promise, index) { Promise.resolve(promise).then((value) { results[index] value; count; if (count promises.length) { resolve(results); } }).catch((error) { reject(error); }); }); }); }这个版本实现了核心逻辑按传入顺序收集结果所有Promise都完成后resolve。但有个小问题如果传入的数组为空count promises.length的判断永远不成立Promise永远不会resolve。修正这个边界条件function promiseAll(promises) { return new Promise((resolve, reject) { if (promises.length 0) { resolve([]); return; } const results []; let count 0; promises.forEach((promise, index) { Promise.resolve(promise).then((value) { results[index] value; count; if (count promises.length) { resolve(results); } }).catch((error) { reject(error); }); }); }); }还有一个面试时的加分项是说明Promise.resolve(promise)这一步的意义。它把普通值、thenable对象、以及真正的Promise统一包装成Promise保证了代码的健壮性——即使调用方传入的不是Promise也能正常处理。这个细节很多候选人想不到但实际开发中非常有用。6.2 防抖与节流看似简单坑却不少B卷编程题的另一道常客是实现防抖debounce和节流throttle。这两个函数是性能优化的基础工具也是高频手写题。防抖的核心思想是事件触发后等待一段时间如果等待期间再次触发则重新计时。适用于搜索框输入、窗口resize等高频触发、只关心最后一次的场景。function debounce(fn, delay) { let timer null; return function(...args) { if (timer) { clearTimeout(timer); } timer setTimeout(() { fn.apply(this, args); }, delay); }; }这个实现有个容易忽略的细节fn.apply(this, args)里的this必须从返回的函数里继承而不能直接写成fn(...args)。因为如果防抖函数被用作对象方法this必须指向那个对象否则会出现this丢失的问题。节流的核心思想是控制事件执行的频率在一定时间内只执行一次。适用于滚动事件、resize、mousemove等高频触发场景。function throttle(fn, interval) { let lastTime 0; return function(...args) { const now Date.now(); if (now - lastTime interval) { fn.apply(this, args); lastTime now; } }; }这道题在当年B卷里的提问方式是给出一段连续触发的事件处理代码要求用防抖或节流优化并解释两者的区别和适用场景。答题时一定要先说清楚防抖适合只关心结尾的场景比如输入框搜索节流适合控制执行间隔的场景比如滚动时更新位置。这个前置说明比代码本身更能体现理解深度。7. 复盘与应对策略从B卷反推面试准备方向7.1 按知识点权重分配复习时间复盘小米这套B卷可以很清楚地看到前端笔试的通盘逻辑基础扎实比涉猎广泛更重要理解原理比背诵答案更重要。如果你的复习时间有限我建议按以下优先级分配精力。JavaScript语言基础永远排第一优先级。原型链、闭包、作用域、this绑定、事件循环、Promise相关的手写题这些是笔试出现频率最高、同时也是面试环节追问最多的内容。建议把JavaScript高级程序设计的核心章节反复读透每读完一章就手写一遍相关代码。浏览器、网络和性能优化排第二优先级。缓存机制、渲染流程、跨域、从URL输入到页面展示的完整过程是简答题的主角。这些知识点非常适合用自己的话复述一遍如果你能不看资料完整写出来说明已经掌握得差不多了。框架与工程化排第三优先级。无需把源码全读一遍但至少要能画出响应式原理的流程图、说出diff算法的大致策略、知道模块化和构建工具的核心概念。CSS和算法排第四优先级。CSS重点掌握盒模型、flex布局、BFC、CSS Grid算法方面不需要啃高难度题但常见的数组去重、深拷贝、防抖节流、Promise.all、简单的排序算法一定要能独立手写。7.2 笔试时的高效答题策略实话说大厂笔试的时间通常是不够用的。我当时参加小米笔试时编程题部分花了太多时间在前一题上导致最后一题草草收场。复盘以后我总结了一套自己的答题策略分享出来供参考。第一拿到试卷先花两分钟通读所有题目给题目难度分个级。一眼就会的题直接做这类题属于稳拿分项必须快速解决需要思考但方向上熟悉的中等题标记好待做看题目都看不懂的难题直接进入蒙跳过模式。第二编程题优先写能跑通的核心逻辑不必追求完美。比如手写Promise.all第一版能正确resolve结果就已经拿了大半分数边界条件处理属于加分项。很多候选人写完第一版觉得不够完美反复删改结果反而浪费了后面的时间。第三简答题用分条关键词的方式作答。阅卷人看的通常是关键词给分你把要点列出来比写一大段话更高效。比如HTTP缓存题分条列出强缓存、协商缓存、相关请求头和响应头比写一篇小作文分数高得多。7.3 笔试之后的进阶方向B卷只是第一道关卡笔试通过后还有技术面、HR面技术面里必然会追问笔试中涉及的原理。所以笔试结束不是复习终点反而是查漏补缺的起点。我现在回头看2019年秋招的前端面试要求相比现在低了不少。当年会手写Promise.all、能画出Vue响应式流程图已经超过了大多数候选人。现在的市场环境要求更高源码级别的理解、工程化实战经验、性能优化的真实案例、甚至跨端开发能力都成了默认配置。但底层逻辑没有变——真正吃透语言机制和运行原理的人无论框架怎么迭代都能快速跟上。如果你正在准备面试我建议你把每道笔试真题当做一个知识树的分支做一道题的同时把相关原理、边界情况、生产环境应用都顺一遍。这种题目驱动的复习方式是我见过效率最高的前端面试准备方法。
返回列表