
2019年那阵子小米秋招前端卷子在圈子里流传度挺高。我当年也认真做过这套题后来带新人、帮朋友做模拟面试又反复拿它当基础训练材料。现在回头看这套试卷虽然已经是几年前的产物但它考察的知识点结构放在今天依然能打——JavaScript 核心机制、浏览器工作原理、网络协议、手写代码能力几乎是国内大厂前端笔试的通用模板只要你投的是前端岗这套题涉及的方向基本绕不开。这篇文章我会完整复盘这套“小米2019秋招前端开发笔试题A”的题型分布、考察逻辑再挑核心知识点逐个拆解把每类题背后的原理讲透。同时我会给出一套模拟版的答题实战过程和常见失分点总结不管你是在准备校招、社招还是单纯想检验自己的前端基础扎实不扎实这篇内容都能当一份比较完整的参考。1. 这套笔试题到底在考什么考察思路与题型分布1.1 2019年前端圈的技术背景决定了试题侧重点先捋一下时间线。2019年国内前端生态处于一个微妙的过渡期Vue 2.x 和 React 16.x 是绝对主流TypeScript 开始被大厂接受但仍未全面普及小程序开发刚火起来前端工程化Webpack、Babel、CI/CD已经成为团队标配但还没有今天这么复杂的链路。这种背景下小米作为体量很大的互联网公司前端笔试不会考过于偏门的框架 API——因为框架更新太快今天考了明天就过时。反而会花大篇幅考察“换一个框架也依然成立”的基础能力包括JavaScript 语言本身的语言特性作用域、闭包、原型链、异步浏览器与网络相关的知识渲染机制、缓存、HTTP 协议代码实现能力给需求写代码或者手写常见 API面试官真正想通过笔试判断的是你有没有扎实的底层基础能不能在脱离文档的情况下独立写出可运行、可维护的代码遇到性能问题有没有排查思路。这套“A 卷”之所以有参考价值正是因为它的命题逻辑非常典型没有偏题怪题全是高频基础点。1.2 卷面题型结构选择题、简答题、编程题三梯队结合当时网上流传的回忆版和我自己的答题经验2019 小米秋招前端笔试题A大致可以分成三块题型段落主要考察方向建议用时选择题/判断题语言基础、浏览器机制、网络协议、框架常识30 - 40 分钟简答题原理描述、方案设计、场景分析30 分钟左右编程题手写实现、算法逻辑、代码输出60 分钟以上选择题覆盖面广零散知识点多很多同学在这块容易“会但不全”——比如知道闭包是什么但闭包一定造成内存泄漏吗不一定得看用法。这类判断题考察的就是概念的精确性。简答题往往不会让你写大段论文而是考察“用一两句话把原理讲清楚”的能力。比如“浏览器的同源策略是什么”、“请描述 Vue 的响应式数据原理”。看起来简单但答得准、答得稳其实不容易坑挺多的。编程题是拉开差距的核心部分会综合考察语言基本功和逻辑能力。函数防抖节流、数组去重、深拷贝、Promise 执行顺序、事件循环输出题都是那个年代前端笔试出镜率最高的类型。小米这套题也基本符合这个规律。1.3 从出题逻辑反推面试官想要什么样的人我站在面试官角度分析过这套题的设计逻辑发现它其实在筛选三类能力第一类是“语言掌握度”。公司没有精力去验证你背了多少框架 API但可以通过一道var和let混用的输出题判断你理解不理解作用域链通过一道this指向题判断你能否预测函数调用时的上下文。这些能力一旦具备不管框架怎么迭代你都能快速上手。第二类是“系统理解力”。现代前端早就不只是“写页面”所以笔试题会考察浏览器从输入 URL 到页面展示的完整过程、HTTP 缓存策略、回流与重绘的触发条件。这类知识决定你定位问题、优化性能时有没有方向感。第三类是“代码落地能力”。书面答案写得再漂亮动手写一个debounce或手写new的过程立刻能看出你对闭包、this、原型链到底理解到什么层次。所以编程题从来不是“背模板”能应付的真的要自己写过、踩过坑才写得流畅。想通这套逻辑以后再看这张卷子就不会觉得“考点零散”它其实是一个非常完整的知识地图。2. 核心考点拆解从 JavaScript 基础到手写代码这张卷子如果只看一个核心那就是 JavaScript 基础。后面三道编程题也完全围绕 JS 展开。我把核心考点拆成几个模块每个模块都配上典型例题和原理分析。2.1 变量提升、作用域与闭包选择题里的高频区选择题里一定会出现类似这样的题var a 1; function foo() { console.log(a); var a 2; } foo();输出是什么答案是undefined不是1也不是报错。因为函数作用域内存在var a的声明提升但在执行console.log(a)时a仍未赋值所以是undefined。这就是经典的变量提升。关于闭包有一种常见的迷惑选项“闭包一定会造成内存泄漏吗”标准答案是不一定。只有当闭包引用的变量不再需要、但外部引用依然保留时才会造成内存无法回收。比如function create() { let arr new Array(1000000).fill(0); return function () { console.log(arr.length); }; } const fn create();fn引用了arr如果fn一直存活那arr就永远无法释放。但这是“必要的引用”还是“内存泄漏”取决于业务场景如果确实需要持续访问就不算泄漏如果引用不再需要却没有置空那就算泄漏。做这部分选择题的经验是每一个选项都不要只看表面把背后的执行过程在脑子里跑一遍再下笔。宁可慢不要错选择题丢分是最可惜的。2.2 this 指向与箭头函数最简单也最容易踩坑this指向是前端笔试的常青树小米这套题里也出现了。常规考法是这样var name window; var obj { name: obj, say: function () { console.log(this.name); } }; obj.say(); // obj var fn obj.say; fn(); // window第一行输出obj很容易理解方法由obj调用this指向obj。第二行输出window因为fn是裸调用默认绑定到全局。如果放在严格模式下this会是undefined。这里有一个隐藏考点普通函数this是在调用时确定的而不是定义时。箭头函数的规则恰好相反它没有自己的this会捕获定义所在上下文的this。比如var name window; var obj { name: obj, say: function () { var fn () { console.log(this.name); }; fn(); } }; obj.say(); // obj箭头函数被定义在say内部所以它的this与say的this保持一致。我在实际面试中发现很多候选人能答对单道题但问到“如果箭头函数再嵌套一层呢”、“如果定义在对象字面量里呢”就容易懵。复习时要多造几个变体用 Node.js 实际跑一遍输出印象会深很多。2.3 原型链、继承与new的实现逻辑原型链相关题目基本属于“必考但不难”的类型。高频考点包括instanceof的原理遍历原型链Object.create(null)和普通对象的区别无原型链不能调用hasOwnProperty等Function.prototype和Object.prototype的关系各种继承方式原型链继承、组合继承、寄生组合继承的优劣笔试简答题可能会让你描述new执行时发生了什么或者干脆让你手写一个new函数。new的执行过程可以拆成四步创建空对象obj将obj的原型链接到构造函数的prototype将构造函数内部的this绑定到obj并执行构造函数如果构造函数返回对象则返回该对象否则返回obj手写实现function myNew(Constructor, ...args) { const obj Object.create(Constructor.prototype); const result Constructor.apply(obj, args); return result ! null (typeof result object || typeof result function) ? result : obj; }注意第 4 步的处理当构造函数显式返回对象时new的结果会被该对象覆盖这是很多同学容易忽略的细节。用Object.create建立原型链比手动设置__proto__更规范。2.4 异步、事件循环与 Promise 执行顺序前端笔试里事件循环相关题目是判断一个人是不是真正理解 JS 运行机制的分水岭。经典输出题长这样console.log(script start); setTimeout(() { console.log(setTimeout); }, 0); Promise.resolve() .then(() { console.log(promise1); }) .then(() { console.log(promise2); }); console.log(script end);输出顺序是script start script end promise1 promise2 setTimeout核心原因是Promise 的回调属于微任务microtask在当前宏任务macrotask结束后、下一个宏任务开始前执行setTimeout 的回调属于宏任务。执行顺序永远是宏任务 → 当前宏任务产生的所有微任务 → 下一个宏任务。这个知识点到现在也是面试高频我曾经在另一家公司面试时也遇到过几乎一样的题。建议大家刷题时不要再靠背答案而是理解“任务队列”的模型。Node.js 里process.nextTick的优先级甚至还高于 Promise 微任务这是进阶考点2019 年小米这套题没细考但是面试环节很容易追问出来。2.5 手写编程题数组、防抖节流、深拷贝编程题部分通常不会只考一道。据我回忆和圈内交流小米这套 A 卷编程题覆盖了以下几个方向手写数组去重ES5 和 ES6 两种思路手写防抖debounce和节流throttle手写深拷贝实现一个EventEmitter发布订阅解决一个简单的算法问题比如数组扁平化、去重排序防抖和节流尤其高频我见过太多人把两者记混。防抖的核心思路是“等一等再执行”事件触发后如果等待期内又触发就重置计时器。节流的核心思路是“固定频率执行”一段时间内无论触发多少次都只认第一次或最后一次。一个比较稳妥的防抖实现function debounce(fn, delay 300) { let timer null; return function (...args) { if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, delay); }; }这里的this绑定很容易写错。如果用箭头函数包裹fn.apply(this, args)this也会被静默改变所以必须用普通函数写法保留返回函数的this。很多候选人能写出功能但问到这里就答不上来面试官立刻知道他只是背了模板没有真正理解。深拷贝则有几个层次只拷JSON.parse(JSON.stringify(obj))有天然缺陷不处理undefined、function、Symbol、循环引用完整实现需要递归遍历且要解决循环引用一般用WeakMap缓存已拷贝对象。手写完整版代码量不短但思路很固定function deepClone(target, map new WeakMap()) { if (typeof target ! object || target null) return target; if (target instanceof Date) return new Date(target); if (target instanceof RegExp) return new RegExp(target); if (map.has(target)) return map.get(target); const clone Array.isArray(target) ? [] : {}; map.set(target, clone); for (let key in target) { if (Object.prototype.hasOwnProperty.call(target, key)) { clone[key] deepClone(target[key], map); } } return clone; }这版代码虽然在工程里还需要扩充比如处理Map、Set、Symbol属性但已经能覆盖笔试 90% 的考察点而且体现出了循环引用和特殊类型的处理意识。3. 模拟笔试题一整套带答案解析的实操复盘3.1 模拟选择题与判断题原题无法一字不差复原这类企业笔试题一般也不公开。但根据我和其他参与过考试的同学复盘用下面这套模拟题来还原考察方式非常有效。你自己也可以先遮住答案和解析当成真实考试做一遍再对照看错在哪里。第 1 题以下哪些方式可以判断一个变量是数组A.Array.isArray(arr)B.arr instanceof ArrayC.arr.constructor ArrayD.Object.prototype.toString.call(arr) [object Array]答案A、B、D。C 有坑arr.constructor可以被人为修改不够可靠。最稳妥的是 A 和 DB 在跨 iframe 场景下会失效因为不同全局环境的Array构造函数不是同一个这属于进阶考法但选择题里偶尔会出现。第 2 题以下代码输出什么for (var i 0; i 3; i) { setTimeout(() { console.log(i); }, 100); }答案3 3 3。原因是var声明的i是函数作用域循环结束以后i已经变成 3三个定时器回调共享同一个i。如果把var改成let输出就会是0 1 2因为let在每次循环中创建了独立绑定。这类题当年必考今天也是面试常客。第 3 题关于和以下说法正确的是A.null undefined为trueB.null undefined为trueC.0 为trueD.NaN NaN为true答案A、C。会做类型转换null和undefined在宽松相等时相等。NaN与自身不相等这是 JS 语言一个比较反直觉的设计所以判断NaN必须用Number.isNaN()。我在改代码时也发现有些 bug 就是隐式类型转换挖的坑建议公司规范里直接强制要求使用。第 4 题hello.length的值是多少答案5。看起来无脑但有的笔试题会混淆length和size比如字符串没有size属性只有数组和类数组有。这种题考察的是“基础是否足够细”。3.2 模拟编程题手写防抖、深度扁平化、事件总线编程题 1手写一个带立即执行选项的防抖函数第一道模拟题我设计成“带立即执行选项”的版本比普通防抖多一个细节但很能考察边界处理能力。function debounce(fn, wait 300, immediate false) { let timer null; let isInvoked false; return function (...args) { const callNow immediate !isInvoked; if (timer) clearTimeout(timer); timer setTimeout(() { timer null; if (!immediate) { fn.apply(this, args); } isInvoked true; }, wait); if (callNow) { fn.apply(this, args); } }; }这个版本的逻辑是immediate为true时首次触发立刻执行之后在wait时间内触发无效等到定时器结束才能重新触发。适用于按钮提交这种场景第一下点击要立刻生效后续快速点击要屏蔽。编程题 2将多维数组扁平化并去重排序function flattenAndSort(arr) { const flat arr.flat(Infinity); return [...new Set(flat)].sort((a, b) a - b); } console.log(flattenAndSort([3, [1, [2, [3, 4]]], 5])); // [1, 2, 3, 4, 5]Array.prototype.flat(Infinity)是 ES2019 引入的2019 年这个时间点正好可以考察候选人是否关注新特性。如果你不想依赖它也可以手写递归实现两种方式都能得分但手写递归更能体现功底function flatten(arr) { return arr.reduce((acc, cur) { if (Array.isArray(cur)) { return acc.concat(flatten(cur)); } return acc.concat(cur); }, []); }编程题 3实现一个简单的 EventEmitter发布订阅模式是前端非常常用的设计模式笔试考的概率也高。简化版实现class EventEmitter { constructor() { this.events Object.create(null); } on(event, callback) { if (!this.events[event]) { this.events[event] []; } this.events[event].push(callback); return this; // 支持链式调用 } emit(event, ...args) { if (this.events[event]) { this.events[event].slice().forEach((cb) cb(...args)); } return this; } off(event, callback) { if (!this.events[event]) return this; this.events[event] this.events[event].filter((cb) cb ! callback); return this; } once(event, callback) { const wrapper (...args) { callback(...args); this.off(event, wrapper); }; this.on(event, wrapper); return this; } }需要注意emit里用了slice()复制一份回调数组防止回调执行过程中off修改原数组导致遍历异常。这是工程里才会踩到的坑笔试时写出来会很加分。3.3 答题时间分配与现场经验以这套模拟题为例我建议的时间策略是先扫一遍全部题目把会做的选择题快速搞定不确定的题先标记不恋战。编程题先写自己能完全 run 通的那道建立信心再回头啃难啃的。简答题如果一时想不起精确表述先写关键词和要点最后再完善措辞。我当时做这套类型题的时候前 40 分钟在选择题上花得有点多导致最后一道编程题写得仓促结果很明显——前面多拿的 5 分比不上后面编程题丢掉的 10 分。所以时间分配上我后来总结出的铁律是编程题永远是最高优先级至少要留出 50% 的时间给它。4. 常见失分点与面试官视角怎样答才能拿高分4.1 高频失分点汇总结合很多批改笔试、做模拟面试的经验我发现候选人在这种“传统大厂前端笔试题”上的失分点高度集中。下面这张表可以帮你快速自查失分点具体表现解决方案概念模棱两可知道作用域但说不清“词法作用域”和“动态作用域”的区别用 1-2 句话把概念讲透别背定义手写代码不完整防抖函数漏掉this绑定深拷贝不处理循环引用练习时手把手跑一遍边界条件忽略编译环境不区分严格模式和非严格模式下的this主动说明“在严格模式下结果会不同”事件循环顺序混淆以为 Promise 比 setTimeout 一定先执行画清楚宏任务和微任务的队列图简答题没有逻辑讲 Vue 响应式原理只讲一句“依赖收集和派发更新”按“数据劫持→收集依赖→触发更新”三段式表达编码风格差变量名随意缩进混乱笔试也当代码评审来写保持可读性4.2 面试官视角下什么样的答案算高分我后来帮朋友批改过几份模拟题总结下来高分答案往往有三个共性第一个是“有层次”。比如简答题问“浏览器缓存”很多人只知道“强缓存和协商缓存”两个名词但高分答案会进一步说强缓存通过Cache-Control/Expires控制协商缓存通过Last-Modified/ETag实现再补充浏览器刷新行为的影响。这种“总——分——细节”的回答结构面试官一眼就能看出基本功扎实。第二个是“有边界感”。比如写节流函数时主动注明“该版本不支持取消上次计时器如果需要可以扩展一个 cancel 方法”。候选人不需要把每种扩展都写出来但能主动说出“有什么限制”和“怎么改进”比闷头写完一整段没有注释的代码要加分得多。第三个是“有可运行意识”。手写代码时很多候选人会写出Array.prototype.flat这类较新的 API如果面试官并不确认浏览器兼容性候选人主动补充一句“这个 API 在低版本浏览器需要 polyfill”或者“也可以手写递归实现”立刻会让面试官觉得你是真正写过实际项目的而不是在背题。4.3 备考建议与核心路径规划如果你现在正准备前端笔试我的建议是先别急着刷框架题花一到两周把 JS 基础夯实再用整套笔试题进行模拟训练。具体路径可以参考第一周语言基础专项。复习变量提升、作用域、闭包、this 指向、原型链、异步与事件循环。每天 10 道选择题不求速度快只看正确率。第二周手写代码专项。数组去重、深拷贝、防抖节流、new、bind、EventEmitter、Promise.all每个手写实现至少独立写 3 遍直到能默写。第三周高频框架 浏览器 网络。虽然各公司框架偏好不同但浏览器渲染原理、HTTP 缓存、跨域方案等属于通用考点。Vue 和 React 至少要熟悉一个的响应式/数据流机制。最后整套模拟。限定 90 分钟用小白纸当答题纸尽量模拟真实笔试环境。不要开编译器自动补全甚至手写代码。另外我根据“前端开发面试题”这个经常被搜索的方向总结一个经验大厂笔试真正筛选的不是“背了多少题”而是“遇到没见过的问题时能不能用已有知识推导出答案”。所以刷题时遇到不会的题不要立刻看答案先自己想一分钟把思路写下来再去对答案这个过程比单纯刷题有用得多。5. 从这套题延伸出来的能力成长路径回过头看小米 2019 秋招前端笔试题A给我留下的最大影响不是它考到了什么知识点而是它让我意识到前端知识的“滚雪球效应”。当时我为了准备这套题把 JavaScript 高级程序设计翻了两遍又看了不少源码分析和浏览器原理的文章这个过程建立的知识网络直到今天我写 Vue/React 项目、排查线上性能问题时仍然在用。笔试只是入场券。真正决定一个人能不能走远的是持续把“模糊”变成“精确”的能力。比如你知道会做类型转换这不算什么但你能准确说出 JS 抽象相等比较算法的执行流程进而解释为什么[] ![]是true这就是深度。类似的深度考点还有很多为什么0.1 0.2 ! 0.3根本原因是浮点数在 IEEE 754 下的存储精度问题为什么Array.prototype.sort默认按字符串编码排序而不是按数值排序为什么for...in适合遍历对象属性但不适合遍历数组为什么async/await可以被看作是 Promise 的语法糖错误处理机制却不一样建议每个前端学习者都准备一个“追根溯源”笔记本遇到一个知识点就问三个问题它解决什么问题它内部怎么实现的它的局限是什么这套思考方式不仅用于准备笔试题放到实际项目里一样是核心竞争力。以我自己为例我后来面试别人时最喜欢问的一个问题是“你上一份工作里遇到的最难的一个前端问题是什么你是怎么定位并解决的”能在这个问题上回答得好的候选人几乎都能在笔试里拿到不错的分数。因为那类人有一个共同特点——他们会把一个现象追到源码层、协议层、浏览器实现层而不是停留在“改一改就好了”的层面。当年我准备小米这套题时也走过弯路大量刷题却缺少系统整理导致有些题换一个问法就不会了。后来我把所有错题按照“语言基础、浏览器机制、框架原理、工程化、算法手写”五个抽屉重新归档每次复习只针对薄弱抽屉效率明显提升。这个方法到现在我也推荐给身边准备笔试的朋友。最后再分享一个小技巧笔试也好面试也好记得把“思考过程”留下来。笔试题如果给你上机环境多写注释面试手写代码时先跟面试官讲思路再动手。国内大厂前端团队普遍重视代码可读性和沟通能力你展示出“会思考、能表达、敢动手”的特质往往比某道题的答案本身更重要。这套小米笔试题就是一个很好的训练场把它吃透你会收获的不只是一份应试能力而是一整套前端工程师的底层思维。