前言大家好我是木斯佳。相信很多人都感受到了在AI浪潮的席卷之下前端领域的门槛在变高纯粹的“增删改查”岗位正在肉眼可见地减少。曾经热闹非凡的面经分享如今也沉寂了许多。但我们都知道市场的潮水退去留下的才是真正在踏实准备、努力沉淀的人。学习的需求从未消失只是变得更加务实和深入。这个专栏的初衷很简单拒绝过时的、流水线式的PDF引流贴专注于收集和整理当下最新、最真实的前端面试资料。我会在每一份面经和八股文的基础上尝试从面试官的角度去拆解问题背后的逻辑而不仅仅是提供一份静态的背诵答案。无论你是校招还是社招目标是中大厂还是新兴团队只要是真实发生、有价值的面试经历我都会在这个专栏里为你沉淀下来。温馨提示市面上的面经鱼龙混杂甄别真伪、把握时效是我们对抗内卷最有效的武器。面经原文内容面试公司字节跳动面试时间01/0901/13面试岗位前端开发❓面试问题二面前端部分你对前端掌握到什么程度前端需要掌握哪些东西什么是 JS 事件循环机制什么是协商缓存强制缓存和协商缓存有什么区别缓存过期机制是怎么实现的React useState 是什么特性和优势是什么调用 setState 之后 React 内部是怎么处理的使用 setState 有没有发现过渲染失败的问题开发中有没有遇到改了 state 但视图不更新的情况后端/AI部分略过9-15不作答Git merge 和 rebase 区别是什么为什么说 rebase 危险开发时多次 commit如何合并成一个 commit算法题模块依赖编译顺序拓扑排序面试感想作为一名后端选手前端的部分被拷打死了后端的部分全部答出来也挂了二面。面试官前端后端测试AI全都懂非常强。来源牛客网Bug_Maker 字节跳动前端二面·深度解析后端同学视角面试整体画像维度特征面试岗位前端开发候选人是后端背景面试风格全栈式拷打 原理深入型难度评级⭐⭐⭐⭐四星全栈涉及知识比较广但前端部分只能打到三星考察重心前端基础、React原理、缓存机制、Git操作、拓扑排序木木有话说:收录这篇是因为看到up是后端选手被前端岗位捞起来了面试里一些题目也比较有代表性在实习面算比较经典的内容了所以剔除了后端的部分以及AI的部分与其他面经高度重复。有需要的可以专门看AI岗面经逐题深度解析前端部分一、前端掌握程度问题你对前端掌握到什么程度前端需要掌握哪些东西// 前端知识体系后端同学面试前要恶补的// 1. 基础三件套-HTML语义化标签、SEO、Canvas-CSS布局flex/grid、响应式、动画、预处理器-JavaScriptES6、原型链、闭包、事件循环、异步编程// 2. 框架-React/Vue核心原理、生命周期、Hooks、状态管理-至少一个框架要深入// 3. 工程化-Webpack/Vite配置、插件、热更新-Babel编译原理-Git分支管理、协作流程// 4. 浏览器原理-渲染机制重排重绘-缓存策略强缓存、协商缓存-安全XSS、CSRF// 5. 性能优化-加载优化懒加载、代码分割-运行时优化虚拟列表、防抖节流-监控性能指标、错误上报// 6. 网络-HTTP/HTTPS、WebSocket-跨域解决方案二、JS事件循环机制问题什么是 JS 事件循环机制// 事件循环Event Loop是JS处理异步的机制// 1. 执行顺序console.log(1)// 同步setTimeout((){console.log(2)// 宏任务},0)Promise.resolve().then((){console.log(3)// 微任务})console.log(4)// 同步// 输出1, 4, 3, 2// 2. 宏任务 vs 微任务// 宏任务setTimeout、setInterval、I/O、UI渲染// 微任务Promise.then、MutationObserver、queueMicrotask// 3. 详细流程// - 执行同步代码// - 遇到异步任务交给Web API// - 同步代码执行完执行栈清空// - 检查微任务队列执行所有微任务// - 从宏任务队列取一个任务执行// - 重复以上步骤// 4. async/awaitasyncfunctiontest(){console.log(1)awaitconsole.log(2)// await后面的代码相当于Promise.thenconsole.log(3)}console.log(4)test()console.log(5)// 输出4, 1, 2, 5, 3三、协商缓存与强制缓存问题什么是协商缓存强制缓存和协商缓存有什么区别// 1. 强制缓存不发请求// 响应头Cache-Control:max-age3600// 缓存1小时Expires:Wed,21Oct202507:28:00GMT// 适用静态资源带hash的文件// 2. 协商缓存发请求服务器判断// 第一次响应Last-Modified:Wed,21Oct202407:28:00GMTETag:33a64df551...// 后续请求If-Modified-Since:Wed,21Oct202407:28:00GMTIf-None-Match:33a64df551...// 服务器判断// - 未修改 → 304不返回body// - 已修改 → 200 新资源// 3. 区别对比|维度|强制缓存|协商缓存||------|---------|---------||是否发请求|否|是||状态码|200(from cache)|304||控制头|Cache-Control|Last-Modified/ETag||适用场景|长期不变的资源|HTML、API数据|// 4. 最佳实践// HTML协商缓存Cache-Control:no-cache// JS/CSS/图片强缓存 hash文件名// app-8f3c9d.js → 内容变化时hash变化Cache-Control:max-age31536000四、缓存过期机制实现问题缓存过期机制是怎么实现的// 1. 服务端实现// Redis示例// 设置过期时间awaitredis.setex(key,3600,value)// 1小时后过期// 主动删除awaitredis.del(key)// 2. 浏览器端实现// 基于时间的过期localStorage.setItem(timestamp,Date.now())constisExpiredDate.now()-timestamp3600000// 3. CDN实现// CDN节点根据Cache-Control判断// 过期后回源站拉取新资源// 4. 具体算法classCache{constructor(maxAge3600000){this.cachenewMap()this.maxAgemaxAge}set(key,value){this.cache.set(key,{value,timestamp:Date.now()})}get(key){constitemthis.cache.get(key)if(!item)returnnullif(Date.now()-item.timestampthis.maxAge){this.cache.delete(key)// 过期删除returnnull}returnitem.value}}五、React useState问题React useState 是什么特性和优势是什么import{useState}fromreactfunctionCounter(){const[count,setCount]useState(0)return(divp点击了{count}次/pbutton onClick{()setCount(count1)}增加/button/div)}// 1. useState特性// - 返回值[当前状态, 更新函数]// - 初始值只在首次渲染生效// - 更新函数触发组件重新渲染// 2. 优势// - 函数组件有了自己的状态// - 逻辑复用更简单自定义Hook// - 相比class组件代码更简洁// 3. 注意事项// - 更新是异步的console.log(count)// 旧值setCount(count1)console.log(count)// 还是旧值// - 可以使用函数式更新setCount(prevprev1)// 基于上一次的值// - 对象状态要合并const[user,setUser]useState({name:,age:0})setUser(prev({...prev,name:Tom}))// 需要手动合并六、setState内部处理问题调用 setState 之后 React 内部是怎么处理的// setState工作流程// 1. 调用setStatesetCount(count1)// 2. React内部流程// - 将更新加入队列// - 调度更新批量处理// - 协调Reconciliation// - 计算差异Diff// - 提交更新Commit// 3. 简化版实现classComponent{constructor(){this.state{}this.pendingState[]}setState(partialState){this.pendingState.push(partialState)if(!this.isBatching){this.performUpdate()}}performUpdate(){// 合并所有pendingStateconstnextStatethis.pendingState.reduce((state,update)({...state,...(typeofupdatefunction?update(state):update)}),this.state)this.statenextStatethis.pendingState[]// 触发重新渲染this.render()}}// 4. React 18自动批处理functionhandleClick(){setCount(cc1)// 不会立即渲染setFlag(f!f)// 不会立即渲染// React会在事件处理完后批量更新}七、setState渲染失败问题问题使用 setState 有没有发现过渲染失败的问题// 常见渲染失败原因// 1. 对象引用相同const[user,setUser]useState({name:Tom})// ❌ 错误直接修改对象user.nameJerrysetUser(user)// 引用相同React认为没变化不渲染// ✅ 正确创建新对象setUser({...user,name:Jerry})// 2. 数组操作错误const[list,setList]useState([1,2,3])// ❌ 错误直接修改数组list.push(4)setList(list)// ✅ 正确返回新数组setList([...list,4])// 3. 闭包陷阱functionCounter(){const[count,setCount]useState(0)useEffect((){consttimersetInterval((){setCount(count1)// 闭包捕获了初始count},1000)return()clearInterval(timer)},[])// ❌ count是闭包里的旧值// ✅ 正确使用函数式更新setCount(prevprev1)}八、state改了但视图不更新问题开发中有没有遇到改了 state 但视图不更新的情况// 1. 直接修改state最常见const[user,setUser]useState({name:Tom})// ❌user.nameJerrysetUser(user)// ✅setUser({...user,name:Jerry})// 2. 嵌套对象const[state,setState]useState({user:{name:Tom,address:{city:北京}}})// ❌state.user.address.city上海setState(state)// ✅setState({...state,user:{...state.user,address:{...state.user.address,city:上海}}})// 3. key不变化{items.map((item,index)Item key{index}data{item}/)}// ❌ 用index做key列表变化时可能不更新// ✅ 用唯一id{items.map(itemItem key{item.id}data{item}/)}九、Git merge vs rebase问题Git merge 和 rebase 区别是什么为什么说 rebase 危险# 1. git merge# 特点保留分支历史生成merge commitgitcheckout maingitmerge feature# 提交历史# * merge commit# |\# | * feature commit# * | main commit# |/# * base# 2. git rebase# 特点线性历史无merge commitgitcheckout featuregitrebase main# 将feature的提交移动到main之后gitcheckout maingitmerge feature# 快进合并# 提交历史# * feature commit# * main commit# * base# 3. 为什么rebase危险# - 改写历史commit的hash会变# - 如果rebase已经推送到远程的分支# * 别人基于旧历史开发会混乱# * 需要force push可能覆盖他人代码# 4. 使用原则# - 公共分支main/develop用merge# - 个人分支feature用rebase整理后合并# - 永远不要rebase已经推送到公共仓库的分支十、合并多个commit问题开发时多次commit如何合并成一个commit# 1. git rebase -i交互式变基gitrebase-iHEAD~3# 合并最近3个commit# 进入编辑器后将pick改为squashpick 123abc 第一次提交 squash 234bcd 第二次提交# 合并到上一个squash 345cde 第三次提交# 合并到上一个# 保存退出然后编辑新的commit message# 2. git reset 重新提交gitreset--softHEAD~3# 撤销最近3次commit保留修改gitcommit-m新的合并提交# 3. 合并已推送到远程的commitgitrebase-iHEAD~3# 修改后需要force pushgitpush --force-with-lease# 更安全的force push# 4. 只合并最后一个commit到上一个gitcommit--amend# 将暂存区修改合并到上一个commit# 5. 使用场景# - 提交PR/MR前整理commit# - 修复bug的多个小commit合并# - 保持提交历史清晰十一、拓扑排序问题模块依赖编译顺序拓扑排序// 拓扑排序对有向无环图进行排序每个节点出现在它的所有依赖节点之后// 场景编译时确定模块加载顺序// 模块A依赖B和C模块B依赖D模块C依赖D// 1. 数据结构constgraph{A:[B,C],B:[D],C:[D],D:[]}// 2. 拓扑排序实现Kahn算法functiontopologicalSort(graph){constinDegree{}// 入度表constresult[]constqueue[]// 初始化入度for(letnodeingraph){inDegree[node]0}for(letnodeingraph){for(letdepofgraph[node]){inDegree[dep](inDegree[dep]||0)1}}// 将入度为0的节点入队for(letnodeininDegree){if(inDegree[node]0){queue.push(node)}}// BFSwhile(queue.length){constnodequeue.shift()result.push(node)for(letdepofgraph[node]||[]){inDegree[dep]--if(inDegree[dep]0){queue.push(dep)}}}// 检查是否有环if(result.length!Object.keys(graph).length){thrownewError(图中存在环)}returnresult}// 3. DFS实现functiontopologicalSortDFS(graph){constvisitednewSet()conststack[]functiondfs(node){visited.add(node)for(letdepofgraph[node]||[]){if(!visited.has(dep)){dfs(dep)}}stack.push(node)}for(letnodeingraph){if(!visited.has(node)){dfs(node)}}returnstack.reverse()}// 4. 使用示例constordertopologicalSort(graph)console.log(order)// [D, B, C, A] 或 [D, C, B, A]// 5. 应用场景// - 模块打包Webpack确定构建顺序// - 任务调度// - 依赖安装知识点速查表前端部分知识点核心要点前端体系基础三件套、框架、工程化、浏览器原理、性能优化事件循环宏任务/微任务、执行顺序、async/await缓存强缓存(不发请求)、协商缓存(发请求)、304缓存过期服务端过期、本地过期、CDN回源useState状态管理、更新触发渲染、函数式更新setState流程入队→调度→协调→diff→提交渲染失败对象引用相同、闭包陷阱、Hook规则视图不更新直接修改、嵌套对象、key问题git merge/rebasemerge保留历史、rebase线性历史、rebase危险原因合并commitrebase -i、reset、amend、force push拓扑排序Kahn算法、DFS、入度表、环检测 最后一句字节的这场二面后端同学面前端本身就是一次挑战。能答出后端部分说明底子很好前端部分补上在AI 时代还是很吃香的。