行业资讯
📅 2026/8/29 23:40:55
小米前端笔试复盘:基础扎实是关键,手写题与滑动窗口经验分享
每年秋招的节奏都像打仗小米这轮前端岗笔试我是在九月中旬收到的通知。说实话看到“第二批笔试”这几个字的时候反而比第一批更紧张——因为这意味着网上已经有很多人讨论过题型和难度了你要是没刷到这些信息等于裸考。我属于那种考前一定要把信息都摸透的类型所以这篇复盘我会把“怎么准备”、“考什么”、“怎么答”几个层面串起来写既记录我的真实经历也尽量说点对后来人有用的东西。一个值得先说清楚的结论小米前端岗的笔试重点不在算法难不难而在基础扎不扎实。它不像有些大厂上来就是hard题劝退但选择题覆盖面极广手写题又非常考验边界意识整体风格是“你好像都见过但一写就漏”。这篇文章适合正在准备秋招春招的前端同学也适合想了解大厂校招笔试真实节奏的朋友参考。1. 笔试前一周确认形式、准备环境、划定复习范围1.1 第一批考完后的信息收集我是第二批笔试时间排在工作日的晚上和第一批间隔了四天。这四天非常关键因为第一批考完当晚牛客、脉脉、小红书就会有人发帖子。我不是鼓励大家去找原题而是要通过这些讨论判断三件事题量、题型分布、编程题用不用核心代码模式。我当时收集到的信息归纳下来是这样的选择题约二十道涵盖JS基础、CSS、浏览器原理、网络协议、少量框架题编程题两道一道偏手写实现一道偏算法总时长一百分钟左右。这个信息帮我做了两个决定第一复习重点从“刷难题”转向“过基础”第二提前在牛客上练了几套模拟题适应平台那套代码编辑器的手感。说实话如果你连考试系统都没用过直接在正式笔试里花十分钟搞明白答题界面怎么切题、代码怎么运行那太亏了。1.2 考试平台与环境细节小米这批笔试用的是牛客的在线笔试系统。这类系统有几个共同特点选择题可以标记回头再看编程题只能本地调试后粘贴提交或者直接在网页编辑器里写。我建议编程题直接在网页编辑器里写因为本地编辑器有代码提示、有习惯的快捷键一旦复制粘贴出了格式问题反而不容易发现。另外一个细节是摄像头监控。笔试要求全程开启摄像头环境要安静、光线不能太暗桌面不能有笔和纸之外的东西。我当时把桌面清理得很干净连水杯都放地上了省的万一误判就麻烦。还有一点容易被忽略提前测网速别考试中途断线。我用的有线网络把笔记本充电器也插好了因为笔试和面试不一样笔试一断线系统可能直接判定交卷申诉流程极其折腾。1.3 复习范围怎么划一周时间不可能面面俱到我的策略是把高频考点列出来按“必看、了解、放弃”三档切分。必看的内容包括JavaScript事件循环、闭包、原型链、this指向、异步方案演进CSS三栏布局、flex与grid、BFC、层叠上下文HTTP缓存机制、跨域方案、浏览器渲染流程React Hooks使用规则、Vue响应式原理的基本描述。了解的内容包括TypeScript的泛型和工具类型、Webpack的loader和plugin区别、前端工程化常见概念。放弃的内容包括复杂的CSS动画、Canvas底层API、Node.js服务端细节。很多人准备校招笔试喜欢直接刷LeetCode但前端岗的笔试和纯后端算法岗不一样选择题占比高考得杂只刷题不复习八股文等于只练了半条腿。我当时每天把时间切成两块白天刷三到四道LeetCode热题加看手写题源码晚上集中过两到三个前端知识点画成笔记。这套节奏比较适合笔试周期短的情况。2. 选择题里的高频考点哪些八股文值得背哪些可以放弃2.1 JavaScript语言特性考点小米这批选择题里JavaScript部分大概占了百分之四十。具体来说闭包和this指向几乎必考事件循环至少出现两到三题原型链和new的实现逻辑也有一道。举个例子关于this指向的题一般长这样给一段代码里面有普通函数、箭头函数、对象方法问最终输出结果。这类题看起来很基础但正确率其实不高因为箭头函数的this是在定义时确定的普通函数的this要看调用方式再加上严格模式和非严格模式的差异四个选项各有道理。我复习时会拿一张A4纸把所有调用方式列出来直接调用、对象方法调用、call/apply调用、new调用、回调函数调用每种情况对应什么this写上两遍就记住了。事件循环的题则是另一个重灾区。它通常会混合setTimeout、Promise、async/await、微任务、宏任务还要考虑代码的执行顺序。做这类题我有一个笨办法先画一条时间轴把宏任务按出现顺序排好再在宏任务里面标注微任务队列。把Node环境下的process.nextTick和浏览器环境的queueMicrotask区别也理一下虽然笔试不一定考但面试一定会问。原型链的考点主要集中在构造函数的prototype、实例的__proto__、以及Object.create的用法。我记得有一道题是给出一个继承关系问某个属性在哪里能找到属于典型的“你懂原型链就秒选、不懂就全靠蒙”的题。建议把下面的代码自己在脑子里过一遍过不了就动手写function Parent() { this.name parent; } Parent.prototype.say function() { console.log(this.name); }; function Child() { this.age 18; } Child.prototype new Parent(); Child.prototype.constructor Child;这样设置的继承Child实例可以调用say方法并且child.constructor指向Child而不是Parent。这几个结论连在一起比死记硬背强得多。2.2 浏览器、网络与CSS浏览器和网络的部分占比也不低。HTTP缓存是必考项强缓存和协商缓存的字段要背熟Cache-Control的max-age、no-cache、no-store分别干什么ETag和Last-Modified的优先级谁高304出现的场景是什么。跨域问题也考了一道选项大概是从“CORS、JSONP、postMessage、WebSocket”里挑一个满足某场景的方案。我记得还考了cookie和localStorage的区别、浏览器从输入URL到页面渲染的整个过程。CSS部分三栏布局那类题换了个外壳出现。不是直接让你写布局而是给一段flex代码问最终效果。flex的flex-grow、flex-shrink、flex-basis组合起来确实容易迷糊建议复习的时候记三条规则flex-basis定基准尺寸、flex-grow管放大比例、flex-shrink管缩小比例缺省值分别是auto、0、1。BFC也考了问的是哪几种方式能形成BFCoverflow:hidden、display:flow-root、position:absolute这些都要能选出来。选择题的特点是答案相对固定不需要写理由但容错率低。每道题大概值两到三分错五道就等于丢掉了一道编程题的分数。我当时给自己定的目标是选择题正确率百分之八十以上因为编程题不确定性太高选择题的分数必须拿稳。2.3 框架题的占比与应对方式3. 手写代码题从用例设计到边界条件的小坑3.1 防抖节流的两种写法对比3.2 深拷贝的完整实现与隐藏考点3.3 手写题作答的展示策略4. 算法题复盘一道滑动窗口题目的完整思考过程4.1 拿到题先问三个问题4.2 从暴力解到滑动窗口的推导4.3 时间复杂度的取舍与笔试判题机制5. 笔试过程中的节奏管理和意外情况处理5.1 时间分配方案5.2 遇到不会的题和编译报错怎么办5.3 考后及时复盘为面试做铺垫手写题是前端岗笔试最有区分度的部分它不看你会不会背就看你能不能在白板状态下写出能跑的代码。小米这批的手写题风格偏实用不像有些厂让你手写一个完整的Promise或者发布订阅。我记得自己遇到的是两个很经典的题目方向防抖/节流的实现以及深拷贝的实现。这类题平时写业务的时候经常用到但真要你从零手写很多人反而写不完整。3.1 防抖节流的两种写法对比先说说防抖和节流。防抖的核心思想是“一定时间内多次触发只执行最后一次”节流的核心思想是“一定时间内最多执行一次”。笔试的时候经常让你二选一或者先写防抖再写节流。很多人的误区是背了一个版本结果没注意是否支持参数传递、是否带立即执行选项。一个相对完整的防抖实现是这样function debounce(fn, delay 300, immediate false) { let timer null; return function(...args) { const context this; if (timer) clearTimeout(timer); if (immediate) { const callNow !timer; timer setTimeout(() { timer null; }, delay); if (callNow) fn.apply(context, args); } else { timer setTimeout(() { fn.apply(context, args); }, delay); } }; }这个写法里面有三个容易被忽略的细节第一this要保留很多面试官会追问箭头函数能不能用在这里第二immediate参数控制是否立即执行这个在实际业务里很常用比如按钮点击防重复提交第三定时器执行完之后要把timer置空否则下一次callNow的判断会出错。节流我常用时间戳版本function throttle(fn, interval 300) { let last 0; return function(...args) { const now Date.now(); if (now - last interval) { last now; fn.apply(this, args); } }; }时间戳版本有个特性第一次触发会立即执行最后一次触发如果没到间隔时间会丢掉。如果不想丢最后的调用可以用定时器版但笔试一般不会要求双触发边界写清楚一种加注释说明即可。这类手写题真正加分的地方不一定是你写得多完美而是你代码里体现了对边界情况的思考。我在代码注释里标注了“保留this指向”和“支持立即执行选项”阅卷官假设是人工在看也会觉得你平时不是死记硬背是真正理解了这个工具函数。3.2 深拷贝的完整实现与隐藏考点深拷贝这道题看着简单其实考的是你的知识网够不够密。很多人一上来就写JSON.parse(JSON.stringify())这当然也是深拷贝但它的缺陷非常明显遇到undefined、function、Symbol会被忽略遇到Date会变成字符串遇到循环引用会直接报错。如果笔试题目里面的测试用例包含这些类型这个写法一定是过不了全部用例的。一个能覆盖大多数场景的手写深拷贝如下function deepClone(target, map new WeakMap()) { if (target null || typeof target ! object) { return target; } if (target instanceof Date) return new Date(target); if (target instanceof RegExp) return new RegExp(target.source, target.flags); if (map.has(target)) { return map.get(target); } const clone Array.isArray(target) ? [] : {}; map.set(target, clone); Reflect.ownKeys(target).forEach((key) { clone[key] deepClone(target[key], map); }); return clone; }这个版本里最值得说的就是WeakMap的使用——它的作用不是缓存数据而是处理循环引用。假如对象a里面引用了bb里面又引用了a不做处理就会无限递归下去。用一个WeakMap把已拷贝的源对象和克隆对象对应存起来再次遇到同一个引用时直接返回之前克隆好的对象递归就终止了。还有Reflect.ownKeys这个API它会把普通字符串属性和Symbol属性一起枚举出来这是Object.keys做不到的。如果考到Symbol作为属性名的克隆用Object.keys的写法直接漏数据。至于为什么用WeakMap而不是Map是因为WeakMap的key是弱引用不会影响垃圾回收用Map在这个场景里也不会出错但面试官问了能答上来就是加分项。3.3 手写题作答的展示策略手写题和算法题在作答上有本质区别。算法题只看结果对不对手写题大概率存在阅卷环节代码风格、注释、变量命名都会被看在眼里。我的策略有三个第一提前写注释。不是把整段代码注释满而是在关键判断处写一句“// 处理循环引用”“// 保留this指向”让阅卷人一眼看到你的思路。第二先用最保守的方式把核心功能跑通再补边界。不要一上来就写一个很复杂的版本结果语法都报错。第三考虑性能和可读性的平衡。深拷贝用递归是标准做法但如果递归层数很深会栈溢出能提到用迭代法的思路作为后续优化方向写在注释里也足够了。手写题最怕的不是写不出来而是写了一半发现思路有问题划掉重写最后交上去的代码只能用三个字形容——一团糟。我建议先在草稿纸上把思路的关键节点写出来再往编辑器里誊。算法题这部分我运气不算好遇到的一道题需要一点思考量但也没有到劝退的地步。具体题目细节我记不太清了但类型是无重复字符的最长子串算是LeetCode上的热门题考的就是滑动窗口这个经典思路。这类题对有准备的人是送分题但对没刷过的人就是完全没思路。4.1 拿到题先问三个问题看到算法题的完整描述之后我先逼自己回答三个问题输入规模大概多大、返回的是什么类型的结果、有没有明显的特殊边界需要单独处理。输入规模决定你能不能放心用O(n^2)的解法。如果n在500以内暴力解可能都能过n到10^4基本就要往O(nlogn)下面去想了n到了10^5线性解法是必须的。有些笔试题会故意不给你数据范围这时候默认按比较大的规模处理优先写线性或近似线性的解法。边界条件方面我每次都提醒自己检查空字符串、全部字符相同、全部字符都不相同、字符串只有一位。这几个用例分别覆盖了滑窗的初始化、左右指针移动、更新逻辑的极端情况。4.2 从暴力解到滑动窗口的推导“无重复字符的最长子串”这道题最直观的解法是枚举所有起始位置然后从每个位置往后扩展扩展过程中用Set记录出现过的字符一旦发现重复就停止记录当前长度。这个方案的正确性没问题但最坏时间复杂度是O(n^2)当字符串长度到10^5级别的时候很大概率超时。滑动窗口的思路本质上是对暴力解的优化既然右指针扫过的字符已经记录了左指针只需要重复字符出现之后跳过去不需要回到前面的位置重新遍历。代码写出来就是一套双指针加一个Setfunction lengthOfLongestSubstring(s) { const set new Set(); let left 0; let maxLen 0; for (let right 0; right s.length; right) { while (set.has(s[right])) { set.delete(s[left]); left; } set.add(s[right]); maxLen Math.max(maxLen, right - left 1); } return maxLen; }这段代码的思路可以概括为右指针逐个扩展窗口一旦遇到重复字符就把左指针往右移动直到窗口里没有重复字符为止。每次右指针移动后都计算一次窗口大小不断更新最大值。整个过程中左右指针都只往前走时间复杂度是O(n)。我当时的思考路径是先想到暴力解然后发现暴力解重复扫描了很多无效区间接着想到如果用Set记录当前窗口内的字符左指针跳转的时候就不需要从头再来。这个过程也是面试官最想听的“解题过程”但笔试只认最终代码所以如果你在注释里写清楚这是滑动窗口阅卷观感会好很多。4.3 时间复杂度的取舍与笔试判题机制笔试判题只看代码能不能在规定时间内跑完所有用例。你写一个O(n^2)的暴力解如果全用例通过照样满分你写一个O(n)的滑动窗口只要有语法错误照样零分。所以笔试策略上优先保证能跑通其次才追求最优解。但如果时间允许我建议把暴力解和优化解都留在草稿上实际提交时用优化解。为什么因为有些笔试题的最后几个用例是专门针对暴力解设计的超大数据暴力解跑不过去。我个人的习惯是前五分钟写一个暴力解确保思路正确然后立刻评估它的复杂度如果判断可能会超时再写优化版本。另外一个经验是尽量别用递归写深度不确定的算法。递归虽然没有硬件栈那么严格但在牛客这类系统里过深的递归会导致调用栈溢出而这道题完全可以用迭代写。能用循环就用循环能不用递归就不用递归这是在线判题环境下最稳妥的选择。笔试现场的时间节奏其实比很多人想象得紧张。不是说题目难到做不完而是你需要在有限时间内把会的题都稳定发挥出来不因为慌乱而丢分。这里分享一些我实际操作下来的时间分配和临场策略。5.1 时间分配方案一百分钟我的分配是这样的选择题控制在四十五分钟以内手写题三十分钟算法题三十分钟最后留五到十分钟检查。选择题遇到拿不准的先标记一下别死磕因为后面做编程题时可能突然想起来某个知识点。编程题我习惯先把两道题都扫一眼判断哪道题更顺手。手写题和算法题如果水平差不多先做手写题。手写题是“熟悉题型”先把稳定的分拿住算法题可能存在思路卡壳的风险放到后面心态会更稳。但如果算法题一眼就看出了思路、手写题反而没把握那就按“先易后难”原则先做算法题。这个没有绝对标准核心是先拿能拿的分。检查环节被很多人忽略但实际上非常关键。检查什么呢一是代码有没有未定义的变量二是有没有逻辑边界遗漏三是注释和题目要求是否一致。有时候你在草稿上想的和实际写进编辑器的完全是两回事用最后几分钟读一遍代码能挽回不少冤枉分。5.2 遇到不会的题和编译报错怎么办笔试时遇到完全没思路的题最忌讳的是坐着发呆。我的做法是先把输入和输出用简单的示例跑一遍再尝试用暴力解去套哪怕只能通过一部分用例也比零分强。有时候暴力解写着写着思路反而顺了从暴力解优化出正确解的情况并不少见。编译报错则是另一回事。在线笔试系统对语法错误的提示往往不如本地IDE友好报错信息看不太懂的时候先检查几个高频错误点分号是不是漏了、括号是不是匹配的、变量名是不是大小写不一致。如果是运行结果不对在关键位置插入一个console.log看中间状态比靠肉眼看代码快得多。还有一个容易被人忽略的坑牛客这类系统的输入输出格式是固定的。如果题目要求读一行整数你的代码写成读一个字符串再split会导致格式错误。这类题我建议提前在牛客上练几道输入输出模板题熟悉readline的使用方式别把时间浪费在调试IO上。5.3 考后及时复盘为面试做铺垫笔试结束不等于事情结束。我会在自己还能记住题目的时候把所有题目按“做对、做错、蒙对、没思路”四类整理一遍。做错的题不一定是没掌握可能是考场太紧张看错了题目条件蒙对的题更要重视因为面试问到同类型问题立刻露馅。整理完之后我会把每一类题目对应到相应的知识点判断自己哪一块是薄弱环节。“做错”的题涉及原型链那就抽时间重新过一遍原型链的常见问法“没思路”的算法题是动态规划那就补几道经典DP题找感觉。这个环节看似消耗时间但实际上非常高效——它让你下一次笔试或面试前的复习变得非常有针对性。还有一点考完出结果之前的空窗期一边等通知一边积累项目经验别把秋招的节奏停在笔试结束。小米的面试一般会问项目、问基础、问场景题笔试点出的薄弱点正好是接下来要补的面试考点。回头看这场笔试我最大的感受是前端岗的笔试不拼题海战术拼的是广度够不够全、细节够不够细。选择题考察的是你对常见API和原理理解的准确性手写题考察的是边界意识和编码习惯算法题则考察分析问题的路径。这三个层面没有一个是临时抱佛脚能解决的都需要前面几年或者几个月的持续输入。最后再分享一个小技巧笔试前我会把前端核心知识点的标题列在一张思维导图上考试中一旦遇到纠结的选项就在脑中定位到相应的知识点分支回忆我当时写下的关键结论。这个方法帮我避免了很多“凭感觉做题”的情况也推荐给即将参加校招笔试的同学。