
2021年小米秋招前端方向第一场笔试这个题目放在今天看依然很有嚼头。前端面试这东西卷归卷但翻来覆去其实就是在验几件事JS基础扎不扎实、异步编程理不理解、浏览器和网络那套机制熟不熟、框架原理能不能讲明白外加一点手写代码的硬功夫。小米这场笔试在当年秋招里属于基础盘很稳的典型代表不像有些厂喜欢出偏题怪题它更看重你是不是真的把前端核心体系吃透了。这篇文章就来复盘一下这场笔试的考察逻辑结合我自己的做题经验和后来辅导别人备战秋招的体会把那些核心考点、陷阱和应对思路一次讲清楚。不管是准备暑期实习、秋招还是跳槽这套东西都可以直接拿来用。1. 笔试的基本盘形式、结构与踩点节奏1.1 第一场笔试的题型分布与考察配比小米秋招前端方向的第一场笔试整体风格是广而不深覆盖面大但单点难度不算极端。当时拿到的卷子大致是这么个结构单选题、多选题、填空题、简答题和两道编程题。客观题占大头主观题集中考察代码能力和基础概念理解。从知识点配比来看JS语言基础占了将近三分之一浏览器与网络相关的题目紧随其后然后是CSS、框架Vue为主React也会有、数据结构和算法。这个配比其实很有代表性它反映了大厂笔试的一个通用原则先筛掉基础不牢的人再通过少量编程题筛掉代码能力不行的人。很多人容易犯一个错误就是看到前端笔试就疯狂刷框架题、背面试八股结果在基础题上翻了车。小米这场笔试的客观题里有不少都是看似简单、实则挖坑的题目专门用来区分你是真懂还是背过答案。1.2 时间分配策略客观题千万别恋战整套笔试的时间大概在90到120分钟之间具体看当年批次。我印象里比较吃亏的是一部分选择题特别容易让人纠结比如那种以下哪种写法会导致内存泄漏之类的多选题选项设置得模棱两可一犹豫就是两三分钟。我自己的策略是客观题平均每题不超过90秒拿不准的先标记跳过等编程题写完再回头纠结。因为编程题的分值通常占比很高一道题可能顶七八道选择题把所有时间耗在选项上是非常不划算的。另一个容易忽略的点是编程题的输入输出格式。小米的笔试系统用的是牛客网那套和力扣的纯函数式提交还不一样有的题需要自己处理标准输入输出。平时刷题习惯了函数签名一到处理readline就懵了这种情况我见过太多了。1.3 难度定位一套典型的筛选型试卷说句实话这套卷子不是用来选拔顶尖高手的而是用来把不合格的人筛掉的。它的题目区间拉得很开简单题占了六成中等题占三成难题只有一到两题。这种设计逻辑在秋招笔试里非常常见让基础扎实的人能顺利通过让基础一般的人暴露问题让浑水摸鱼的人直接出局。所以你会发现很多题目考察的是你知不知道而不是你能不能写出花来。比如简答题问你和的区别、null和undefined的区别这种题看起来简单但能准确说清楚的人比例并不高。2. 基础但决定生死的一关核心知识点的考察方式2.1 作用域、闭包与this指向那类一选就错的题笔试里关于作用域和闭包的题目基本是必考的。考察方式也很多样可以给你一段代码问输出结果可以问闭包的实际应用场景也可以结合var、let、const的区别一起考。我记得当时有一道很经典的题大致是这样的for (var i 0; i 5; i) { setTimeout(() { console.log(i); }, 100); }问输出结果。答案是5个5不是0、1、2、3、4。这道题就是典型的var没有块级作用域循环结束之后i已经变成5所有定时器回调共享同一个i。如果改成let i结果就会变成0到4。这类题目在面试里出现频率极高而且会变着花样考。比如结合闭包来问怎么用var实现0到4的输出或者问setTimeout第三个参数传进去有什么作用——没错setTimeout(fn, 100, i)确实可以把当前i的值传进回调。关于this指向的题目核心就记住几句话谁调用函数this就指向谁箭头函数没有自己的this沿作用域链向上找new调用时this指向新创建的对象call、apply、bind可以显式绑定。笔试中常见的坑是对象方法取出赋值给变量后再调用this就丢了这种题当年也考了。2.2 原型链、继承与new的底层逻辑原型链这一块也是笔试的常客。考察方式通常有问instanceof的原理、判断a instanceof A为什么返回true或false、手动实现一个new、写出原型链继承的代码。手动实现new是高频中的高频大概思路是四步function myNew(Constructor, ...args) { // 1. 创建空对象原型指向构造函数的prototype const obj Object.create(Constructor.prototype); // 2. 执行构造函数绑定this const result Constructor.apply(obj, args); // 3. 如果构造函数有返回值且是对象则返回该对象否则返回新建对象 return (typeof result object result ! null) || typeof result function ? result : obj; }笔试里考到这个其实就是在验证你知不知道new到底干了什么。但更深入一层面试官在后续面试中会追问Object.create做了什么Constructor.prototype和__proto__是什么关系Function.prototype和Object.prototype谁在链条顶端这些基础一定要提前捋清楚。继承这块ES6的class extends考得最多但有时也会让你对比几种组合继承的优缺点。比如寄生组合继承为什么优于组合继承本质原因是组合继承会调用两次父类构造函数导致子类实例和父类原型上有重复的属性。这种细节问出来很多人就会卡壳。2.3 事件循环与输出顺序异步编程的送分题与陷阱题事件循环是前端笔试的必考模块小米这场也不例外。考察形式基本就是给一段混合了同步代码、setTimeout、Promise、async/await的代码让你写出输出顺序。这类题核心掌握几条规则同步代码最先执行Promise.then里的回调属于微任务queueMicrotask也是微任务setTimeout、setInterval的回调属于宏任务每执行完一个宏任务就会清空当前微任务队列async函数里await后面的代码相当于.then里的回调属于微任务举个例子这道题当年很多人在输出顺序上栽了跟头console.log(1); setTimeout(() { console.log(2); }, 0); Promise.resolve().then(() { console.log(3); }); console.log(4);输出顺序是1、4、3、2。同步代码1和4先执行然后微任务3最后宏任务2。如果题目里再加上async函数嵌套或者await后面接了一个立即resolve的Promise情况会更复杂但底层规则还是那几条。想把这部分吃透我建议把Node.js和浏览器的事件循环差异也了解一下。笔试虽然大概率不考那么深但面试环节很可能会被追问。比如process.nextTick在Node里的优先级高于普通的微任务这类点能体现你的知识深度。2.4 客观题里那些看似基础、实则反直觉的变体笔试里的客观题有个特点就是喜欢考反直觉的结论。比如[] false结果是true但[] 0也是true比如NaN ! NaN但NaN 0和NaN 0都是false比如5 - 3得到25 3得到53。这些都是JS隐式类型转换的经典考点一道题里就能看出你是死记硬背还是理解了转换规则。再有一类常见变体是围绕typeof和instanceof做文章。typeof null是objecttypeof function(){}是functiontypeof []是object[] instanceof Array是true[] instanceof Object也是true。这些结论在平时开发中可能没什么感觉但笔试就是喜欢打这种知识盲区。要想在这部分少丢分我的建议是把JavaScript高级程序设计》里关于操作符、隐式类型转换的章节翻出来重新过一遍尤其是ToPrimitive的规则。理解了这个几乎所有类型转换相关的题都能推出来而不是全靠背。3. 工程能力与场景题笔试里那些看起来简单、一写就错的手写题3.1 手写深拷贝为什么直接JSON.stringify不够小米笔试的编程题有一类风格很鲜明不考你复杂的算法而是考你平时开发中一定会遇到但你可能从来没有完整思考过的函数。手写深拷贝就是其中最有代表性的一道。基础版本的深拷贝用递归就能实现。但笔试里通常会加条件考虑循环引用。如果对象里存在环const obj { name: test }; obj.self obj;直接递归拷贝会爆栈。这时候就需要用一个Map或者WeakMap来记录已经拷贝过的对象遇到循环引用时直接返回之前拷贝的结果。function deepClone(target, map new WeakMap()) { if (typeof target ! object || target null) { return target; } 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; }这里有几个细节要注意用Reflect.ownKeys可以把Symbol类型的属性也遍历到用WeakMap而不是Map是为了避免内存泄漏因为WeakMap的键是弱引用的数组要单独判断。如果能答出这些细节笔试官对你的评价会明显不一样。至于很多人标配的JSON.parse(JSON.stringify(obj))能说清楚它为什么不能用来做深拷贝也很重要它无法处理函数、undefined、Symbol会丢失值分别为undefined和Symbol的属性Date对象会被转成字符串Map、Set会被转成空对象循环引用直接报错。这些为什么比怎么写更能体现功力。3.2 Promise.all至少三种写法的问题手写Promise.all也是前端笔试的高频题。这道题看起来不难但坑点不少。第一版很多人会写成这样function promiseAll(promises) { return new Promise((resolve, reject) { const results []; promises.forEach((p, i) { Promise.resolve(p).then(res { results[i] res; if (results.length promises.length) { resolve(results); } }).catch(reject); }); }); }问题在于results.length的判断不可靠。如果索引为0的Promise最后才返回中途results的长度就已经等于promises.length了但数组里还有空位。正确做法是维护一个计数器function promiseAll(promises) { return new Promise((resolve, reject) { const results []; let count 0; promises.forEach((p, i) { Promise.resolve(p).then(res { results[i] res; count; if (count promises.length) { resolve(results); } }).catch(reject); }); // 处理空数组的情况 if (promises.length 0) { resolve(results); } }); }这里还有一个容易被忽略的问题then回调里的results[i] res顺序是乱的没有关系最后结果数组里每个元素的位置依然正确因为是通过索引写入的。这其实体现的是并发写入按序输出的思路和Promise.all的语义是一致的。有的人会在这里直接用results.push(res)那输出顺序就是完成顺序而不是传入顺序了属于典型错误。除了Promise.allPromise.race、Promise.allSettled、Promise.any的实现思路也建议一并练习。这些API平时都在用但笔试和面试问到的时候能不能现场写出来就是另一回事了。3.3 防抖与节流别把两个概念搞混防抖和节流是所有前端笔试里出场率最高的手写题之二。考察点本身不难但很多人一紧张就混淆了两者的适用场景。防抖的核心思路是事件触发后等待一段时间再执行如果等待期间又被触发则重新计时。经典场景是搜索框输入联想用户停止输入一段时间后才发起请求。function debounce(fn, delay) { let timer null; return function(...args) { clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, delay); }; }节流的核心思路是在一段时间内只执行一次。经典场景是窗口滚动、按钮防重复提交。function throttle(fn, interval) { let lastTime 0; return function(...args) { const now Date.now(); if (now - lastTime interval) { lastTime now; fn.apply(this, args); } }; }笔试时我建议在代码里加注释说明各自的适用场景这属于白给的体现工程意识的机会。另外有些题目会要求支持第一次触发时立即执行或者最后一次触发后在延迟结束后再补一次执行。这种边界条件加进来后就要考虑用时间戳和定时器组合实现逻辑会更复杂一些。能把这层想清楚并写出来基本就是满分答案了。3.4 数组去重、扁平化、字符串解析必拿分的小算法除了手写工具函数编程题里也会出现一些数组、字符串相关的简单算法。数组去重至少有两种经典写法// 利用 Set const unique arr [...new Set(arr)]; // 利用 reduce includes const unique2 arr arr.reduce((acc, cur) acc.includes(cur) ? acc : [...acc, cur], []);要去重对象数组按某个字段去重的话用Map或者对象做哈希表就行。数组扁平化也是常客核心思路就是递归或者利用reduceconst flatten arr arr.reduce((acc, cur) Array.isArray(cur) ? acc.concat(flatten(cur)) : acc.concat(cur), []);如果需要指定扁平化深度就要在函数里多传一个depth参数来控制递归层级。这类小算法题难度不高但特别考验代码整洁度和边界情况处理。比如空数组、多层嵌套、包含null和undefined的情况都需要在思路上提前覆盖。写的时候可以先用注释把思路列出来再动手写代码这样既能减少错误也能在代码规范上给笔试官留下好印象。4. 浏览器、网络与安全前端笔试里的知识广度区4.1 从URL输入到页面渲染一题串起所有关键环节浏览器输入URL到页面展示的全过程是大厂笔试、面试的最高频综合题之一。小米的笔试也没有绕开这个点虽然不一定要求写长篇大论但通过选择题加简答题的组合把其中几个关键环节拆开来考了。完整的链路大致如下DNS解析 → TCP连接 → 发送HTTP请求 → 服务器响应 → 浏览器解析HTML构建DOM树 → 解析CSS构建CSSOM树 → 合并生成渲染树 → 布局Layout/Reflow→ 绘制Paint→ 合成Composite。笔试里常见的考点有DNS查询过程浏览器缓存、系统缓存、路由器缓存、根域名服务器逐级查询TCP建立连接的三次握手HTTP请求头里的Cache-Control、If-Modified-Since、ETag分别起什么作用DOM树和CSSOM树为什么要并行解析什么情况会触发回流和重绘以及如何减少回流。一次完整的页面加载其实把整个前端网络和浏览器知识点都覆盖了。所以我一直建议备战笔试的读者花一个下午把这个过程从头到尾写一遍用自己的话讲清楚每一步发生了什么这是性价比极高的复习方式。4.2 HTTP缓存机制笔试里最消耗耐心的那一组选项HTTP缓存相关的题目几乎每次笔试都会出现而且出题方式特别刁钻。常见的是给一组响应头让你判断浏览器是走强缓存还是协商缓存或者问修改了某个响应头之后缓存策略会发生什么变化。强缓存相关的头主要是两个Expires和Cache-Control。Expires是HTTP/1.0时代的东西返回的是服务器时间客户端和服务器时间不一致时就会出问题。Cache-Control: max-age600是HTTP/1.1的标准做法用相对时间避免了时间同步问题。两者同时存在时Cache-Control优先级更高。协商缓存相关的头主要是Last-Modified/If-Modified-Since和ETag/If-None-Match。前者用文件的最后修改时间判断后者用内容标识判断更精确。ETag的优先级高于Last-Modified。这些知识点看起来零散其实有一个统一的记忆框架强缓存是浏览器自己决定直接用本地缓存不用问服务器协商缓存是浏览器不确定缓存是否有效需要向服务器确认。只要把这两大类的判断流程梳理清楚无论题目怎么变都能拆解出答案。4.3 跨域与安全CORS的细节不能只背结论跨域问题也是笔试的高频考点。核心知识点包括同源策略的定义协议、域名、端口三者一致常见的跨域解决方案CORS的简单请求和预检请求的区别。其中很多人在细节上容易出错简单请求需要满足什么条件、什么时候会触发OPTIONS预检请求、Access-Control-Allow-Credentials配合withCredentials使用时会有什么限制。笔试不一定考到这么细但面试很可能会沿着这条线一直追问。安全方面的考点主要是XSS和CSRF。XSS要能区分存储型、反射型和DOM型并说明各自的防御思路输入过滤、输出转义、CSP。CSRF要理解攻击流程和防御方案比如SameSite属性、Token校验、Referer校验等。这块建议花时间整理成表格对比记忆因为在笔试中经常以多选题形式出现选项会故意混淆攻击类型和防御手段。5. 从笔试反推备战主线面试官真正想筛选的能力5.1 前端知识体系的四大支柱经历过小米这场笔试之后我最大的感受是前端基础知识一定要成体系地复习不能东一榔头西一棒槌。如果把前端面试要考的东西做一个归类大致是四大支柱。第一是JS语言基础。包括变量、作用域、闭包、原型链、继承、事件循环、类型转换、高阶函数、ES6新特性。这是笔试占比最大、也最容易通过系统复习拿分的部分。第二是浏览器与网络。包括页面渲染机制、事件机制、缓存、跨域、HTTP协议、网络安全。这部分知识相对独立适合用一个URL的旅程这样的大线索串起来复习。第三是CSS。盒模型、BFC、Flex、Grid、层叠上下文、响应式布局、移动端适配方案。小米笔试里CSS题量不算大但它属于懂就是懂、不懂就是不懂的知识点靠猜很难猜对。第四是框架。Vue和React的响应式原理、生命周期、组件通信方式、虚拟DOM和diff算法、性能优化手段。笔试对框架的考察相对浅更多是概念性选择题但面试环节会深挖。5.2 刷题策略牛客网与力扣怎么搭配针对小米这类大厂笔试刷题策略不能是盲目的题海战术要有针对性。第一个方向是前端基础题推荐在牛客网上刷前端专项练习尤其是JS基础、HTML/CSS、网络知识这几类。每天刷50到100道坚持一到两周客观题部分会有质的提升。牛客的题目下面通常有答案解析和热门评论很多评论里都藏着一些口诀和记忆技巧。第二个方向是手写题推荐把常见的工具函数全部练一遍深拷贝、防抖节流、Promise.all系列、数组去重、数组扁平化、new、bind、call、apply、柯里化、发布订阅。这些在前端面试手写题汇总里基本都能找到但一定不要只看不写要手敲到能无脑默写的程度。第三个方向是力扣上的基础算法题。前端笔试的算法难度整体低于后端但数组、字符串、链表、二叉树、动态规划这几类基础题还是建议刷一下主要练思路和编码速度。建议从简单题开始刷到中等难度即可不必死磕困难题性价比不高。5.3 复盘方法论错题本到底怎么记才有用我见过很多人的备战方式是疯狂刷题但每道题对完答案就翻篇了这样的效率其实很低。真正有效的做法是建立一个错题本而且不是简单地把错题粘贴进去。我在带人备战面试的时候会建议按以下格式记录错题题目考察的知识点标签比如类型转换、原型链、事件循环我当时的错误答案和错误思路正确答案和正确推导过程这类题目的统一解题模板或口诀以事件循环为例如果某道题的输出顺序判断错了不要只记这道题而是把当初分析这道题时遗漏的规则补进去。比如漏了await后面的代码微任务的机制那么这个点就是你的知识漏洞要单独整理成一个条目。每周花半小时翻一遍错题本重点看错误的思路会比盲目刷新题有效得多。因为笔试中真正让你丢分的往往不是新知识而是同一个坑反复踩。5.4 秋招时间规划前三个月怎么安排结合小米秋招的时间线来看前端方向的第一场笔试通常安排在秋招提前批或正式批早期。按正常节奏正式批大约是8月中旬到9月中旬笔试那么建议在6月就开始进入备战期。第一个月用来系统性过基础。对照四大支柱把JS、浏览器、网络、CSS、框架的基础知识逐一过完一边看一边做笔记每个知识点都要保证能用自己的话说出来。第二个月进入刷题期。以牛客专项为主搭配手写题和力扣基础算法题。建议每天保持两到三个小时的编程时间手写题要写到不需要思考就能写出来的程度。第三个月是模拟冲刺期。找往年的笔试真题或模拟卷按真实考试时间完整做几遍。重点训练时间分配和心态控制。模拟的时候要故意留出不会做的题练习跳过和取舍的能力这样上考场时才不会因为一道题卡住而慌掉全盘。6. 笔试之外从这场考试延伸出的几个后端与工程化思考6.1 项目中涉及的后端知识点前端笔试为什么考这些小米的笔试有一部分题目看似超出了前端的范围比如微任务、宏任务的执行顺序实际上它考的其实是JavaScript在浏览器和Node.js环境下的运行时机制。而有些关于HTTP、跨域、Cookie的题需要你对后端的工作方式有一定的理解。原因很简单一个优秀的前端开发者至少能看懂接口是怎么设计的了解服务端返回的数据要经过什么样的链路才能到达页面。这类题目不用专门去学后端的完整知识体系只需要掌握几个和前端强相关的点HTTP请求的完整流程、常见的状态码含义、CORS的完整交互过程、Cookie和Session的原理与区别、JWT的构成与使用场景。这些内容在笔试里出现时通常不会特别深但会以给一个场景让你判断应该怎么做的形式出现。6.2 工程化与代码规范笔试中的隐形加分项编程题除了考察代码正确性其实也会关注代码风格。如果你在笔试中写出了清晰的函数命名、合理的边界判断、必要的注释那么即便某道题正确率不是100%面试官也更愿意给你机会。比如实现深拷贝的时候函数命名为deepClone而不是func1判断类型时用typeof target ! object而不是target null || target undefined在循环引用处理上主动加上WeakMap并注释为什么用WeakMap。这些细节看起来不起眼但它们传递的信息是你写代码的方式是严谨的、可维护的而不是临时拼凑的。再比如Promise.all那道题如果你能在代码里处理空数组的情况或者在注释里写清楚为什么用计数器而不是results.length这两行附加信息就会成为你的差异化优势。6.3 面试官看完笔试之后会在面试里做什么笔试结束之后其实面试官会拿着你的笔试作答记录做文章。如果你在笔试卷面里暴露出某个薄弱点那面试时有很大概率会沿着这个薄弱点追问。举个例子如果笔试里有一道关于this指向的题你答错了面试官可能会在面试开头问我看你笔试里这道题选的是A能解释一下为什么你觉得this指向obj吗然后顺着你的回答逐步深挖直到把你的真实水平摸清楚。所以复盘笔试时不要只把目光放在哪些题做对了、哪些题做错了还要思考如果面试官在这里追问我能不能接得住。提前把这些可能的追问写下来模拟回答一遍这样的准备才是有效的。7. 写在最后的实战建议我的踩坑体会关于2021年小米秋招前端方向第一场笔试我能给到的最实在的建议有几点。首先刷题一定要刷到写吐的程度尤其手写题。很多人在牛客上看别人的题解觉得我都会了真到考场一紧张函数签名都记不全。把代码肌肉记忆练出来考场上才能稳。其次基础知识不要只背结论。比如闭包、原型链、事件循环每道题不仅要会做还要能解释为什么会这样。因为在面试环节这部分是必问的而且会以一种连环追问的形式出现。理解底层原理比记住一堆题目答案重要得多。最后一点笔试不理想不代表终结。大厂的笔试通常是多场次、多批次小米也不例外。一场挂了还有下一场的机会关键是要学会从每场考试里总结自己的薄弱点然后在下一场之前补上。说实话我能走到后来拿到offer靠的不是把每一道题都做对而是每次考完都认真复盘确保在同样的坑里不摔第二次。前端这条路基础决定高度细节决定成败。希望这篇复盘能帮你在备战时少走一些弯路。