# 2026 前端面试题大全:最新面经与框架真题
这份 2026 前端面试题与面经按技术方向持续更新,每道题都提供 30 秒速记、真实示例代码、可交互 DEMO 和面试官追问,适合校招、社招以及中高级前端面试前集中复习。
2026 年的前端面试没有放弃 JavaScript、CSS、浏览器和网络基础,但更强调把原理放进真实项目:排查 React 闭包与渲染性能、解释 Vue 3 响应式、选择 Webpack 或 Vite、划分 Next.js Server Component,以及处理大模型流式输出和审查 AI 生成代码。
# 2026 前端面试题分类
- 基础核心:JavaScript 与浏览器、CSS 与现代布局、TypeScript、浏览器与网络进阶
- 主流框架:React 19 与 Next.js、Vue 3、Angular、Svelte 与 Nuxt
- 工程化:Webpack、Vite、Monorepo 与包工程、测试与质量
- 渲染与架构:服务端渲染与同构、微前端与模块联邦、性能与安全
- 跨端开发:Electron、React Native、Flutter、小程序、Taro 与跨端
- AI 与服务端:AI 前端高频题、AI 应用工程、Node.js 与服务端 JavaScript
如果复习时间有限,可以先按前端面试题复习路线补基础,再用本页做 2026 高频场景自测;临近面试时再结合真实公司前端面经模拟追问。
# JavaScript 与浏览器
# 微任务为什么可能让页面“卡死”?
30 秒速记
- 一轮同步任务结束后,浏览器会清空微任务队列,再决定是否渲染和执行下一个宏任务
- 微任务执行时继续递归追加微任务,队列就可能一直清不完,定时器、输入和绘制都被饿死
Promise.then、queueMicrotask都能制造这种问题;关键不是单次任务长,而是队列没有让出控制权- 修复要把长工作切片并主动让到新的任务,生产中还应设置批量上限和取消条件
微任务可能让页面“卡死”,是因为浏览器通常要清空当前微任务队列后,才有机会继续渲染或处理后续任务。 如果微任务执行时不断用 Promise.then 或 queueMicrotask 追加新微任务,队列就迟迟无法清空,绘制、输入和定时器都会等待。问题不一定是单次计算很慢,而是代码始终没有让出控制权。长工作应分批执行,并设置处理上限或取消条件,必要时切换到新的任务再继续。
# React 中的闭包为什么会读到旧状态?
30 秒速记
- 每次渲染都会创建一套新的变量与函数,旧回调闭包捕获的是那次渲染的状态快照
setInterval、异步请求和事件订阅寿命较长,最容易暴露旧闭包- 只依赖前值时用函数式更新;需要读取最新值时可用
ref,但不要用它绕过正常渲染 useEffectEvent适合“读取最新值但不希望它触发 Effect 重连”的事件逻辑,使用时要遵守当前React版本约束
React 每次渲染都会创建新的变量和函数,旧回调闭包保存的是当次渲染的状态快照,所以会读到旧状态。 这个问题常见于 setInterval、异步请求和事件订阅,因为它们可能在后续渲染完成后才执行。只根据前值更新时,我一般用函数式更新;确实要读取最新值时可以用 ref,但不应借它绕过正常渲染。若事件逻辑要读最新值、又不希望触发 Effect 重连,可考虑 useEffectEvent,同时遵守当前 React 版本的约束。
计数器只需要前值,函数式更新最直接;它接收提交时的最新状态,因此 Effect 不必依赖 count,也不会每次重建定时器。
import { useEffect, useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
useEffect(() => {
const timer = window.setInterval(() => {
setCount((current) => current + 1)
}, 1000)
return () => window.clearInterval(timer)
}, [])
return <output>{count}</output>
}
不要机械地把每个值塞进依赖数组。先判断 Effect 是否应该随它重新同步;如果只是回调内部要读最新值,拆分事件逻辑比反复退订、重订更准确。
面试官追问
追问 1计数器页面的 setInterval 每秒执行 setCount(count + 1),界面却一直停在 1;既然回调持续运行,为什么它没有读到新的 count?
定时器回调捕获的是创建该 Effect 时那次渲染中的 count,持续执行不等于闭包会自动切换到后续渲染。这里只依赖前值,应写成 setCount(current => current + 1),让 React 在提交更新时提供最新状态。
追问 2监控面板有十几个计数器,如果把 count 加进依赖数组也能递增,为什么代码评审仍可能要求改成函数式更新?
把 count 加入依赖会在每次变化后清理并重建定时器,行为虽正确,却把“读取前值”误建模成了“重新同步外部系统”。函数式更新可让 Effect 保持空依赖,减少计时器重建及时间漂移风险。
追问 3聊天页连接只由 roomId 决定,但收到消息时要按最新 theme 显示通知;把 theme 放进依赖后每次换肤都会重连,边界该怎么拆?
连接 Effect 应只依赖真正决定连接生命周期的 roomId,主题只属于消息回调中的展示逻辑。应把读取最新主题的事件逻辑与订阅过程拆开,避免为了更新通知样式而反复退订、重订。
追问 4线上偶发出现同一房间收到两份消息,你怀疑是为解决旧闭包而频繁重建订阅,会怎样验证?
先记录 Effect 建立、清理时的 roomId 和订阅实例标识,再核对依赖变化与服务端连接日志,确认清理是否与重订一一对应。若把非生命周期值塞进依赖,任何主题或展示状态变化都可能扩大重复订阅窗口。
追问 5团队想规定所有异步回调都通过 ref.current 读取状态,避免再讨论依赖数组,这个约定是否合理?
不合理,ref.current 虽能提供最新快照,却绕开响应式数据流,修改它也不会触发渲染。仅在命令式集成或确实需要最新快照时使用;状态只基于前值更新时,函数式更新更清晰、更受 React 调度语义约束。
# 如何实现可取消、不会串结果的搜索防抖?
30 秒速记
- 防抖只减少触发频率,不能自动取消已经发出的旧请求
- 输入变化时既要清定时器,也要
abort上一次请求,避免旧响应覆盖新关键词 - 用本次请求的
AbortController绑定信号;卸载时执行同一套清理 AbortError是预期控制流,不应当成线上异常上报
搜索防抖要同时清理定时器和取消旧请求,因为防抖只能减少触发次数,不能阻止已发出的旧响应覆盖新结果。 每次输入变化时,先 clearTimeout,再调用上一个 AbortController.abort(),并为新请求绑定新的 signal;组件卸载时也复用这套清理逻辑。AbortError 属于预期的取消流程,不需要当作线上异常上报。还要处理空关键词和组合输入法;如果服务端无法真正中断请求,就比较请求序号,只让最新请求更新界面。
export function createSearch(search: (q: string, signal: AbortSignal) => Promise<void>) {
let timer: ReturnType<typeof setTimeout> | undefined
let controller: AbortController | undefined
return (query: string) => {
if (timer) clearTimeout(timer)
controller?.abort()
controller = new AbortController()
const signal = controller.signal
timer = setTimeout(async () => {
try {
await search(query, signal)
} catch (error) {
if (!(error instanceof DOMException && error.name === 'AbortError')) throw error
}
}, 300)
}
}
生产代码还要处理空关键词、组合输入法和服务端不支持中断的情况。即使网络层无法真正取消,也应比较请求序号,只允许最新请求提交 UI。
面试官追问
追问 1商品搜索页先输入 rea,请求较慢,随后输入 react,后一个请求先返回;已经做了 300ms 防抖,为什么列表仍可能被旧结果覆盖?
防抖只减少请求次数,不能保证已发请求按输入顺序返回,因此 rea 仍可能最后提交 UI。每次新输入应清除定时器并中止旧请求;即使无法真正中断网络,也要比较请求序号,只接纳最新结果。
追问 2封装 createSearch 时,新关键词到来应在定时器回调里再创建 AbortController,还是输入发生时就创建并取消旧控制器?
应在新输入发生时立即清除旧定时器、取消旧控制器,再为本次查询创建 AbortController
