前言
大家好,我是木斯佳。
相信很多人都感受到了,在AI浪潮的席卷之下,前端领域的门槛在变高,纯粹的“增删改查”岗位正在肉眼可见地减少。曾经热闹非凡的面经分享,如今也沉寂了许多。但我们都知道,市场的潮水退去,留下的才是真正在踏实准备、努力沉淀的人。学习的需求,从未消失,只是变得更加务实和深入。
这个专栏的初衷很简单:拒绝过时的、流水线式的PDF引流贴,专注于收集和整理当下最新、最真实的前端面试资料。我会在每一份面经和八股文的基础上,尝试从面试官的角度去拆解问题背后的逻辑,而不仅仅是提供一份静态的背诵答案。无论你是校招还是社招,目标是中大厂还是新兴团队,只要是真实发生、有价值的面试经历,我都会在这个专栏里为你沉淀下来。专栏快速地址 
温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。
面经原文内容
📍面试公司:字节跳动 🕐面试时间:近期 💻面试岗位:前端一面 ⏱️面试时长:45分钟 📝面试体验:面试官非常和善,会复述你的回答double check
❓面试问题:
实习项目穿插八股
coding(3道)
来源:牛客网 盐酸不酸
💡 木木有话说(刷前先看)
字节这场一面,是典型的“项目深挖+基础考察+算法”组合。面试官非常友善,会复述你的回答确认理解正确,这种风格下更容易发挥真实水平。尤其值得关注的是“接口降级方案”和“LCP具体数值”,考察的是生产环境的实战思维。(最近的题多讲思路,因为重复题比较多,建议大家看汇总篇,根据汇总篇的热点考点去进行专项复习)
📝 字节前端一面·深度解析
🎯 面试整体画像
| 面试风格 | 项目驱动型 + 友善沟通型 + 实战追问型 |
| 难度评级 | ⭐⭐⭐⭐(四星,覆盖面广,算法有区分度) |
| 考察重心 | 登录鉴权、缓存策略、大文件上传、SSE、性能优化、构建工具、Vue原理、算法 |
| 特殊之处 | 面试官会复述回答确认理解,考察沟通确认能力 |
🔍 逐题深度解析
一、登录页面怎么做的
回答思路:从表单交互、数据提交、token处理、路由守卫等方面说明。
要点:
- 表单校验(手机号/邮箱、密码格式)
- 提交时禁用按钮,防止重复提交
- 调用登录API,获取token
- token存储(cookie/localStorage)
- 登录成功跳转,失败展示错误提示
- 路由守卫:未登录时重定向到登录页
二、JWT存在哪里?为什么放cookie?localStorage怎么携带?
回答思路:对比存储方式的优劣。
存储位置:
- localStorage:需手动在请求头Authorization: Bearer <token>携带
- cookie:设置httpOnly防止XSS,设置Secure和SameSite,请求自动携带
为什么放cookie:
- httpOnly:防止XSS攻击窃取token
- 自动携带:无需手动处理请求头
- SameSite:可防范CSRF
localStorage携带方式:
// 存储在localStorage
localStorage.setItem('token', token)
// 请求拦截器添加
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
三、sessionStorage和localStorage区别
| 生命周期 | 永久(手动清除) | 标签页关闭即失效 |
| 作用域 | 同源 | 同源+同标签页 |
| 容量 | 5-10MB | 5-10MB |
四、除了本地存储,还有其他缓存手段
回答思路:用户答了协商缓存、强缓存,正确。
HTTP缓存:
- 强缓存:Cache-Control: max-age=3600,缓存期间不发请求
- 协商缓存:ETag/If-None-Match,Last-Modified/If-Modified-Since,返回304则复用
其他:CDN缓存、Service Worker缓存、内存缓存(Memory Cache)。
五、强缓存和协商缓存的请求行为、字段、状态码
强缓存:
- 请求行为:缓存有效期内,不发请求,直接从缓存读取
- 状态码:200 (from disk cache) 或 200 (from memory cache)
- 字段:Cache-Control(优先级高)、Expires
协商缓存:
- 请求行为:缓存过期后,发请求验证资源是否变化
- 状态码:304 Not Modified(资源未变化)或 200(资源更新)
- 字段:请求头If-None-Match(对应响应头ETag)、If-Modified-Since(对应Last-Modified)
六、大文件上传怎么做的
回答思路:核心是分片上传、断点续传、并发控制。
流程:
// 分片上传示例
async function uploadFile(file) {
const chunkSize = 1024 * 1024 // 1MB
const chunks = Math.ceil(file.size / chunkSize)
for (let i = 0; i < chunks; i++) {
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize)
const formData = new FormData()
formData.append('chunk', chunk)
formData.append('index', i)
formData.append('total', chunks)
await fetch('/upload', { method: 'POST', body: formData })
}
// 通知合并
await fetch('/merge', { method: 'POST', body: JSON.stringify({ fileName: file.name }) })
}
七、为什么要用SSE
回答思路:SSE适用于服务端向客户端单向推送的场景。
AI对话场景:
- LLM生成是服务端→客户端的单向流
- SSE基于HTTP,实现简单,自动重连
- 比WebSocket轻量,资源开销小
八、SSE连接断开怎么办
回答思路:SSE原生支持自动重连,但生产环境需做增强。
处理方案:
let retryCount = 0
function connectSSE() {
const source = new EventSource('/api/stream')
source.onerror = () => {
source.close()
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
setTimeout(() => {
retryCount++
connectSSE()
}, delay)
}
source.onopen = () => { retryCount = 0 }
}
九、接口降级方案具体是怎么实现的
回答思路:降级是高可用设计,当主接口失败时,使用备用方案。
实现方式:
async function fetchWithFallback() {
try {
const response = await fetch('/api/main', { timeout: 3000 })
return await response.json()
} catch (error) {
// 降级:使用缓存或备用接口
const cached = localStorage.getItem('cachedData')
if (cached) return JSON.parse(cached)
const fallback = await fetch('/api/fallback')
return await fallback.json()
}
}
十、主要观测的性能指标、LCP计算方式与数值
回答思路:核心Web Vitals包括LCP、FID、CLS。
LCP(Largest Contentful Paint):最大内容绘制时间,衡量加载性能。通过PerformanceObserver观测页面中最大的元素(图片、视频、文本块)渲染完成的时间。
计算方式:
new PerformanceObserver((list) => {
const entries = list.getEntries()
const lastEntry = entries[entries.length – 1]
console.log('LCP:', lastEntry.renderTime || lastEntry.loadTime)
}).observe({ entryTypes: ['largest-contentful-paint'] })
数值标准:
- 优秀:≤ 2.5秒
- 需改进:2.5-4秒
- 差:> 4秒
十一、性能优化做了哪些内容
回答思路:从加载、渲染、网络多个维度展开。
优化项:
- 代码分割:路由懒加载、动态导入
- 图片优化:WebP格式、懒加载、响应式图片
- 资源压缩:JS/CSS压缩、Gzip/Brotli
- 缓存策略:强缓存、协商缓存、CDN
- 虚拟滚动:长列表优化
- 减少重排重绘:用transform/opacity做动画
- 预加载:preload关键资源、prefetch低优先级资源
十二、Vite和Webpack的流程、区别
| 开发环境 | 打包所有模块,启动慢 | 利用ESM,直接启动 |
| 热更新 | 重新打包相关模块 | 只更新变更的模块 |
| 生产打包 | 统一打包成bundle | 使用Rollup预打包 |
| 配置复杂度 | 高 | 低 |
流程:
- Webpack:入口→依赖解析→loader转换→打包→输出
- Vite:启动服务器→请求时编译→利用浏览器ESM加载
十三、技术选型:Vue3 vs React
回答思路:结合项目需求说明。
Vue3优势:
- 上手简单,模板语法直观
- 响应式自动依赖收集,开发效率高
- 官方生态完善(Vue Router、Pinia)
React优势:
- 灵活度高,适合复杂交互
- 生态更丰富
- 函数式编程范式
选型原因:团队熟悉度、项目复杂度、社区生态。
十四、Vue3响应式
回答思路:基于Proxy实现,懒递归。
核心:
- reactive:用Proxy代理对象,拦截get/set/deleteProperty
- ref:包装基本类型,通过.value访问
- effect:依赖收集,触发更新
- 懒递归:只有访问嵌套对象时才代理
十五、Vue编译渲染是怎么做的
回答思路:模板 → AST → 渲染函数 → VNode → 真实DOM。
流程:
Coding题一:限制数量的事件调度器(Scheduler)
题目:实现一个并发限制的调度器,最多同时执行2个任务。
class Scheduler {
constructor(limit = 2) {
this.limit = limit
this.running = 0
this.queue = []
}
add(promiseFactory) {
return new Promise((resolve, reject) => {
this.queue.push(() => {
promiseFactory().then(resolve, reject).finally(() => {
this.running—
this.next()
})
})
this.next()
})
}
next() {
if (this.running < this.limit && this.queue.length) {
const task = this.queue.shift()
this.running++
task()
}
}
}
Coding题二:括号生成
题目:给定n对括号,生成所有有效的括号组合。
function generateParenthesis(n) {
const result = []
function backtrack(str, left, right) {
if (str.length === 2 * n) {
result.push(str)
return
}
if (left < n) backtrack(str + '(', left + 1, right)
if (right < left) backtrack(str + ')', left, right + 1)
}
backtrack('', 0, 0)
return result
}
时间复杂度:卡特兰数,O(4^n / √n)
Coding题三:版本序列号排序
题目:对版本号数组进行排序,如['1.0.1', '1.0', '1.0.2', '2.0']。
function compareVersion(v1, v2) {
const parts1 = v1.split('.').map(Number)
const parts2 = v2.split('.').map(Number)
const maxLen = Math.max(parts1.length, parts2.length)
for (let i = 0; i < maxLen; i++) {
const num1 = parts1[i] || 0
const num2 = parts2[i] || 0
if (num1 !== num2) return num1 – num2
}
return 0
}
function sortVersions(versions) {
return versions.sort(compareVersion)
}
📚 知识点速查表
| 登录鉴权 | JWT存储(cookie/localStorage)、请求拦截器携带 |
| 存储区别 | localStorage永久/sessionStorage标签页/cookie自动携带 |
| HTTP缓存 | 强缓存(Cache-Control/Expires)、协商缓存(ETag/Last-Modified) |
| 大文件上传 | 分片(Blob.slice)、断点续传、秒传(文件哈希) |
| SSE | 单向推送、自动重连、断线恢复(Last-Event-ID) |
| 接口降级 | 超时降级、熔断、限流、缓存兜底 |
| LCP | 最大内容绘制,≤2.5s优秀,PerformanceObserver获取 |
| 性能优化 | 代码分割、图片优化、虚拟滚动、预加载 |
| Vite vs Webpack | 开发环境ESM/打包、热更新机制、生产构建 |
| Vue3响应式 | Proxy代理、懒递归、ref包装基本类型 |
| Vue编译 | 模板→AST→渲染函数→VNode→DOM |
| 调度器 | 并发限制、任务队列 |
| 括号生成 | 回溯、左右括号数量约束 |
| 版本排序 | 拆分解析、补0对齐 |
📌 最后一句:
字节这场一面,风格友善但内容硬核。从登录JWT存储、大文件上传,到LCP性能指标、Vue编译原理,再到三道算法题,全面考察了项目实战能力和计算机基础。面试官会复述你的回答double check,这种沟通方式值得学习——确认理解一致,是高效协作的关键。能通过这样的面试,说明你不仅会写代码,更能清晰地表达技术决策。

