ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

爱奇艺前端笔试复盘:事件循环、原型链与浏览器渲染核心考点

2026/8/29 21:46:36 拓冰建站 浏览量
爱奇艺前端笔试复盘:事件循环、原型链与浏览器渲染核心考点 1. 这场笔试为什么值得拿出来复盘2018年秋季校招季我投了爱奇艺前端工程师岗位。当时身边不少同学都觉得视频网站的前端业务应该不难无非是页面展示、播放器嵌入、会员中心那套东西。真正坐到笔试系统前才发现爱奇艺这场第一场前端笔试的考察思路和我想的完全不一样题面不偏不怪但覆盖面极广JavaScript基础、浏览器机制、网络协议、框架原理、前端工程化全都有涉及。它更像一场前端基础能力的全面体检扎实的人能稳稳拿下全靠背诵和套路的人很容易在第一轮就翻车。这篇复盘我拖了很久因为一直觉得校招笔试这种东西时效性太强过了就过了。但仔细想想2018年这场笔试的设计思路放在今天依然有参考价值——它考察的大量知识点比如事件循环、缓存策略、原型链、浏览器渲染流程完全是前端面试的基础盘《JavaScript高级程序设计》反复讲的那些东西换着花样考。正好最近在带团队做新人招聘回头看这套题很多场景依然经典值得拆开揉碎了聊聊。文章里我会按自己的记忆把当年的题目类型做一个还原并结合实际开发经验做延伸不会直接贴原始题目而是把考点、易错点、解题思路完整梳理一遍。如果你正在准备校招或者工作两年想回头补补基础这篇复盘应该能给你一些实实在在的帮助。2. 基础题翻车现场DOM操作与事件机制的那些坑2.1 事件流和事件委托你以为你懂但答不全爱奇艺笔试的基础选择题里DOM事件相关题目占比不低而且出题角度非常刁。它不是直接问你冒泡和捕获的顺序而是扔给你一段嵌套结构问你点击某个子元素时事件触发的完整路径是什么event.target、event.currentTarget、this分别指向谁。来先看这道我印象很深的还原题div idouter div idinner button idbtn点击/button /div /divvar outer document.getElementById(outer); var inner document.getElementById(inner); outer.addEventListener(click, function(e) { console.log(outer 冒泡阶段); console.log(target:, e.target.id); console.log(currentTarget:, e.currentTarget.id); }); inner.addEventListener(click, function(e) { console.log(inner 捕获阶段其实默认是冒泡); e.stopPropagation(); });很多人会答错原因在于对两个阶段的划分不够清晰。DOM事件传播分三个阶段捕获阶段从window一路向下到目标元素目标阶段然后冒泡阶段从目标元素一路向上回到window。addEventListener的第三个参数传true表示在捕获阶段触发默认false表示在冒泡阶段触发。上面这段代码里两个监听器都默认走冒泡阶段所以点击button时事件先到达目标然后向上冒泡。问题来了如果inner的监听器里写了stopPropagation()outer的监听器还能触发吗答案是触发不了因为冒泡在inner就被拦住了根本没到outer。笔试容易栽的地方是很多人以为stopPropagation()只能阻止冒泡但实际上它把捕获和冒泡都停了只是我们通常监听的阶段是冒泡阶段所以感知不强。这里还有一个细节容易被忽略——stopImmediatePropagation()不仅阻止事件继续传播还会阻止同一元素上后续注册的监听器执行。这个差距在写业务代码时如果没注意很容易出现为什么第二个监听器没跑的排查难题。2.2 事件委托为什么列表点击事件推荐写在父级上事件委托是笔试选择题里的常客一般以性能优化的形式出现。题干大致是有一个ul里面有几千个li点击每个li要输出对应索引你该怎么绑定事件错误的做法是给每个li分别addEventListener。几千个监听器挂在页面上每个都是一个独立函数对象内存开销大而且新增li后还需要重新绑定。正确做法是把一个监听器挂在ul上通过e.target判断点击的是不是livar ul document.getElementById(list); ul.addEventListener(click, function(e) { var target e.target; if (target target.nodeName LI) { console.log(点击了第, Array.prototype.indexOf.call(ul.children, target), 个li); } });这套写法在笔试里拿分没问题但真实现开发里还要处理一个细节li里面如果有span或者i图标点击的时候e.target是那个子元素不是li。这时候需要用closest方法向上查找ul.addEventListener(click, function(e) { var li e.target.closest(li); if (!li) return; console.log(li.textContent); });笔试不太会考到closest因为它不是ES5时代的东西但实际项目里这是标准答案。我当年在这道题上把target和currentTarget写反了后来面试复盘才真正把这两个概念理清。target是事件真正的来源元素currentTarget是当前正在处理事件的元素。在事件冒泡过程中target始终不变currentTarget会随着冒泡路径变化这也是事件委托能实现的核心原理——监听器挂在父级上但currentTarget始终是父级本身。2.3 模拟题同步代码里的一处隐藏的this丢失有一道选择题我印象很深不是因为它难而是因为太贴近日常业务了。题目大概是这样的var obj { name: 爱奇艺前端, getName: function() { return this.name; } }; var fn obj.getName; console.log(fn());答案是undefined而不是爱奇艺前端。原因很简单fn被赋值后调用时的this已经不再指向obj在浏览器非严格模式下指向window而window上没有name属性。严格模式下直接报错。很多同学做错是因为背了谁调用指向谁这句口诀但没有理解到赋值操作会让函数脱离原来的对象上下文这一层。这种题在笔试里是送分题但如果答错很容易让人觉得你对JavaScript最基础的运行机制理解不透彻。我当时在这类题上还算稳因为平时写代码就经常遇到解构赋值导致this丢失的问题已经被实际教训磨出来了。3. 原型链与异步一道题卡住半个考场的真相3.1 new操作符到底做了什么为什么笔试必考爱奇艺这套笔试的选择题里原型链的题目出了好几道考察方式不是让你背__proto__和prototype的区别而是给一段代码让你推导输出。比如这样一道经典题function Person(name) { this.name name; } Person.prototype.sayName function() { return this.name; }; var p new Person(qiuyi); console.log(p.sayName()); // qiuyi console.log(p instanceof Person); // true console.log(p.hasOwnProperty(name)); // true console.log(p.hasOwnProperty(sayName)); // falsehasOwnProperty和原型链上方法的区别是笔试的黄金考点。name是实例自身属性sayName是原型方法。hasOwnProperty只会检查实例自身不会顺着原型链查找。所以最后一个输出是false。进阶一点的题会这样考function A() {} function B() {} B.prototype new A(); var b new B(); console.log(b instanceof B); // true console.log(b instanceof A); // true console.log(b.constructor A); // true这里有个坑B.prototype new A()之后B.prototype.constructor已经被覆盖为A了所以b.constructor A是true。很多人在这一步翻车是因为没有记住修改原型对象之后constructor指向也跟着变这个结论。正确做法是在重写原型后手动把constructor指回来B.prototype.constructor B;这个知识点看起来基础但实际业务里踩过一次会痛很久。我在一个老项目里见过有人直接用obj.constructor来判断对象类型结果因为原型链被改过判断逻辑全乱了。笔试考的往往就是这类看似会用实则没理解的地方。3.2 一道Promise和setTimeout的混排题考的是事件循环爱奇艺笔试题里有一道异步执行顺序的编程题几乎可以算作当时前端笔试的标配大概长这样setTimeout(function() { console.log(timeout); }, 0); Promise.resolve().then(function() { console.log(promise1); }).then(function() { console.log(promise2); }); console.log(sync);正确的输出顺序是sync、promise1、promise2、timeout。这里考察的是宏任务和微任务的执行顺序。同步代码先执行然后清空微任务队列Promise.then最后才轮到宏任务队列里的setTimeout回调。注意Promise.resolve().then()注册的回调属于微任务setTimeout的回调属于宏任务即使setTimeout的延时是0也必须等当前宏任务和微任务队列都清空后才执行。这一题错的人不少原因在于很多人把0ms定时器理解为立刻执行。实际上setTimeout的延时0只是说尽快排队不代表插队。浏览器的事件循环机制里每执行完一个宏任务都会先去清空整个微任务队列然后再取下一个宏任务。爱奇艺这场笔试的异步题还没有考到async/await的微观执行顺序但如果放到现在大概率会加一道async function test() { console.log(1); await Promise.resolve(); console.log(2); } test(); console.log(3);输出是1, 3, 2。await后面的代码会被放到微任务队列中执行所以console.log(2)在console.log(3)之后。这个知识点在2020年后的面试里几乎是必问而且是区分看没看过原理的分水岭。3.3 深拷贝手写题里最稳的拿分项编程题部分爱奇艺给了一道手写深拷贝。要求不依赖JSON.stringify和JSON.parse自己实现一个函数能够正确处理普通对象和数组。当时我写的版本被自己后来多次优化第一版只考虑了普通对象和数组连Date、RegExp都没处理。笔试时间有限这样写已经能拿大部分分但放到工程角度显然不够健壮。一个生产可用的深拷贝至少要考虑这些分支function deepClone(obj, map new WeakMap()) { if (obj null || typeof obj ! object) return obj; if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); if (map.has(obj)) return map.get(obj); let clone Array.isArray(obj) ? [] : {}; map.set(obj, clone); // 处理symbol属性 const symbolKeys Object.getOwnPropertySymbols(obj); symbolKeys.forEach(key { clone[key] deepClone(obj[key], map); }); for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { clone[key] deepClone(obj[key], map); } } return clone; }这里最关键的优化点是使用WeakMap来处理循环引用。笔试阶段很多人写不出循环引用的处理但面试官如果追问如果对象里有循环引用怎么办能答出WeakMap或Map存映射关系就是一个明显加分项。WeakMap的优势是键是弱引用不会阻止垃圾回收在深拷贝这种临时数据结构里更合适。另外Object.keys和for...in的差别也要注意。for...in会遍历原型链上的可枚举属性所以必须配合hasOwnProperty过滤。设计这套题的人把这个考点巧妙地藏在手写题里粗心的同学可能写出来功能正常但会在无意中拷贝到原型属性造成安全隐患。4. HTTP缓存与浏览器渲染那些背过但没用过的知识点4.1 强缓存与协商缓存笔试必答但好多人分不清场景爱奇艺这套笔试的网络题目考得挺细主要集中在HTTP缓存上。选择题会给你一个请求头/响应头组合让你判断这次请求是从缓存读取还是重新请求服务器。这里面的核心就是强缓存和协商缓存的流程对比。强缓存走的是Cache-Control和Expires。Cache-Control: max-age3600表示1小时内不会向服务器发起请求直接读本地缓存。它还有一个常见属性no-cache注意这个命名有迷惑性no-cache不是不缓存而是缓存但每次使用前必须去服务器验证真正不允许缓存的是no-store。协商缓存走的是Last-Modified/ETag这一对。当强缓存失效后浏览器带着If-Modified-Since或If-None-Match请求头去服务器验证服务器返回304 Not Modified表示可以继续用本地缓存如果资源变了返回200和新的资源。笔试常考的一个选择题是ETag和Last-Modified同时存在时谁优先答案是ETag优先因为它能处理秒级以内的文件变化还能通过内容哈希应对文件内容不变但修改时间变了的情况。我在写项目时遇到过一个坑静态资源用时间戳做版本号但同一秒内改了两次文件版本号一致导致客户端拿到旧资源。上了ETag之后就正常了这就是理论和实践对照的典型场景。4.2 从输入URL到页面渲染这道简历题里的隐藏考点爱奇艺笔试有一道问答题要求描述浏览器从输入URL到页面展示的完整过程。这种题目在笔试中经常出现因为它是检验知识体系完整性的高效试金石。完整的链路可以压缩成以下几步DNS解析把域名转换成IP地址。建立TCP连接经过三次握手。发起HTTP请求服务器返回HTML。浏览器拿到HTML后解析构建DOM树。解析CSS构建CSSOM树。合并DOM树和CSSOM树生成渲染树Render Tree。根据渲染树进行布局Layout。绘制Paint并合成Composite。如果回答到这里算是及格。但想拿高分需要补充几个细节解析HTML过程中遇到script标签会阻塞DOM构建所以script通常放在body底部或者使用defer/async属性。CSS解析不会阻塞DOM树构建但会阻塞渲染因为渲染树需要CSSOM。现代浏览器还有预扫描机制在解析HTML时提前发现外部资源并开始下载。这几个细节全答出来就能和只会背流程的候选人拉开差距。爱奇艺当时这道题分值不低我在回答时还把script标签defer和async的区别也写上了算是知识点联动。4.3 重排和重绘笔试最后一公里与渲染流程配套的必考点是重排Reflow和重绘Repaint。爱奇艺这道选择题问的是以下哪个操作不会触发重排选项通常包括修改字体大小、改变窗口尺寸、修改color、增删DOM节点。正确答案是修改color它只触发重绘。因为color的变化不影响元素几何尺寸和位置浏览器只需要重新绘制不需要重新计算布局。但笔试只是第一步真正的功力在实际优化里。比如循环里不断读取offsetHeight再修改样式会导致浏览器强制同步布局Forced Synchronous Layout每次循环都重新计算布局性能极差。正确做法是先把需要的值读出来再批量修改或者用documentFragment做DOM批量操作。我在那个阶段的体会是笔试考的知识点看起来零散但其实是同一个体系里的不同侧面。HTTP缓存、浏览器渲染、重排重绘背后都是浏览器如何高效地把页面呈现给用户这个命题。爱奇艺的题正好把这几个点串在一起考察方向是一致的。5. 从爱奇艺客户端linux热词看移动端前端的加分项5.1 为什么笔试之外还会有客户端方向的考察我注意到一个有意思的现象当年我搜爱奇艺前端面试相关经验时常会出现爱奇艺客户端linux这类热词。爱奇艺的招聘体系里前端工程师和客户端工程师存在交叉尤其是播放器方向、WebView容器方向很多岗位会明确要求候选人了解跨端知识和操作系统常识。虽说是前端工程师的笔试但在很多题目里会暗含客户端环境的考察意图。比如有一道关于播放器视频封面加载的题本质是HTML5video相关但如果你往深了想会发现封面加载策略、视频预加载、缓存命中这些点和Android/iOS端WebView的缓存机制是打通的。笔试考的不只是前端语法更是你面对真实复杂场景时有没有跨端思维。5.2 WebView交互这类题回答思路是什么当时笔试里有一道简答题在一个App内嵌H5页面中H5如何调用原生功能比如调起相机这道题放在现在依然是热门面试题标准答案是JS Bridge。基本思路是原生端向全局window对象注入一个方法H5通过window.NativeBridge.camera()调用。或者通过自定义URL SchemeH5用location.href或iframe发起跳转原生端拦截这个URL后解析参数执行对应功能。更现代的方案是使用postMessageAPI安全性和兼容性都更好。笔试能答出第二种和第三种就很不错了。我当时补充了一个细节URL Scheme有长度限制不适合传大数据所以大文件、图片之类的数据要考虑存本地再传引用。这个补充来自实际踩坑因为当时做播放页里分享图片时图片base64直接拼在URL里把Scheme挤爆了。5.3 Linux热词带来的联想命令行环境与部署常识爱奇艺客户端linux这个热词让我联想到当年前端工程师面试里已经开始出现Linux基础相关的问题不是考你背命令而是问线上日志怎么排查Nginx配置改错了怎么办。这本质上是在筛选那些能独立处理线上问题的人。我当时在准备阶段专门补了一圈Linux基础命令尤其是日志查看相关的# 实时查看日志 tail -f app.log # 按关键词过滤 grep ERROR app.log | head -20 # 查看端口占用 netstat -tlnp | grep 8080 # 磁盘空间 df -h这套东西虽然在笔试里很难直接考但在面试环节经常被追问。爱奇艺这类体量的公司前端要跟运维、客户端、后端不断打交道能懂点Linux会显得你好用。现在回看这个判断是对的。今年我带新人遇到一个线上静态资源404的排查新人一脸懵我让他先去看Nginx日志他连tail都不太熟这种基础差距是笔试看不出来的。5.4 给校招准备的延伸建议如果你现在还处于校招准备期除了把原生JS、HTTP、浏览器机制这些基础打牢我建议额外做三件事第一去理解WebView的工作原理。知道Android和iOS的WebView差异知道JSSDK的授权机制这对进视频类、内容类公司是硬加分项。第二学会看客户端日志和抓包。Charles、Whistle这类工具要会用至少知道怎么拦截HTTPS请求、怎么查看请求头和响应体。很多前端负责人实际面试时会问一句线上页面白屏你怎么排查能说出先看抓包工具里的接口返回和静态资源状态码的人分数一下就不一样。第三补充基础的Linux部署知识。哪怕只是自己买台便宜的云服务器把静态页面用Nginx部署一遍试过改配置文件、重启服务、看日志这个过程就比只会在浏览器里玩前端的人高一截。6. 复盘这套题后我重新理解的校招准备思路6.1 基础题不是背诵题是理解题当年刷题有个误区以为把《JavaScript高级程序设计》里的概念背下来就能过笔试。爱奇艺这套题让我意识到前端笔试的大部分题目都在考你能否在具体代码场景中理解这个概念的边界。比如this的指向、事件循环的时序、原型链的继承关系、缓存策略的适用场景都不是靠背定义能答对的。一个有效的刷题方法是在每道题后面多问三个问题这个知识点在什么场景下会出现如果我忽略了它会出现什么bug有没有办法通过代码验证我的判断我当时就是把每道选择题都当成小案例写进CodePen里实际跑一遍。this指向、事件冒泡、setTimeout和Promise的顺序全都在浏览器控制台里亲眼看过输出之后再碰到类似题目就不怕了。6.2 编程题拿分的关键先跑通再优化像深拷贝这种手写题考场上时间紧张优先级应该是先实现一个能处理普通对象和数组的版本跑通基本用例然后再考虑Date、RegExp、循环引用等边界。这个思路在笔试里非常实用。我见过不少同学一开始就想着把循环引用、Symbol、不可枚举属性全处理掉结果写到一半时间不够连基础版本都没交上去。面试官看的不仅是代码正确性还有工程思维——你能不能识别核心需求在有限资源下做出取舍。拿到编程题后先花30秒理清题目要什么哪些是必写的核心逻辑哪些是优化项这对大部分校招笔试都适用。6.3 这场笔试过去很久但基本功永远不过时现在带团队的面试中我出的笔试题还是那几类事件循环输出顺序、原型链指向、手写函数防抖节流、HTTP缓存场景判断。看起来和2018年爱奇艺这场笔试差不多但考察意图更纯粹了——不管框架怎么变这些JavaScript和浏览器底层机制是绕不过去的。爱奇艺这套题对我在校招阶段的定位很有帮助。它让我意识到前端不是会写页面就行而是要对网络、浏览器、运行机制有体系化的理解。这种认知在后面几年的工程实践中不断被验证无论做组件库、性能优化还是跨端方案都离不开这些底层素养。如果你也在准备校招与其焦虑面试题变化快不如踏实把基础打好。框架可以换API可以忘但事件循环、原型链、渲染流程这些东西值得反复咀嚼。