2016阿里前端笔试题(二)复盘:JavaScript闭包与事件循环核心考点解析

发布时间:2026/8/29 21:13:13
2016阿里前端笔试题(二)复盘:JavaScript闭包与事件循环核心考点解析 这套笔试我前前后后看了好几遍也在团队内部带着新人一起复盘过。2016年的阿里前端笔试题放到今天来看里面有不少题目依然是面试常客甚至可以说是前端基础能力的一块试金石。标题里写着“(二)”说明是那批笔试题的后面一部分但恰恰是这部分考察的东西更偏向工程思维和边界处理能力单纯背八股文是过不去的。我自己当年刷这套题的时候最大的感受是它不考你“会不会用某个API”而是考你“在真实场景下能不能把这个API用对”。很多题目看起来简单真正动笔写才发现自己脑子里对JavaScript运行机制的理解是模糊的。这篇文章就把我对这套笔试题的拆解、复盘、以及后来在实际开发中验证过的经验一起整理出来希望能给正在准备前端面试的同学一些参考也给带新人的老工程师一份可以直接用的题目解读。1. 这套笔试题到底在考什么1.1 题型分布与考察维度从整体结构来看这套笔试题的题型大致可以分为几类JavaScript语言基础作用域、闭包、this指向、原型链、异步编程与事件循环、DOM操作与事件机制、HTTP与浏览器缓存、以及少量带工程色彩的开放题。跟现在很多面试卷子动不动就上框架源码、手写Vue响应式不同2016年的阿里前端卷更侧重于语言本身和浏览器原生能力。这里有一个很重要的信息那年React和Vue已经流行起来但笔试里几乎不问框架API。原因很简单框架更新太快今天背下的API明天就可能废弃面试官想看的不是你会不会写某个组件而是你脱离框架之后能不能理解浏览器和JavaScript本身的工作原理。这一点放到今天依然成立前端基础扎实的人上手任何框架都快反过来只会用框架的人一旦遇到底层问题就抓瞎。1.2 为什么阿里会这么出题我后来跟几位参加过当年面试的工程师聊过大家的共识是阿里前端团队当时已经有相当规模需要的人不是“能写页面”的而是“能解决复杂问题”的。所谓复杂问题无非就是几类异步流程控制、性能优化、跨浏览器兼容、代码维护性。这几块恰恰是这套笔试题的考察重点。举个例子题目里经常出现“下面代码输出什么”这类题表面看是考输出顺序实际上是在考你脑子里的JavaScript执行模型是不是清晰的。如果你能准确说出每一行代码在调用栈、任务队列、微任务队列里是怎么流转的那你写复杂业务逻辑的时候就不容易出竞态问题。我面试别人的时候也喜欢从这个角度切入因为这个问题能快速筛掉那些只靠记忆API混日子的人。另外这套题对“边界情况”的重视程度也拉满了。比如数组去重、字符串处理这类看似简单的题目总是会在输入为空、类型不一致、重复元素连续出现等边界上埋坑。面试官想看的其实就是你有没有处理边界条件的习惯这个习惯直接对应到生产代码的健壮性。2. 核心题目详解与考点拆解2.1 JavaScript作用域、闭包与this指向这一块几乎是必考的而且考法非常灵活。先说一个最经典的闭包题。for (var i 0; i 5; i) { setTimeout(function () { console.log(i); }, 100); }问输出结果是什么。这个题目放到今天大部分人都知道答案是5个5但如果我追问一句“为什么不是0、1、2、3、4”不少人就说不清楚了。关键在于var声明的i是函数级作用域5个setTimeout回调共享同一个i变量当100毫秒后回调执行时循环早已结束i已经变成5所以打印出来全是5。要解决这个问题常规做法是把var改成let让每次循环都生成一个新的块级作用域绑定或者用IIFE手动捕获当前值。我在实际开发中更推荐let因为它更直观代码更简洁。如果项目环境比较老必须用var那就老老实实写IIFE。for (var i 0; i 5; i) { (function (j) { setTimeout(function () { console.log(j); }, 100); })(i); }这道题背后的核心考点是“词法作用域”和“变量生命周期”。你需要理解闭包能访问定义它的那个作用域而不是调用它的那个作用域。这个理解如果不到位后面很多实际问题都会踩坑比如循环里绑定事件、定时器里访问循环变量、异步请求回调里用错索引全是这个原理。再说this指向。有一类题长这样var obj { name: Alice, getName: function () { console.log(this.name); } }; var fn obj.getName; fn();答案是undefined非严格模式下是undefined严格模式会直接报错因为fn被赋值后调用时的执行上下文变成了全局对象this不再指向obj。很多人在这道题上栽跟头不是不知道规则而是临场紧张忘了“this由调用方式决定”这条基本法。我总结一个简单的判断方法看函数调用点。如果是obj.getName()这种形式this就是点号前面的对象如果是fn()这种裸调用this就是undefined严格模式或全局对象非严格模式如果用了call/apply/bindthis就是这几个方法传入的第一个参数。判断时先看调用方式再看有没有显式绑定基本不会错。2.2 异步与事件循环异步编程这一块2016年的笔试题里已经出现Promise了但考得不算深更多是把setTimeout、Promise、事件循环放在一起考输出顺序。这类题现在依然是前端面试的标配我甚至见过不少高级岗面试还拿它做开场。console.log(start); setTimeout(function () { console.log(timeout); }, 0); Promise.resolve().then(function () { console.log(promise); }); console.log(end);输出顺序是start、end、promise、timeout。这个顺序背后是事件循环的完整机制同步代码先执行完然后清空微任务队列Promise.then最后处理宏任务队列setTimeout。这里最容易让人困惑的是“setTimeout延迟为0”感觉它应该立即执行但实际上浏览器最快也要到下一轮事件循环才会把它捡起来。我在复盘这套题的时候发现一个更好的理解方式把JavaScript执行环境想象成一个公司同步任务就是正在开会的员工Promise.then是会议结束后马上要交的日报setTimeout则是第二天要做的事。不管日报写得多快它一定在第二天的事之前处理完。用这个类比去跟新人讲效果比直接扔规范文档好得多。另外一个容易混淆的点是如果setTimeout嵌套在Promise里面又该怎么排队比如Promise.resolve().then(function () { setTimeout(function () { console.log(inner timeout); }, 0); }); setTimeout(function () { console.log(outer timeout); }, 0);这种情况下外层的setTimeout先进入宏任务队列所以先打印outer再打印inner。理解这个你就能明白微任务和宏任务的嵌套关系是怎么流转的不会在复杂的异步流程里迷路。2.3 DOM事件机制与性能DOM事件在当年的笔试题里出现的频率也很高特别是事件冒泡和事件委托。有一道题让我印象很深给一个ul下面动态添加1000个li每个li点击时打印自己的序号你会怎么实现懂套路的同学会直接说“事件委托”把监听器挂到ul上通过event.target判断点了谁。但这道题真正的坑在后面动态添加的li怎么保证index正确如果用循环变量i去绑定哪怕用了事件委托只要你在回调里直接闭包引用i依然会踩到上文说的那个闭包陷阱全部打印1000。正确做法是用dataset把索引存到元素上或者用let声明循环变量。ul.addEventListener(click, function (e) { var target e.target; if (target.tagName LI) { console.log(target.dataset.index); } });这道题背后有三层考点第一层你知不知道事件委托能解决性能问题而不是给每个li绑定单独的事件第二层你能否正确处理动态元素第三层你在思考事件处理时有没有考虑事件对象、目标识别这些细节。真正能把三层都想全的人写出来的代码在生产环境里才经得起考验。除了事件委托浏览器默认行为和事件传播机制也是常考项。比如event.stopPropagation()可以阻止冒泡event.preventDefault()可以阻止默认行为有些人总是把这两个混在一起。当时有一道题问点击一个a标签既不想跳转又不想触发外层容器的click应该怎么处理正确答案是两者都要调用先preventDefault阻止跳转再stopPropagation阻止冒泡。这个知识点本身不难但放在真实业务里比如弹窗内的按钮点击和遮罩层关闭逻辑如果处理不当就会出现点击穿透等bug。2.4 逻辑与算法题这部分题目其实都不算难难在紧张状态下能不能写得又快又准。我记得有一道数组去重的题目要求不能用ES6的Set。当时的常规思路是双重循环去重或者用对象做哈希表。对象的方式需要注意如果数组里有数字1和字符串1用对象去重会把它们当成同一个键这个坑我亲眼见过很多人在笔试卷上踩了。function unique(arr) { var obj {}; return arr.filter(function (item) { return obj.hasOwnProperty(item) ? false : (obj[item] true); }); }这个实现如果遇到[1, 1, 2]输出结果是[1, 2]数字1和字符串1会被合并。要解决这个问题可以用typeof item item作为key或者用Map。这个问题在笔试后我专门去查了V8里对象键的存储方式其实就是键会被强制转成字符串理解了这一点你就永远不会在这个问题上翻车。字符串类题目也经常出现比如判断回文、统计字符出现次数、取出最长不重复子串等。做题的时候我建议大家先跟面试官确认输入约束比如字符串长度、字符集范围。这个动作本身不加分多少但能体现你的工程意识。实际工作中处理用户输入之前先做边界确认是能让代码质量上一个台阶的习惯。3. 答题思路与策略复盘3.1 拿到题目先拆题再动手我在复盘这套笔试题的时候最大的体会就是别急着写代码。很多题像闭包输出、事件循环顺序其实考察的是你脑子里有没有一张完整的执行流程图。拿到题之后先花几十秒在草稿纸上把关键步骤画出来尤其是异步题目把宏任务、微任务的队列变化过程一步步写下来答案自然就出来了。这不是浪费时间而是避免落入“凭感觉写答案”的陷阱。我见过很多人做事件循环题时凭记忆背答案看到Promise就先输出也不管它注册在哪个队列里。只要题目稍作变形比如setTimeout和Promise嵌套或者中间夹着async/await背答案的人立刻露馅。拆题的过程就是把你自己的思考过程可视化这是面试官最想看到的。真正需要手写完整代码的题目比如数组去重、事件委托我建议先写一个简单实现再逐步优化。比如先写出能跑的版本再补充边界判断再考虑性能。这样做有两个好处第一即使时间不够你至少交出了一个能用的答案第二展示了你迭代式思考的过程这比一次性写出完美代码更能打动面试官。3.2 答案组织怎么才加分笔试不仅仅是代码正确表达也是分。我批改过多份笔试卷发现一个规律那些最终进入面试的人往往会在代码旁边写下简单的注释或者在关键步骤处标注“这里为什么这么做”。比如事件委托那道题有同学只写了一段代码虽然正确但我看不出他的思考深度另一位同学写了三行注释为什么会用事件委托、dataset为什么要作为索引存储、边界情况怎么处理这一下就拉高了评价。加分的一个重要技巧是把边界条件和异常情况单独写清楚。比如数组去重的函数如果你能写出“输入为null或undefined时返回空数组”这行判断那就说明你平时写代码的时候有防御性编程的习惯。别小看这个细节在工程实践中几乎所有线上事故都源于对异常输入的处理不足。另一个加分点是代码风格。变量命名是否语义化、是否使用严格相等、是否避免全局变量污染这些都是能快速判断一个人有没有工程素养的信号。我当年看卷的时候就算候选人思路稍有瑕疵只要代码风格干净整洁我都会在评语里标注“基础扎实”。3.3 时间分配与做题顺序这套笔试题给的时间说不上充裕所以做题顺序很关键。我的建议是先把所有题目快速浏览一遍标记出“完全有把握”和“需要思考”的题目先拿稳有把握的分数再回头啃难题。这个策略看起来非常简单但很多人一紧张就忘了容易卡在一道题上死磕最后导致后面的简单题没时间写。我自己的做题习惯是第一遍先把所有答案写在草稿纸上尤其是编程题先写伪代码然后第二遍统一誊写到卷面上。这样做的好处是避免在卷面上涂改影响卷面分也给大脑留出了思考缓冲期。有经验的面试官能从草稿纸上的改动看出你的思考路径这反而是加分项。4. 常见失分点与避坑清单4.1 高频失分点实录这套题里失分最严重的地方第一是作用域与闭包第二是异步输出顺序第三是边界条件遗漏。作用域和闭包失分的原因不是不知道定义而是不会在具体代码里应用。很多人能背出“闭包是函数嵌套函数”但一遇到for循环加setTimeout就蒙圈说到底还是没形成条件反射。异步输出顺序的失分原因则相反太相信自己的记忆。比如看到Promise.resolve().then就条件反射认为肯定在setTimeout之前输出但忽略了一个前提条件就是这两者必须在同一轮事件循环里比较。如果Promise是在某个宏任务回调里创建的而setTimeout在另一个宏任务回调里注册那它们的执行顺序就会变得复杂得多。边界条件遗漏的问题主要出现在编程题。算法题里如果数组为空、只有一个元素、包含NaN、包含对象都是容易被忽视的边界。我建议大家做题时养成一个习惯写完主体逻辑后列出至少三个特殊输入自测一下。这个习惯在笔试中能帮你捞回不少分在工作中能帮你减少大量的bug。4.2 排查思路实录我在复盘题目的时候自己动手把每道题跑了一遍有些题第一遍跑出来的结果和预期不一致我就按下面的流程排查先打印关键变量的值确认数据流是否正确再检查作用域链看变量是否被意外遮蔽最后看事件队列理解异步调用的先后顺序。这个排查流程不只是笔试时有用日常开发调bug也完全适用。很多前端新手遇到问题就乱改代码结果越改越乱。有条不紊地排查才是一个工程师该有的状态。我建议你们在家练习的时候就用浏览器自带的开发者工具打断点、看调用栈、观察作用域把工具彻底玩熟。面试的时候虽然没有调试器但这个过程会内化成你的思考方式让你在脑子里也能“模拟执行”代码。4.3 避坑速查表易踩的坑问题描述应对方案循环变量捕获var声明的循环变量被多个闭包共享使用let或IIFE捕获当前值事件循环顺序误以为setTimeout执行完立即执行回调记住宏任务与微任务的队列优先级对象键类型数字1与字符串1会被当作同一键用Map或组合typeof构建唯一key事件委托索引错误闭包引用循环变量导致索引全部为末值用dataset存储索引动态读取this指向混乱方法被赋值后调用丢失上下文查看调用方式必要时用bind边界条件遗漏空数组、NaN、null等输入处理不当写完代码先拿特殊输入自测代码风格散漫命名随意、使用隐式类型转换保持语义化命名统一用这张表是我带新人时一定会发出去的大家平时写代码的时候也可以往里面继续补充。前端这个领域知识更新太快但基础问题翻来覆去就这些只要把一类问题的根源吃透所有变种题目都能迎刃而解。5. 从笔试题到工程实践的延伸5.1 笔试题背后的真实能力要求很多人觉得笔试过了就结束了其实笔试题目暗含了团队对工程师的期待。以这套题为例它虽然考的是JavaScript语言基础但这些基础对应的是实际业务中的真实痛点。作用域与闭包对应的是大型项目里的模块化设计和数据隔离事件循环与异步对应的是请求竞态、接口时序、加载优化这些每天都要面对的问题DOM事件对应的是页面交互的响应速度和内存泄漏排查。我带团队这几年越来越觉得一个工程师能不能独当一面看的就是这些基础能力能不能在业务中灵活运用。比如线上用户反馈页面卡顿有经验的工程师会先去查是不是事件监听器绑了太多导致内存泄漏或者是不是有高频触发的scroll/resize事件没有做节流这些排查思路的起点就是你对事件机制的理解程度。所以我建议准备面试的同学不要只停留在刷题层面。每做一道题想一想它在业务中对应的场景是什么。比如考节流防抖你就要知道它是为了解决滚动加载、搜索建议这类高频触发的问题。把题目和业务场景串起来面试官跟你聊项目的时候你自然就能展示出深度。5.2 工程化与规范从解题到写代码2016年的笔试题里其实还没怎么考工程化但现在的面试基本跑不掉Vue/React、构建工具、组件设计这些话题。这里我想多说一句笔试中那些基本功和工程化经验是互补的不是对立的。比如你在项目里用Vue组件里大量使用了computed、watch如果不懂响应式原理遇到性能问题就只能瞎猜但如果你基础扎实能理解数据流和依赖收集很多问题一眼就能看穿。我当时看阿里的招聘要求特别强调“代码规范”和“可维护性”。这一点在笔试里也能体现出来就是那些代码风格、命名习惯、边界处理的细节分。等到实际工作中代码规范更是团队协作的地基。大家每天Review代码的时候都是在跟别人的私有习惯和通用规范做对抗。所以我强烈建议从笔试刷题阶段就养成规范意识命名用有意义的英文单词函数保持单一职责变量不滥用全局这些习惯越早形成越好。5.3 面试官视角的“前端开发skills”清单站在面试官角度一场技术面下来其实就三件事基础扎实不扎实、项目有没有深度、思路清不清晰。笔试成绩能筛掉一部分人但能走到面试阶段的人笔试分数都差不太多真正的分水岭是聊项目和聊技术方案时的表现。如果你想给自己做一份面试准备清单我建议包含以下几项JavaScript核心机制作用域、闭包、原型、异步、浏览器工作原理渲染流程、事件循环、存储、HTTP与网络基础缓存、状态码、跨域、至少一个主流框架的深入理解、性能优化实战案例、代码调试能力。这份清单放到今天依然适用跟2016年相比只是多了框架深度和数据可视化等细分方向底子还是那一套。我见过很多候选人简历上写着“精通JavaScript”但一问“闭包的实际应用场景”就只能背出“防抖节流”。这个答案不是错但太浅了。闭包在模块化、私有变量、高阶函数、函数柯里化里都有大量应用光会背一个名词是撑不起“精通”两个字的。准备面试的时候每个知识点至少准备一个真实项目里的案例这样才能把知识从“知道”变成“理解”。6. 复盘这套题的一些个人体会这套2016年的阿里前端笔试题我在不同阶段翻过好几遍每翻一遍都有新的收获。第一次看的时候我自己也在不少题目上栽了跟头尤其是事件循环那类题当时还专门翻了一堆博客才把微任务和宏任务的顺序理清楚。后来做了几年工程再回看这些题才真正理解出题人想考察什么。我给你们一个实操层面的建议刷题不要只看答案一定要自己动手把题目代码跑一遍然后打断点、看作用域、看调用栈亲眼验证答案。这个过程比你背十篇题解都有用因为你在用“做实验”的方式学知识印象深刻得多。我自己带新人的时候也是让他们用这种方式刷题效果比单纯刷LeetCode好不少。最后再分享一个小技巧当你遇到一道不熟悉的前端题目时先别急着搜答案试着用自己的话把题目复述一遍然后再去查资料你会发现查到的内容更容易记住。这套笔试题里的很多考点其实一直延续到了今天的前端面试里如果你能把这些基础彻底吃透不管未来技术怎么变你都有一块足够稳的底子。