
2020年春季我投了字节跳动的前端实习岗从一面到HR面一共四轮磕磕绊绊两周走完最后拿到了offer也顺带把当年那些前端面试题沉淀成了这份面经。现在回头翻自己当时记的复盘笔记发现这场面试最大的价值不在于offer本身而是它逼着我把整个前端知识体系重新梳理了一遍。如果你也在准备字节或其他大厂的前端实习这篇面经应该能帮你少走一些弯路。这篇文章不打算写成“标准答案合集”我更想还原一个真实备战状态哪些地方我准备得很充分哪些地方明明不会却靠现场推理混过去哪些问题被面试官追问两轮之后就彻底露馅。同时我也会把每个高频考点的原理拆解和答题思路补全方便你对照自己的薄弱环节去补强。适合正在准备暑期实习、或者第一次投大厂技术岗的同学阅读。1. 投递前的那两个月时间规划、简历与项目沉淀1.1 倒排期准备基础复习、算法、项目复盘三线并行我当时给自己留了两个月的完整准备周期实际走下来发现这个时间刚好够用但前提是每天保持固定节奏。我的安排大致分为三个阶段前两周集中过基础中间三周刷算法和手写题后面三周做项目复盘和模拟面试。三个阶段的划分不是绝对的算法从第一天就开始了只是前期每天只刷一两道保持手感进入第二阶段后增加到每天三五道。说句实话“两个月”是一个理想化的数字。很多同学是临时决定投简历可能只有两到三周时间。如果时间紧张我建议优先保住三个东西JS基础、项目复盘、高频手写题。CSS和网络可以在面试前一周突击但JS的底层机制和项目里自己做过的技术决策几乎每轮面试都会碰到临时抱佛脚很难糊弄过去。另外我给自己的一个硬性要求是每天面试结束后必须把当天复习的知识点用文档总结一遍。不是抄教材目录而是用“如果我是面试官我会怎么问”的视角把每个知识点变成问题写在文档里隔一天再自问一遍能不能答上来。这个习惯后来被证明非常有用很多面试题我都在自己的问题清单上提前演练过。1.2 简历上哪些内容真正会被追问字节的简历筛选不算玄学实习岗主要看学校和项目经历。如果项目经历写得好非科班也能过筛。但要注意简历上的每一条技术点都可能成为面试官的提问素材。我自己就吃过这个亏简历里写了“熟悉Webpack配置”结果二面的时候被追问了整整十分钟的Loader和Plugin运行机制。面试官真正关心的不是你会用某个工具而是你在项目里为什么选择它、遇到问题时怎么排查的。写项目经历的时候不要列“负责了什么功能”而要写“实现了什么效果用到了哪些关键技术解决了什么问题”。比如普通写法负责后台管理系统登录模块开发能打的写法基于Vue 2 Vuex实现登录状态持久化通过路由守卫拦截未认证用户解决页面刷新后登录态丢失的问题面试官看到第二种写法大概就会往路由守卫的触发时机、token存放在哪里更安全这些方向追问。这些都是你能提前准备好答案的问题。我在简历上写了三个项目主推其中一个前后端都自己写的个人博客项目因为它的技术栈最全也最能展示我对整个链路的理解。1.3 项目复盘要做到什么程度才算够项目复盘是很多人忽略的一块但实际面试中它的权重可能比基础知识还高。我当时花了很长时间整理一个文档内容不是“做了什么功能”而是围绕三个问题为什么用这个技术方案当时有没有替代方案这个项目遇到的最大难点是什么怎么定位和解决的如果重做一遍哪些设计你会推翻重来这些问题面试官不一定会按清单问但只要你把一个项目讲透了他们顺着你的回答就能判断出你的技术深度。我主推的博客项目里有一个“基于localStorage的草稿自动保存”功能看起来很简单但面试官追问的是“如果用户同时开两个标签页编辑同一篇文章草稿怎么处理”这就上升到分布式一致性问题了我当场没答好后来面试官给了一个思路利用浏览器storage事件监听其他页面的变更以时间戳作为版本号做冲突处理。这个回答思路后来我在别处也用过很多次是很好的加分点。2. 四轮面试逐题复盘一面基础、二面原理、三面综合、HR面软题2.1 一面JS、CSS、浏览器、网络一个都没少我的一面面试官看起来只比我大两三岁非常温和。没有上来就“介绍一下你自己”而是先问我投了哪些部门对什么方向感兴趣。这种暖场在字节技术面里比较常见不用紧张正常聊就行。暖场结束后照样是先做自我介绍。这里提醒大家自我介绍不要说太多项目细节重点说清楚“我是谁、会什么、做过什么”把项目悬念留给面试官来问。我当时的介绍控制在两分钟以内主要讲技术栈和项目方向。接下来就是一连串基础题。印象比较深的是这几道第一题是看代码说输出涉及事件循环、async/await 和 Promiseasync function async1() { console.log(async1 start); await async2(); console.log(async1 end); } async function async2() { console.log(async2); } console.log(script start); setTimeout(() { console.log(setTimeout); }, 0); async1(); Promise.resolve().then(() { console.log(promise1); }); console.log(script end);输出顺序是script start、async1 start、async2、script end、promise1、async1 end、setTimeout。这里最容易被忽略的是await之后的代码会被推迟到微任务队列中执行而不是立即执行。异步函数的 await 相当于把后续代码包装成一个 Promise 的 then 回调所以async1 end会排在promise1后面。同时async2()里的console.log是同步执行的不要被 async 关键字迷惑了。第二题是 CSS 三栏布局要求左右固定宽度、中间自适应高度铺满视口。我用了 flexbox 方案面试官又追加了几个追问如果中间内容很长怎么办、flex 布局的 flex-grow 和 flex-shrink 默认值是多少、flex: 1究竟代表什么。这些问题如果没实际写过样式很难答完整flex: 1实际上是flex: 1 1 0%的缩写即等分剩余空间可以避免只写 flex-grow 时内容宽窄不均的情况。第三题从“浏览器输入 URL 到页面展示”开始一路追问到 DNS 解析、TCP 三次握手、HTTP 请求响应、渲染进程解析 HTML、构建 DOM 树、CSSOM、布局和绘制。这是前端面试的经典送分题但也是层层深入的漏斗题。准备的时候不要只背流程要能把每一步都讲出细节比如回流和重绘的区别、CSS 是否阻塞渲染、defer 和 async 的区别等。我在这一块花了很大功夫把整个流程串成了一个连环回答面试官问到哪里我都能接住。一面最后是一道算法题给定两个有序数组找出中位数。题目不难但我一开始只想到合并数组的暴力解法时间复杂度 O(mn)。面试官提示优化到 O(log(mn))我才意识到要用二分查找。这里要说一下字节的算法题不是只要求能跑通还会期望你做复杂度分析。如果只写出暴力解大概率过不了。2.2 二面框架原理与项目深挖二面明显比一面硬核面试官上来直接就问项目对我在简历里写的博客项目进行了非常细致的追问。从数据库表设计、登录鉴权流程、为什么用 JWT 不用 Session到首屏渲染优化做了什么几乎每个细节都被盘了一遍。这个环节的体验很像在做一个代码评审如果项目是自己真做的会非常有底气。项目聊了二十分钟之后开始框架原理题。因为简历写的是 Vue 技术栈面试官先问 Vue 2 的响应式原理。我背过很多次了从 Object.defineProperty 拦截属性、依赖收集到 Watcher 通知更新一气呵成。然后面试官追加了一个非常经典的问题“为什么 Vue 3 要改用 Proxy”这个问题我当时的回答是比较浅的只知道 Proxy 可以监听对象属性的新增和删除而 Object.defineProperty 做不到。但面试官继续追问“Vue 2 对数组做了特殊处理你知道为什么吗”这就涉及到底层细节了Object.defineProperty 无法检测数组索引变化所以 Vue 2 只能重写数组的 push、pop 等方法来实现响应式。接着是虚拟 DOM 和 diff 算法。面试官问了一个很贴合实际的问题“这个列表如果用 index 作为 key会有什么问题”我结合一个表格拖拽排序的场景做了说明当列表顺序变化时index 作为 key 会导致 Vue 复用了错误的 DOM 节点从而产生状态错乱。这个知识点不难但尽量要用项目里的真实场景来回答不要只背结论。最后又考了一道手写题实现一个发布订阅 EventEmitter支持 on、emit、off、once 方法。这道题考察的是最基础的 JS 能力但要注意细节once包装后的函数必须在触发后自动移除off要能删除指定的监听器并且要能在触发过程中安全地移除。我写完后面试官让我自己测试几个用例我漏掉了“重复引用同一个函数注册多次再移除”的边界情况被指出来了。2.3 三面综合面与场景设计三面的风格和前两面完全不一样面试官没有按常规套路出牌而是直接抛了一个场景设计题“如果要你给我们现在的新闻推荐页面设计一个前端错误监控系统你会怎么做”这种开放题没有标准答案考察的是你对整个前端监控链路的理解。我当时的回答分成四个部分采集、上报、聚合、告警。采集层面用window.addEventListener(error)捕获运行时错误用window.addEventListener(unhandledrejection)捕获 Promise 异常同时利用 Vue 的config.errorHandler捕获组件链路错误上报层面用 1x1 像素的 Image 或者navigator.sendBeacon发送数据因为常规的 XHR 上报在页面崩溃场景下可能发不出去聚合层面需要带上 sourcemap 还原压缩后的源码告警层面按错误采样率和影响用户数做分级处理。面试官听完之后追问了两个点。第一个是“如果页面已经完全白屏你的监控脚本还能生效吗”这让我意识到监控脚本本身也必须提前加载、独立于业务代码不能放在业务 bundle 里。第二个是“如果是跨域的脚本错误错误信息会是什么”这涉及 crossorigin 属性配合 CORS 响应头才能捕获详细信息否则只能看到Script error.。三面还问了我最近在学什么技术、未来的职业规划是什么。这种问题不要说得太空你可以说在学习某个具体的技术点比如“最近在看 Webpack 5 的持久化缓存实现原理”然后讲一下你为什么对这个感兴趣。我当时说的是在学习 TypeScript 在大型前端项目中的类型设计面试官顺藤摸瓜问了一下类型体操的问题好在提前读过一些教程没有卡壳。2.4 HR面比想象中更重要HR面看起来不涉及技术但绝不能掉以轻心。一方面HR面会考察你对岗位的期望是否合理、入职时间是否符合团队要求另一方面HR拥有一票否决权如果发现你有明显的沟通问题或不稳定性因素技术面通过也可能被卡掉。我们那批实习生的 HR面 主要聊了三件事为什么选择字节跳动、实习时间能保证多久、导师和同学如何评价你。关于第一个问题不要只说“薪资高”“平台大”可以结合具体的业务方向或者团队氛围来说。我当时提到自己在校招群和学长那边了解到的字节前端技术氛围比较活跃开源项目和技术分享多所以希望能在这样的环境里成长。这个回答既客观又诚恳。HR面还有一个隐藏考点情绪稳定性和抗压能力。如果前两轮技术面有一轮感觉没发挥好HR会问你是不是觉得很难这时候要坦诚但不过度抱怨保持积极的态度。面试本身就是双向匹配不是非要说自己完美无缺而是要让对方觉得你是一个靠谱、愿意学习、能合得来的人。3. 贯穿全程的高频原理事件循环、浏览器渲染与Vue响应式3.1 事件循环从宏任务和微任务的执行顺序到实际题目事件循环是字节前端面试的常青树一面那道 async/await 输出顺序题只是基础真正拉开差距的是追问。面试官会继续问“setTimeout 那个宏任务的回调执行完了后面又注册了微任务会怎么办”这背后的规则是每个宏任务执行结束后都会清空微任务队列浏览器的渲染更新则是在微任务队列清空之后、下一个宏任务开始之前进行的。理解这个机制最好的方式是看你去看一段代码把每一个数据结构的入队和出队时间记录下来自己走一遍。我刷题时习惯用控制台实际跑一遍然后用process._tickCallback或者浏览器 performance API 去验证。面试的时候一般不允许线上跑但你只要把关键三步讲清楚同步代码先执行、微任务优先于宏任务、await之后的代码会排进微任务队列基本就能应付大部分题目。3.2 浏览器渲染流程与性能优化一个高频组合拳这个考点和事件循环经常组合出现比如“为什么说 setTimeout 不准确”。本质上是因为 setTimeout 的回调可能被前面宏任务的执行时间延迟而浏览器每帧的渲染和事件循环是协同工作的实际触发时机和预期的 delay 之间会有偏差。这类问题从渲染流程切入来解释是最顺畅的。完整的答题思路从输入 URL 开始DNS 解析、TCP 连接、HTTP 请求服务器返回 HTML 后进入渲染进程解析 HTML 构建 DOM 树、解析 CSS 构建 CSSOM、合并成渲染树、计算布局、绘制、合成。面试官最喜欢在“CSS 是否会阻塞渲染”处打断你回答是CSS 会阻塞渲染进程因为渲染树需要 CSSOM 才能生成但可以通过媒体类型、内联关键 CSS、延迟非关键 CSS 等方式优化。在此基础上还能延伸出重排和重绘的概念。我用一个小项目里的优化经验来解答当时我们的数据大屏每分钟刷新一次直接操作 DOM 导致页面卡顿后来改成使用 DocumentFragment 批量更新 DOM并且把高频触发的事件监听改成 requestAnimationFrame 控制绘制频率效果立竿见影。这个案例既展示了渲染优化的知识又证明你把这些知识用在了实际项目里。3.3 Vue 2 响应式原理从 Object.defineProperty 到依赖收集Vue 原理是字节一轮面试的保留节目因为前端团队大量使用 Vue而且能够层层深挖。最好的答题方式是画出一条完整链路data对象在初始化时通过walk递归遍历每个属性被Object.defineProperty变成 getter/setterrender 函数执行时读取属性会触发 getter当前 Watcher 被收集进 Dep属性更新时触发 setterDep 通知所有订阅的 Watcher 执行 update进而触发视图重新渲染。我面试时被追问最多的是两个问题。第一个是data中对象的新增属性和删除属性为什么不是响应式的因为Object.defineProperty只能拦截已有属性所以 Vue 2 提供了Vue.set和Vue.delete来弥补。第二个是数组为什么不能直接通过索引赋值监听到变化因为数组的索引是无限制的对每个索引拦截开销太大Vue 2 选择重写七个改变数组自身的方法来实现响应式。如果你能主动提到 Vue 3 通过 Proxy 完全解决了这些限制会让面试官觉得你有技术展望。3.4 diff 算法和 key 的作用理解组件更新的判断逻辑虚拟 DOM 和 diff 算法是框架题里的硬骨头。大多数人能背出“相同 key 复用、key 应该是唯一的”这句话但换一个场景就问倒了。比如面试官问“如果不传 keydiff 是如何工作的”如果不传 keyVue 会执行就地复用策略通过比较标签名和属性来决定是否复用节点这种情况在某些列表中反而性能不差但会带来状态复用的副作用。所以 key 的作用不是百分之百优化性能而是明确告诉框架“这两个节点是不同的”避免错误复用。我当时结合一个表单场景说明拖拽排序后如果使用 index 作为 key输入框会互相串值因为复用的节点和旧节点状态不一致。后来我把 key 改为列表项的唯一 id这个问题就消失了。这个例子具体、好懂面试官听了通常会接一句“那你知道同层比较是什么意思吗”继续往算法层面深入。4. 算法和手写题的考场细节从刷题方式到临场表达4.1 刷题量与刷题策略不是越多越好关于算法题我能给出的最中肯的建议是不要盲目追求刷题量关键是刷出“手感”和“题型敏感度”。我在两个月里一共刷了大约 200 道力扣题加上剑指 Offer 全部过了一遍。这个量不算大但每道题我都逼自己先思考十分钟再动手写写完再看题解。刷题策略上我是按 tag 分类刷的数组、链表、二叉树、动态规划、字符串、双指针、哈希表。每个 tag 先刷力扣 Hot 100 和剑指 Offer 里的经典题再挑几道中等难度的变形题。字节实习算法题一般就是 easy 到 medium 的难度偶尔会有 hard但多数情况 hard 是给 full-time 面试用的实习岗的算法面更看重基本功和沟通能力。4.2 高频手写题深拷贝、防抖、节流、Promise手写题是前端面试的一个特有环节本质是考察对 API 底层设计的理解和代码组织能力。高频题基本固定深拷贝、防抖节流、Promise.all、EventEmitter、数组去重、instanceof、new 操作符的模拟实现。我建议大家不要只看会写还要能说清楚每一步为什么这么写。以深拷贝为例最容易被忽略的是循环引用问题。我写了第一版function deepClone(obj) { const map new WeakMap(); function clone(target) { if (target null || typeof target ! object) return target; if (map.has(target)) return map.get(target); const result Array.isArray(target) ? [] : {}; map.set(target, result); Object.keys(target).forEach(key { result[key] clone(target[key]); }); return result; } return clone(obj); }用 WeakMap 记录已克隆对象就能解决a.self a这种循环引用导致的死循环。这一段不光是一道题面试官还会追问 WeakMap 和 Map 的区别、为什么这里用 WeakMap假如不了解 GC 相关知识就会卡住。所以手写题不是背代码是背背后的原理。4.3 考场上的思考和沟通方式我发现很多同学在面试写算法时喜欢全程沉默闷头写代码这是大忌。字节的面试官非常看重你的思维过程他们宁可你一开始想的方向不对也不希望你半天没有输出。我的做法是拿到题目后先把理解复述一遍“我现在要找出数组中的众数我可以先用哈希表统计次数然后找到次数最大的那个时间复杂度 O(n)空间复杂度 O(n)。如果希望空间复杂度降到 O(1)我还可以用摩尔投票法。”这样面试官马上能知道你的思路如果他觉得你的方向没问题就会让你继续写如果方向错了他也会主动纠正。写完代码之后一定要主动拿一个测试用例在脑内跑一遍把每一步的结果说出来。这一步非常加分因为这能体现你的代码 verifier 能力。我那次在写“两数之和”时第一版代码通过后面试官追问“如果数组非常大内存装不下怎么办”我从内存角度的回答是如果内存装不下可以用外部排序或者分布式哈希表不过这已经不是前端岗位的主战场了。面试官笑了一下换了一个题目。这种追问不是真的等待标准答案而是想看你的思路边界在哪里。5. 复盘后的避坑清单准备期与面试期最容易翻车的地方5.1 准备期常见的几个坑第一个坑是只看面经不写答案。面经刷多了会给人一种“我都看过”的错觉但面试的时候你会发现那些很熟悉的题目真正开口讲时却吞吞吐吐。我后期强迫自己把每道题写成一篇小短文语音讲一遍再对着镜子讲一遍效果比看十篇面经都好。第二个坑是项目复盘停留在功能描述层面。面试官问你做了某个功能你不要只说“我用 Vue 写了一个表格”而要拆解为什么这么写、有没有对比过其他方案。我最初的项目复盘文档写的全是功能列表后来按照“问题拆解”的方式重写了一遍才真正感受到自己的成长。第三个坑是手写题只背代码模板不理解意图。我记得自己背了很多遍 Promise.all 的实现但被问到“如果其中一个 promise 先 reject 了后面的 promise 会不会继续执行”时我一下子愣住了。Promise.all 是 fail-fast 的有一个 reject 就会立即 reject但其他 promise 仍然会执行只是结果不再被关心。这种细节不写测试用例很难发现。5.2 面试期常见的心态与节奏问题很多同学在面试时容易陷入“一定要答对每一题”的紧张状态其实没必要。字节的面试官更关注候选人面对未知问题时的思考方式。我在三面被问到错误监控中 sourcemap 如何保证安全性时确实没有深入了解过就直接说明“这方面了解得比较浅但我知道可以通过权限校验避免源码泄露”然后反过来请教面试官的正确做法。面试官没有因为这个问题否定我反而主动延伸讲了很多实践细节。面试节奏上我建议每两轮面试之间留出至少一天的间隔让自己有时间整理面经和查漏补缺。如果被连续安排也要在结束后立刻记下忘记的知识点不要拖到第二天否则记忆就模糊了。5.3 最后再分享一个小技巧每次面试结束后我做的第一件事不是盯着流程进度而是立刻打开录音软件把整个面试过程复盘一遍。我不是说要录下面试对话而是趁记忆还热快速记录“哪些题答得比较顺利、哪些题卡壳了、卡壳的原因是什么”。这个习惯陪着我走完了整场求职季到后面几轮面试时我的复盘文档已经整理成了一套自己的思维框架。在我个人看来面试更像是一场知识体系的对账对不上的缺口恰恰是接下来要重点补齐的地方。字节那次面经的最后我在文档里写了一段给自己看的话不要期待所有问题都落在准备范围内但准备得足够深你就有了现场推理的底气。这也是我现在最想告诉你的话。