小米秋招前端笔试复盘:JS核心机制与实战考点全解析 📅 发布时间:2026/8/29 23:35:52 👁 浏览次数: 又是一年秋招季好几个学弟学妹跑来问我小米前端笔试怎么准备。我翻了一下当年整理的“2022年小米秋招笔试-前端-卷1”的复盘笔记发现这套题虽然过去几年了但它考察的点很典型JavaScript基础、浏览器原理、框架理解、工程化意识外加一两道让很多人措手不及的手写编程题。这篇文章我不打算给你罗列网上的“标准答案”而是把这套卷子背后的命题逻辑、高频考点和答题策略拆开讲顺便把容易踩的坑也一并说清楚。不管是准备大厂秋招还是想巩固前端基本功这篇都能帮你把复习路线理得更顺。1.1 题型分布与分值占比先还原一下2022年小米秋招前端卷1的整体结构。就我手头收集到的信息这套卷子大致分为四类题目单选/多选、填空题、简答题、编程题。选择题通常占30%左右重点考察语言基础和网络基础填空题占比不高但特别喜欢抠细节比如闭包输出结果、事件循环打印顺序、CSS选择器优先级这类一眼看不出答案的题目简答题多为“描述XX原理”或“写出XX实现方式”一般在20%左右剩下的编程题是大头通常有两到三道一道纯算法一道偏业务场景还有一道是手写工具函数。我记得当年拿到卷子的第一反应是题目数量并没有想象中那么多但每道题的陷阱密度很高。选择题里有很多“二选一”的迷惑项比如和的差别、let和var在循环中的表现、Array.prototype.map与forEach的区别这些知识点平时写代码好像都懂但放到题目里就不一定了。所以复习的时候不能只记结论得结合执行过程去推演。1.2 命题趋势与考察逻辑小米的笔试风格在大厂里算是比较“务实”的他们不会问太多偏门概念更倾向于把基础知识和实际开发场景揉在一起考。举个例子他们不大会直接问“什么是闭包”而是给你一段代码让你写出打印结果或者问你“这段代码在循环结束之后为什么拿不到正确的索引值”。这种题目背后的逻辑是要求你对执行上下文、作用域链、变量提升有真正的理解而不是背熟定义。我复盘后发现整套卷子至少有一半的题目是围绕“事件循环异步时序”和“对象/数组操作”展开的。这也不难理解前端日常开发最多遇到的就是异步请求、状态更新、数据转换这些能力在笔试里是最容易量化的。所以如果你的复习时间有限我建议优先搞定 JavaScript 核心、浏览器渲染机制、HTTP 缓存和 Vue/React 的响应式原理这几块几乎是大厂前端的“必考清单”。1.3 我的答题顺序建议笔试时间一般紧凑选择题和填空题卡住了就赶紧跳过不要死磕。我的习惯是先把所有编程题的题目都看一遍心里有个底再回到前面从选择题开始写。因为编程题往往需要冷静思考放在最后如果时间不足会很被动。另外一个技巧是编程题即便写不出完整代码也要把核心思路写成注释或伪代码阅卷时能看到你的思考过程有时候能拿到步骤分。我见过不少同学在选择题上花了20分钟去纠结一道两分题最后编程题来不及写。笔试的时间分配其实比正确率更影响最终结果目标应该是“在有限时间内拿到最多分”而不是“做对所有题”。2. JavaScript 核心考点解析2.1 原型链、this指向与作用域JS部分是卷子的绝对核心原型链和 this 指向几乎是必考内容。其实这两块放在一起考特别经典因为this的指向在普通函数、箭头函数、对象方法、构造函数里完全不同背后牵扯到作用域链和执行上下文。我整理了一道类似的原型链题目function Parent() { this.name parent; } Parent.prototype.say function () { console.log(this.name); }; function Child() { this.name child; } Child.prototype new Parent(); const child new Child(); child.say();问最后输出什么答案显然是child。但很多人会误以为say是从 Parent 原型上拿到的所以应该输出parent。这里的关键是方法定义在原型上但this永远指向调用它的对象也就是child。这就是典型的“原型链机制与 this 绑定分离”的考点。再看一个常见变体const obj { name: obj, arrow: () console.log(this.name), normal: function () { console.log(this.name); } };箭头函数在定义时就绑定了外层this普通函数则根据调用方式动态绑定。如果这个obj在全局环境下定义this指向window所以obj.arrow()输出的是全局name而不是obj。这类题目考察的其实是两个机制的交叉点比较容易踩坑。复习建议把call/apply/bind的实现原理过一遍再手写一个new的实现这样对原型链和 this 的理解会深刻很多。不要只看别人写的代码一定要自己亲手动笔或打字跑一遍。2.2 异步与事件循环事件循环是前端笔试的高频困难点小米的卷子通常会给出一段混合了setTimeout、Promise、async/await的代码让你写出打印顺序。要解答这类题目必须把“调用栈、宏任务、微任务”三者的关系理清楚。来看一道经典变式async function test() { console.log(1); await Promise.resolve(); console.log(2); } setTimeout(() console.log(3), 0); test(); console.log(4);正确的输出顺序是1、4、2、3。原因是test()是同步调用先打印1await后面的部分会被包装成微任务所以先继续执行同步代码打印4之后微任务队列里的console.log(2)先执行宏任务setTimeout最后执行打印3。这里要注意async/await的本质await相当于一个Promise.then()它会中断当前函数继续执行直到微任务队列被处理。很多同学错误地把await当成同步阻塞导致顺序判断出错。还有一道经常出现的陷阱题for (var i 0; i 5; i) { setTimeout(() console.log(i), 0); }输出是5 5 5 5 5因为var声明的i是函数作用域所有定时器回调共享同一个变量循环结束时i已经变成5。如果把var改成let输出就是0、1、2、3、4因为let在每次循环中会创建独立的块级作用域副本。这类题目既考事件循环又考作用域复习时可以把两个知识点结合起来理解。2.3 手写实现防抖、节流、深拷贝手写代码是笔试中的“试金石”比选择题更考验真实水平。小米的卷子常常会要求实现防抖、节流、深拷贝这类工具函数题面一般很短但想拿满分并不容易。先说防抖。它的核心是“在事件触发后等待一段时间如果期间再次触发就重新计时”function debounce(fn, wait) { let timer null; return function (...args) { if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, wait); }; }注意这里必须用fn.apply(this, args)绑定调用时的this否则在某些场景里this会丢失。很多同学就栽在这上面。节流的核心则是“在单位时间内只执行一次”常见的有时间戳版和定时器版function throttle(fn, interval) { let lastTime 0; return function (...args) { const now Date.now(); if (now - lastTime interval) { lastTime now; fn.apply(this, args); } }; }实际开发中防抖适合搜索输入、窗口 resize节流适合滚动监听、按钮点击。笔试如果要求“说明使用场景”你要能结合业务讲清楚不能只说实现。深拷贝是另一个高频手写题。很多人背过JSON.parse(JSON.stringify())但它处理不了undefined、Symbol、函数、循环引用也会丢失日期对象原型。所以笔试里要求手写深拷贝时至少要考虑基础类型、对象、数组和循环引用。一个简化但完整的版本可以是function deepClone(target, map new WeakMap()) { if (typeof target ! object || target null) { return target; } if (map.has(target)) return map.get(target); const result Array.isArray(target) ? [] : {}; map.set(target, result); Object.keys(target).forEach((key) { result[key] deepClone(target[key], map); }); return result; }这个版本用WeakMap解决循环引用问题而且用Object.keys遍历可枚举属性算是一个比较完整的面试答案。如果写代码时能顺手把 Symbol 也考虑进去印象分会更高但面试中先把核心逻辑写对更重要。3. 浏览器、网络与前端性能3.1 浏览器渲染流程与回流重绘浏览器相关的题目在前端笔试中比例不低小米这套卷子也不例外。基础题一般会问“从输入 URL 到页面展示发生了什么”这个流程要能背得清楚DNS 解析、TCP 连接、HTTP 请求、服务器响应、浏览器解析 HTML/CSS/JS、构建 DOM 树和 CSSOM 树、合成渲染树、布局绘制。但光会背不够还得理解每一步背后的原理和可能出现的性能问题。更细的考点是“回流”和“重绘”。回流Reflow是当元素的几何属性发生变化时浏览器需要重新计算元素位置和尺寸重绘Repaint是元素外观变化但几何属性不变。两者都会消耗性能回流的代价更高。笔试常问“哪些操作会触发回流”一般包括改变窗口大小、修改元素尺寸/位置、增删 DOM 节点、调用getBoundingClientRect()等强制同步布局的属性。优化手段包括批量修改 DOM、使用transform代替top/left、用DocumentFragment减少回流次数等。我记得卷子里有一道简答题问“如何减少重排重绘”我第一时间想到的是把多次样式修改合并成一次 class 切换另外用requestAnimationFrame来控制动画帧。这些点单独看都不难但答题时要把原理和方案对应起来讲阅卷人会觉得你真懂。3.2 缓存机制与跨域策略网络方面HTTP 缓存基本属于必考内容。要分清强缓存和协商缓存以及它们对应的字段。强缓存通过Cache-Control相对时间和Expires绝对时间控制如果命中强缓存浏览器直接使用本地副本不发请求协商缓存则通过Last-Modified/If-Modified-Since或ETag/If-None-Match向服务器验证资源是否新鲜服务器返回304或者重新返回资源。缓存类型主要字段判断方式请求是否发出强缓存Cache-Control / Expires判断时间是否过期否协商缓存Last-Modified / ETag请求头携带上次标识服务器判断是跨域问题也是高频考点笔试里常见的问法是“有哪些跨域方案”。答案通常有JSONP、CORS、代理服务器、postMessage、WebSocket、document.domain 等。其中 CORS 是标准方案重点是理解简单请求和预检请求preflight的区别JSONP 只能支持 GET原理是动态创建 script 标签开发环境常用 webpack-dev-server 或 Vite 的 proxy 代理。实际项目里我把 CORS 和代理结合使用既能解决跨域又能处理登录态 Cookie 的传递。注意答题时不要只列名字要说明每个方案的适用场景和局限。比如 CORS 需要后端配合设置响应头JSONP 有安全性问题且只支持 GET代理只适用于开发环境。能把优缺点讲清楚这道题就能拿高分。3.3 性能优化综合题这类题基本是简答题但要是只写“压缩图片、懒加载”这种白话得分不会高。需要从几个维度系统化回答加载性能、渲染性能、运行性能。加载性能的核心是减少资源体积和请求次数。具体手段包括代码分割、Tree Shaking、图片格式转换WebP/AVIF、开启 gzip 压缩、CDN 加速、HTTP/2 多路复用。渲染性能包括减少 DOM 层级、避免同步强制布局、优化 CSS 选择器、恰当使用content-visibility等。运行性能则包括事件节流防抖、避免内存泄漏、合理使用 Web Worker 处理复杂计算。有一道印象深刻的题是“一个表格在数据量很大的情况下如何保证流畅度”。参考答案可以从虚拟滚动入手只渲染可视区域的行也可以做数据分页或者时间分片。这道题很考验前端性能优化经验不是背八股能cover住的。平时在项目中偶尔会遇到大数据列表的卡顿我一般会用虚拟滚动库但笔试中如果能自己写出虚拟滚动的基本思路会加分不少。4. 框架与工程化实战4.1 Vue 响应式原理与依赖收集小米笔试对 Vue 的关注度历来很高基本会问“Vue 2 和 Vue 3 的响应式分别怎么实现”。这道题一方面看你对框架源码的理解另一方面也能看出你是否在关注新版本。Vue 2 通过Object.defineProperty拦截对象的属性读写在get中收集依赖Watcher在set中触发更新。它有三个明显的痛点无法监听对象新增/删除属性需要使用Vue.set/$set对数组的索引和 length 变化无法直接监听初始化时需要递归劫持所有属性性能有损耗。Vue 3 改用Proxy代理整个对象天然可以拦截属性新增、删除、数组索引变化等操作而且只在读取时才会递归代理深层对象性能更好。依赖收集使用WeakMap管理 target - Mapkey - Set(effect)的结构。笔试中如果让你手写一个简易响应式面试官希望看到的是function reactive(obj) { return new Proxy(obj, { get(target, key, receiver) { // 依赖收集 return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const result Reflect.set(target, key, value, receiver); // 触发更新 return result; } }); }真正的难点不在代码本身而是你能不能讲清楚Reflect.get/Reflect.set和普通target[key]的区别。我喜欢用一个生活化类比Proxy 就像是给对象装了一个智能门卫每次有人读值或写值都会经过这个门卫门卫可以记录“谁在读”也可以通知“关注这个值的人”而 Vue 3 的响应式系统就是靠这套机制工作的。4.2 React 渲染机制与 diff 算法如果卷子里出现了 React 题一般会问“setState 之后发生了什么”或者“虚拟 DOM 和 diff 的原理”。这类题目需要把握核心链路调用setState后将本次更新加入队列React 根据调度策略决定何时重新渲染然后生成新的虚拟 DOM 树与旧树进行 diff最后只把差异部分更新到真实 DOM。diff 算法在笔试中不必写出完整代码但需要知道几个关键策略从根节点开始逐层比较只在同一层级进行比较不跨层级比较。两个不同类型的元素会产生不同的树直接替换整个子树。通过 key 属性标识列表节点尽可能复用已有节点。当对列表进行插入、删除、排序时key 的稳定性非常重要不要用 index 作为 key否则会导致状态错乱。解释 diff 时可以用一个类比你维护了两份购物清单一份是上一次的一份是这次新增的diff 算法就是帮你找出“有哪些菜需要买、哪些菜可以留着”避免把整个厨房重装一遍。这样理解“最小更新”就很容易。React 部分还可能考察useEffect的依赖项机制、受控组件与非受控组件、HOC 和自定义 Hook 的区别。我觉得与其死记硬背不如自己动手写一个小组件把useEffect的清理函数和依赖项变化分别打印出来观察执行时机笔试遇到相关题目会从容很多。4.3 工程化模块化、Webpack/Vite 与 Tree Shaking工程化题在前端笔试中的地位逐年提高。2022年的卷子里就有一道问 Webpack 构建流程的简答题。核心流程是初始化参数从配置文件读取并合并参数。编译阶段通过入口文件递归解析依赖生成模块。通过 Loader 转换非 JS 模块比如让 webpack 能处理 CSS、图片、字体。通过 Plugin 扩展功能比如 HtmlWebpackPlugin、MiniCssExtractPlugin。输出阶段根据依赖关系生成 chunk输出到指定目录。这里容易混淆的是 Loader 和 Plugin 的区别。我习惯这样记Loader 是“翻译官”负责把不同类型的文件转成 JS 模块Plugin 是“项目经理”负责从打包到输出的生命周期里做各种定制比如压缩、分包、注入环境变量。Vite 和 Webpack 的对比也经常考。Vite 在开发阶段利用浏览器原生 ES Module按需加载启动速度比 Webpack 快很多生产构建则使用 Rollup。Tree Shaking 是 Rollup 和 Webpack 都支持的特性它通过静态分析 ES Module 的导入导出把没被使用的代码在构建时移除。要启用 Tree Shaking需要确保代码是 ES Module 写法不要写 CommonJS 的动态 require否则摇树会失效。工程化题最容易出现“背了概念但不会用”的情况。我会在复习时拿一个真实项目跑npm run build看看最终输出的 chunk 大小、依赖图、构建时间再尝试配置splitChunks和按需加载这样遇到“如何优化打包体积”的题目时你能结合实际数据回答而不是空谈理论。5. 编程题实战与代码赏析5.1 高频基础题数组去重与扁平化编程题里最基础但最容易出错的就是数组去重、扁平化这类“一看就会一写就错”的题目。2022年的卷子里有一道数组去重题面不限定方法但所有解法都需要考虑NaN的判断和对象引用问题。数组去重的经典解法有Set、reduce、双重循环。如果问“哪种方法最优”要分情况讨论。简单场景下[...new Set(arr)]最短但它基于 SameValueZero 规则NaN会被正确去重如果数组元素包含对象Set判断的是引用地址两个内容相同但引用不同的对象不会被去重这时需要自定义去重 key。数组扁平化的考点更多常见的有递归、flat()、reduce concat。如果你在笔试中写arr.flat(Infinity)没问题但面试官可能会追问“实现flat的思路”。一个递归版本可以写成function flatten(arr) { return arr.reduce((acc, val) { return acc.concat(Array.isArray(val) ? flatten(val) : val); }, []); }如果要求处理“嵌套深度限制”可以在参数里加一个 depth。做题时记得考虑空数组、多层嵌套、非数组数据类型这些边界条件往往就是测试用例的隐藏点。5.2 手写 Promise.all 与 LRU 缓存编程题里出现“手写 Promise.all”的概率很高。核心思路是接收一个 Promise 数组遍历执行把结果按顺序存到数组等待所有 Promise 结束后 resolve一旦有 reject 就立刻 reject。function promiseAll(promises) { return new Promise((resolve, reject) { const results []; let count 0; if (promises.length 0) { resolve(results); } promises.forEach((promise, index) { Promise.resolve(promise).then((value) { results[index] value; count; if (count promises.length) { resolve(results); } }, reject); }); }); }这里要注意几个细节一是用Promise.resolve包一层兼容非 Promise 参数二是结果数组按索引赋值不能用push否则执行结果顺序会乱三是在 then 回调里判断完成数量。代码虽然短但每一行都有含义能完整写对的人不多。LRU 缓存是近年很常见的算法题考察点是“最近最少使用”策略。可以用 Map 实现因为 Map 会保留键值对的插入顺序每次访问或写入时先删除旧纪录再重新写入这样最近访问过的 key 会排到最后超出容量时删除最前面的 key 即可。class LRUCache { constructor(capacity) { this.capacity capacity; this.map new Map(); } get(key) { if (!this.map.has(key)) return -1; const value this.map.get(key); this.map.delete(key); this.map.set(key, value); return value; } put(key, value) { if (this.map.has(key)) this.map.delete(key); this.map.set(key, value); if (this.map.size this.capacity) { const oldestKey this.map.keys().next().value; this.map.delete(oldestKey); } } }这个解法既简洁又效率高而且很符合前端工程“可读性优先”的风格。笔试中遇到类似的题面试官更看重你能否在有限时间内写出可运行、边界正确的代码而不是追求炫技。5.3 答题时的边界处理技巧编程题最容易丢分的不是算法本身而是边界条件。我在做这套卷子时发现不少同学代码主体写对了但遇到null、undefined、空数组、极大数字时直接报错。分享几个实用的检查习惯输入为null或undefined时先判空再处理。处理数组时考虑长度为 0 或 1 的情况。字符串处理注意大小写、空字符、Unicode。访问对象属性前先判断属性是否存在。使用递归时设置退出条件防止栈溢出。另外笔试环境通常需要你自己定义函数签名这时一眼就要看出题目要求的是“返回新数组”还是“原地修改”。这两者的实现方式完全不同弄反了代码就直接跑不通。读题时把关键词圈出来比如“原地修改”意味着不能返回新数组“深拷贝”意味着要考虑对象嵌套。6. 常见问题与避坑指南6.1 明明会做却丢分的细节复盘这套卷子时我发现有几个失分点特别可惜。第一选择题里经常出现“下列哪些说法是正确的”很多人只选了第一个感觉对的项目漏了多选。答案通常是两个或以上不要单凭直觉要逐项判断。第二简答题只写结论不写原因。比如“为什么 Vue 3 比 Vue 2 快”如果只写“因为用了 Proxy”可能只能拿一半分你得把 Proxy 的优势写清楚比如懒代理、支持新增属性、数组索引拦截。第三编程题只写核心逻辑不处理异常。比如手写 deepClone必须面对循环引用的问题如果没处理就是功能不完整。哪怕你加两行注释说明“这里用 WeakMap 避免循环引用”都能让阅卷人觉得你考虑全面。还有一个细节是命名问题。笔试代码里如果变量名写得乱七八糟比如a、b、tmp即使代码逻辑对也可能影响整体印象分。尽量用有意义的英文变量名比如result、index、cacheMap这是职业素养的体现。6.2 复习重点与资料推荐如果你的目标是小米或者其他大厂前端秋招我的建议是把复习资源按优先级排列第一优先级是 MDN 的 JavaScript 教程尤其是原型、作用域、事件循环、Promise 这几个章节。第二优先级是 Vue/React 官方文档先把响应式原理、组件通信、生命周期彻底吃透。第三优先级是刷算法题重点做数组、字符串、链表、栈队列相关的题目以及手写实现“call/apply/bind、new、深拷贝、防抖节流、Promise”这些常见工具。不要迷信零散的“面试题合集”。很多八股文答案看一眼就觉得自己会了实际上手写就露馅。我自己的方法是准备一个在线代码编辑器每天花一小时把当天看过的知识点用代码复现一遍遇到报错再分析原因这样留下的记忆远比背诵深刻。我从不反对背“八股文”但八股文应该是最后阶段查漏补缺的“速查表”而不是复习的全部。它可以帮助你快速覆盖高频考点但如果你不理解原理换一种问法就会露怯笔试中尤其明显。6.3 关于“八股文”的辩证看法现在的技术社区对“八股文”的批评很多甚至有同学觉得刷题没用。但我的个人经验是对于刚毕业的学生来说没有太多工程项目可写笔试就是筛选基本功最有效的方式。你不能只说“会使用 Vue”你得能说清楚 Vue 的响应式是怎么收集依赖的你不能只说“项目有性能问题”你得能从浏览器渲染原理下手分析。归根到底八股文考察的是“底层逻辑系统思维”。比如“前端安全”这道题能牵扯出 XSS、CSRF、CSP、输入过滤、Cookie 安全属性等多个知识点。如果你只是背了几条 XSS 定义很难应对这种交叉问题。所以复习的时候可以尝试由一个主题向外扩展画一张自己的知识脑图把零散的点串成线再用代码验证一遍这样面试官问到任何角度你都有话可说。我有一位朋友就是在刷小米这套笔试题时专门把“事件循环”相关的所有变种题都整理了一遍从 setInterval 嵌套到 async/await 和微任务组合再到 Node.js 环境下的机制差异。后来真上了笔试看到同一类型的题目基本就是秒答。这种“把一个点吃透”的方法远比盲目刷一百道零散题有效。如果你正在准备秋招我建议你也找一个自己最薄弱的知识点比如“事件循环”或者“浏览器缓存”花几天时间把它彻底搞懂再回过头做这套复盘笔记。等你把一类高频题吃透之后会发现很多题目看起来不一样但内核都是相似的。到时候再上笔试心态会稳很多。