前言
大家好,我是木斯佳。
相信很多人都感受到了,在AI浪潮的席卷之下,前端领域的门槛在变高,纯粹的"增删改查"岗位正在肉眼可见地减少。曾经热闹非凡的面经分享,如今也沉寂了许多。但我们都知道,市场的潮水退去,留下的才是真正在踏实准备、努力沉淀的人。学习的需求,从未消失,只是变得更加务实和深入。
这个专栏的初衷很简单:拒绝过时的、流水线式的PDF引流贴,专注于收集和整理当下最新、最真实的前端面试资料。我会在每一份面经和八股文的基础上,尝试从面试官的角度去拆解问题背后的逻辑,而不仅仅是提供一份静态的背诵答案。无论你是校招还是社招,目标是中大厂还是新兴团队,只要是真实发生、有价值的面试经历,我都会在这个专栏里为你沉淀下来。专栏快速链接

温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。
面经原文内容
📍面试公司:腾讯
🕐面试时间:近期
💻面试岗位:前端一面
❓面试问题:
- 自我介绍
- Monorepo 架构实现(代码位置 + 配置)
- AI 模块如何拆分(放在哪里)
- 双 token 如何实现 + 自动续期 + 无感刷新
- 无感刷新下请求队列问题(分页错乱):双token无感刷新下,如果存储的历史请求队列中的请求再次请求,会有什么问题?比如分页请求数据,原本请求page=1,此时access_token过期,接着再次点击下一页,导致在分页的情况下有两次请求(请求到不同的页下的数据),原本只希望出现一页的数据,此时出现两页的数据,如何解决这个bug问题?
- 项目与市面产品差异,优点
- AI对话是否与项目有强关联的关系?为什么这样?
- AI对话下上下文记忆的实现
- 详细介绍SSE,以及为什么不用websocket,他们两个有什么区别?常见的chatgpt等对话模式中为什么要用SSE,解决了什么问题?
- 为什么AI智能对话需要使用流式输出?不用其他的
- 介绍前端埋点SDK如何实现
- 介绍埋点中的web性能指标(FP/FCP/LCP/INP/CLS)的每个的详细含义
- 说明以上性能指标中你认为哪一个是最重要的?为什么?
- 讲述如何优化spa应用白屏,从哪几个方面可以优化这个问题?
- 说说对于你使用过的vue,react,svelte这三个框架,更擅长,喜欢使用哪一个框架,为什么?
- 说说为什么在react框架中为什么需要使用单标签?原理是什么?
- 详细说说缓存机制
- https 建立连接的原理,如何建立连接
- 说说http有哪几个版本,以及他们的不同之处(区别)
- 使用过哪些 AI 工具或者 AI 辅助开发的编辑器?
来源:牛客网 寻觅流光
💡 木木有话说(刷前先看)
腾讯这场一面,是一份"深度+广度"兼备的高质量面经。现在大厂对前端的要求好像越来越高了。从网络到项目基建到AI,属于什么都问类型的。
📝 腾讯前端一面·深度解析
🎯 面试整体画像
| 维度 | 特征 |
|---|---|
| 面试风格 | 工程实战型 + 深度追问型 + 场景设计型 |
| 难度评级 | ⭐⭐⭐⭐(四星,涉及复杂业务场景和底层原理) |
| 考察重心 | 工程化(Monorepo/模块拆分)、鉴权(双token/无感刷新)、AI对话(SSE/上下文)、性能(埋点/指标/白屏)、框架原理、网络协议 |
| 特殊之处 | 第5题的分页错乱bug非常真实,考察业务场景下的问题解决能力 |
🔍 逐题深度解析
二、Monorepo架构实现
回答思路:Monorepo是用一个仓库管理多个项目/包。
常用工具:
- pnpm workspace(推荐):节省磁盘空间,严格依赖管理
- Nx:智能构建缓存,适合大型项目
- Turborepo:增量构建,缓存优化
- Lerna(较老)
目录结构:
my-monorepo/
├── packages/
│ ├── shared/ # 共享工具库
│ ├── components/ # 公共组件库
│ ├── ai-module/ # AI模块
│ └── web-app/ # 主应用
├── pnpm-workspace.yaml
└── package.json
pnpm-workspace.yaml:
yaml
packages:
- 'packages/*'
三、AI模块如何拆分
回答思路 :按职责拆分,放在packages/ai-module。
拆分原则:
- 核心层:AI SDK封装(调用API、SSE连接)
- 业务层:提示词管理、对话上下文
- UI层:对话组件、消息渲染
- 工具层:函数调用(Tool Calling)实现
typescript
// 目录结构
ai-module/
├── core/
│ ├── client.ts # API客户端
│ └── stream.ts # SSE处理
├── prompts/
│ └── templates.ts # 提示词模板
├── hooks/
│ └── useChat.ts # React/Vue Hook
├── components/
│ └── ChatBox.tsx # 对话组件
└── index.ts
四、双token实现 + 自动续期 + 无感刷新
回答思路:access_token(短期)+ refresh_token(长期)。
流程:
- 登录成功,服务端返回access_token(如2小时过期)和refresh_token(如7天过期)
- 前端存储token(通常access_token在内存,refresh_token在httpOnly cookie)
- 请求携带access_token
- access_token过期时,接口返回401
- 前端调用刷新接口,携带refresh_token换取新access_token
- 重试原请求
无感刷新实现:
javascript
let isRefreshing = false
let pendingRequests = []
axios.interceptors.response.use(
response => response,
async error => {
if (error.response?.status === 401 && !error.config._retry) {
if (isRefreshing) {
// 等待刷新完成
return new Promise(resolve => {
pendingRequests.push(() => resolve(axios(error.config)))
})
}
error.config._retry = true
isRefreshing = true
try {
const { access_token } = await refreshToken()
setAccessToken(access_token)
// 重试所有等待的请求
pendingRequests.forEach(cb => cb())
pendingRequests = []
return axios(error.config)
} catch {
// 刷新失败,跳转登录
redirectToLogin()
} finally {
isRefreshing = false
}
}
return Promise.reject(error)
}
)
五、无感刷新下的请求队列问题(分页错乱)
问题场景:
- 用户请求第1页数据(page=1),此时access_token过期,请求进入等待队列
- 用户继续点击下一页(page=2),也进入等待队列
- token刷新成功后,两个请求同时发出,页面同时显示两页数据(错乱)
解决方案:
方案1:请求去重(推荐)
javascript
// 为每个请求生成唯一key,相同key的请求只保留最后一个
const requestMap = new Map()
function dedupeRequest(config) {
const key = `${config.method}_${config.url}_${JSON.stringify(config.params)}`
if (requestMap.has(key)) {
// 取消上一个相同请求
requestMap.get(key).cancel()
}
const cancelToken = new CancelToken(c => {
requestMap.set(key, { cancel: c })
})
return { ...config, cancelToken }
}
方案2:请求队列只保留最新
javascript
let latestRequest = null
function enqueueRequest(requestFn) {
latestRequest = requestFn
}
function flushQueue() {
if (latestRequest) {
latestRequest()
latestRequest = null
}
}
方案3:禁用刷新期间的交互:刷新时显示loading遮罩,禁止用户操作。
六~七(项目差异与AI关联):略
八、AI对话上下文记忆的实现
回答思路:核心是将历史对话作为上下文传递给模型。
实现方式:
- 前端维护消息数组 :
messages = [{ role: 'user', content: '...' }, { role: 'assistant', content: '...' }] - 每次请求携带历史消息 :
POST /api/chat { messages: [...messages, newUserMessage] } - 限制上下文长度:超出token限制时,截断或压缩早期消息
- 摘要压缩:历史过长时,调用模型生成摘要,替代原始消息
javascript
const MAX_TOKENS = 4000
function buildContext(messages, newMessage) {
let context = [...messages, newMessage]
let tokens = estimateTokens(context)
while (tokens > MAX_TOKENS && context.length > 1) {
// 移除最早的非系统消息
const removed = context.shift()
if (removed.role !== 'system') {
tokens = estimateTokens(context)
}
}
return context
}
九、SSE详解与WebSocket区别
回答思路:参考之前面经。
区别:
| 维度 | SSE | WebSocket |
|---|---|---|
| 方向 | 单向(服务端→客户端) | 双向 |
| 协议 | HTTP | WS/WSS |
| 自动重连 | 内置 | 手动实现 |
| 数据格式 | 文本(UTF-8) | 文本/二进制 |
为什么ChatGPT用SSE:
- 对话是单向流(模型生成→用户接收),不需要双向
- 实现简单,基于HTTP
- 自动重连,网络波动时体验好
十、为什么AI对话需要流式输出
回答思路:用户体验 + 实时反馈。
原因:
- 减少等待焦虑:逐字输出让用户感知到"模型在工作"
- 提前展示结果:用户可以提前阅读,不必等全部生成
- 可中断性:用户发现回答不对时可以提前停止
- 长文本友好:生成1000字不需要等待10秒才看到内容
十一、前端埋点SDK实现
回答思路:一个完整的埋点SDK需要包含数据采集、上报、配置等模块。
核心功能:
javascript
class Tracker {
constructor(options) {
this.appId = options.appId
this.reportUrl = options.reportUrl
this.queue = []
this.timer = null
}
// 初始化:监听全局错误、路由变化
init() {
this.listenErrors()
this.listenRoute()
this.startBatchTimer()
}
// 手动埋点
track(eventName, properties = {}) {
this.queue.push({
event: eventName,
properties,
timestamp: Date.now(),
url: location.href,
ua: navigator.userAgent
})
}
// 批量上报
batchReport() {
if (this.queue.length === 0) return
const data = [...this.queue]
this.queue = []
navigator.sendBeacon(this.reportUrl, JSON.stringify(data))
}
// 定时批量发送
startBatchTimer() {
this.timer = setInterval(() => this.batchReport(), 3000)
}
// 页面关闭时立即发送
beforeUnload() {
this.batchReport()
}
// 监听性能指标
reportPerformance() {
const perf = performance.getEntriesByType('navigation')[0]
this.track('page_performance', {
dns: perf.domainLookupEnd - perf.domainLookupStart,
tcp: perf.connectEnd - perf.connectStart,
ttfb: perf.responseStart - perf.requestStart,
domReady: perf.domContentLoadedEventEnd - perf.fetchStart,
load: perf.loadEventEnd - perf.fetchStart
})
}
}
十二、Web性能指标详解
| 指标 | 全称 | 含义 | 标准 |
|---|---|---|---|
| FP | First Paint | 第一个像素绘制到屏幕 | 无硬性标准 |
| FCP | First Contentful Paint | 第一个内容(文本/图片)绘制 | ≤1.8s |
| LCP | Largest Contentful Paint | 最大内容绘制完成 | ≤2.5s |
| INP | Interaction to Next Paint | 交互延迟(点击/键盘响应) | ≤200ms |
| CLS | Cumulative Layout Shift | 累计布局偏移 | ≤0.1 |
获取方式:
javascript
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime)
}
}).observe({ entryTypes: ['paint', 'largest-contentful-paint', 'layout-shift'] })
十三、最重要的性能指标
回答思路:取决于业务类型,没有标准答案。
示例回答 :
"对于内容型网站(新闻、博客),我认为LCP最重要 ,因为它直接影响用户看到主要内容的速度。对于交互型应用(后台管理系统),INP最重要,因为用户需要频繁点击、输入,响应延迟会严重影响操作体验。"
十四、优化SPA应用白屏
回答思路:白屏是指从输入URL到看到内容的时间。
优化方向:
-
减少首屏资源大小
- 路由懒加载:
React.lazy() - 代码分割:按页面拆分chunk
- Tree shaking:移除未使用代码
- 路由懒加载:
-
优化加载顺序
- 关键CSS内联
- 非关键脚本异步加载(
async/defer) - 预加载关键资源(
<link rel="preload">)
-
骨架屏:在内容加载前展示占位图,减少用户感知等待
-
服务端渲染(SSR):直出HTML,无需等待JS执行
-
使用缓存:强缓存、Service Worker缓存静态资源
-
CDN加速:静态资源走CDN
十五、Vue、React、Svelte框架对比(主观)
回答思路:根据实际使用情况回答,展现思考。
对比:
- React:生态最丰富,适合大型应用;需要手动优化(memo/useMemo)
- Vue:上手简单,响应式自动依赖收集;官方生态完善
- Svelte:编译时框架,无虚拟DOM,打包体积小;适合中小型项目
十六、React中为什么需要单标签(Fragment)
回答思路:React组件必须返回单个根元素。
原因:React的虚拟DOM diff算法基于树结构,每个组件返回的JSX必须是一个节点,否则无法进行diff比较。
解决方案:
- 使用
<div>包裹(会多一层DOM) - 使用
<React.Fragment>或<> </>(不产生额外DOM节点)
jsx
// 错误:相邻JSX元素
return (
<h1>Title</h1>
<p>Content</p>
)
// 正确:用Fragment包裹
return (
<>
<h1>Title</h1>
<p>Content</p>
</>
)
十七、缓存机制
回答思路:从浏览器缓存、HTTP缓存、服务端缓存多维度说明。
浏览器缓存:
- localStorage/sessionStorage:键值对存储
- IndexedDB:大量结构化数据
HTTP缓存:
- 强缓存 :
Cache-Control: max-age=3600,缓存期间不发请求 - 协商缓存 :
ETag/If-None-Match,Last-Modified/If-Modified-Since,返回304
CDN缓存:边缘节点缓存静态资源
Service Worker缓存:拦截请求,返回缓存,支持离线
十八、HTTPS建立连接原理
回答思路:HTTPS = HTTP + TLS/SSL。
TLS握手流程:
- Client Hello:客户端发送支持的加密套件、随机数
- Server Hello:服务端选择套件、发送服务端随机数 + 证书
- 验证证书:客户端验证证书有效性
- 密钥交换:客户端生成Pre-Master Secret,用公钥加密发送
- 生成会话密钥:双方用随机数 + Pre-Master生成对称密钥
- 加密通信:后续数据用会话密钥加密传输
关键点:非对称加密用于交换密钥,对称加密用于实际数据传输。
十九、HTTP版本区别
| 版本 | 特点 | 问题 |
|---|---|---|
| HTTP/1.0 | 短连接,每次请求建立TCP | 效率低 |
| HTTP/1.1 | 长连接(Keep-Alive)、管道化 | 队头阻塞 |
| HTTP/2 | 多路复用、头部压缩、服务器推送 | 仍受TCP队头阻塞影响 |
| HTTP/3 | 基于UDP(QUIC协议),0-RTT连接 | 较新,兼容性待完善 |
多路复用:一个TCP连接上可以并发多个请求和响应。
二十、AI工具使用
回答思路:诚实回答使用过的工具。
常见工具:
- Copilot/Cursor:代码补全、生成
- ChatGPT/Claude:调试问题、学习、代码审查
- 通义灵码:国内替代
- V0/bolt.new:UI生成
使用方式:生成重复性代码、调试错误、优化建议、学习新技术。
📚 知识点速查表
| 知识点 | 核心要点 |
|---|---|
| Monorepo | pnpm workspace/Nx/Turborepo,按功能拆分packages |
| 双token | access_token短期 + refresh_token长期,401自动刷新 |
| 分页错乱 | 刷新期间多个请求堆积导致数据错乱,解决方案:请求去重、禁用交互 |
| 上下文记忆 | 维护messages数组,超长时截断或摘要压缩 |
| SSE vs WS | 单向/双向、HTTP/WS协议、自动重连、AI场景选SSE |
| 流式输出 | 减少焦虑、提前展示、可中断 |
| 埋点SDK | 数据采集、批量上报、sendBeacon、性能监听 |
| 性能指标 | FP/FCP/LCP/INP/CLS,LCP内容型最重要、INP交互型最重要 |
| SPA白屏优化 | 懒加载、内联CSS、骨架屏、SSR、缓存、CDN |
| Fragment | React需单根节点,Fragment不产生额外DOM |
| 缓存机制 | 本地存储、HTTP缓存(强缓存/协商缓存)、CDN、Service Worker |
| HTTPS握手 | 非对称交换密钥,对称加密传输 |
| HTTP版本 | 1.1长连接、2多路复用、3基于UDP |
📌 最后一句:
腾讯这场一面,是一场"实战派"面试。从双token无感刷新的分页错乱bug,到AI对话的上下文记忆,从埋点SDK设计到HTTP版本演进,每一题都在考察你是否具备解决真实业务问题的能力。面试官不是在考八股,而是在看你的工程化思维和系统设计能力。能扛住这样的追问,说明你离"生产级"前端工程师又近了一步。