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

温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。
面经原文内容
📍面试公司:质谱华章
🕐面试时间:5月14日,30分钟
💻面试岗位:前端
📝面试体验:未提及
❓面试问题:
- 自我介绍
- 讲两个实习期间的项目、功能和核心流程
- 实习期间有成长的地方、难点亮点、印象深刻的地方
- 对react的理解、有哪些优点和缺点(虚拟dom、fiber架构、diff算法、jsx、react生态等)
- 做过哪些性能优化
- 场景题:类似抖音视频左右滑动,初始化有很多个视频,怎么保证首次快速加载
- 左右滑动怎么保证视频加载不卡顿、下次用户打开网页怎么保证视频快速加载
- LocalStorage存储内存有上限,了解过其他存储方式么?(IndexDB)
- 用到过Tailwind么?
- 多端适配有做过么、怎么做的、有哪些方案?(媒体查询、flex/grid响应式布局、wv/wh相对单位)
- 在各种屏幕尺寸如何保证某个元素的宽高尺寸比例保持一致(说了浏览器的缩放倍率、css的aspect-ratio属性、以及图片多个尺寸搭配source和size适配)
- 能实习多久
- 反问技术栈和项目
来源:牛客网 求求了快给孩子天降一个offer吧
💡 木木有话说(刷前先看)
这篇面经难度不大中规中矩,围绕一个具体音视频优化场景展开。偏向移动端开发,可以作为补充了解。
📝 质谱华章前端一面·深度解析
🎯 面试整体画像
| 维度 | 特征 |
|---|---|
| 面试风格 | 场景实战型 + 性能优化型 + 短视频专项型 |
| 难度评级 | ⭐⭐⭐(三星半,视频场景优化有区分度) |
| 考察重心 | React理解、性能优化、视频加载策略、本地存储、响应式布局 |
| 特殊之处 | 围绕"抖音左右滑动视频"场景连续追问加载和缓存策略 |
🔍 逐题深度解析
四、对React的理解,优点和缺点
回答思路:从核心概念、设计哲学、优缺点三个维度展开。
核心概念:
- 虚拟DOM:通过JS对象描述UI,diff算法对比变化,批量更新真实DOM。虚拟DOM的优势在于将多次DOM操作合并为一次,减少浏览器重排重绘。
- Fiber架构 :React 16引入的可中断异步渲染架构,将渲染任务拆分成多个小任务,支持优先级调度,解决了长任务卡顿问题。核心是
requestIdleCallback的polyfill实现。 - Diff算法:同层比较、key优化、类型决定策略。时间复杂度从O(n³)降到O(n)。
- JSX :声明式UI,JavaScript语法扩展,编译时转为
React.createElement调用。 - Hooks :函数组件状态管理,解决了类组件的this绑定、逻辑复用困难等问题。核心Hooks包括
useState、useEffect、useContext、useReducer等。
优点:
- 声明式编程,UI与状态绑定,代码可预测性强
- 组件化开发,复用性高
- 生态丰富(Next.js、React Native、Redux、TanStack Query等)
- 性能优秀(虚拟DOM + Fiber调度)
- 社区活跃,学习资源多
缺点:
- 学习曲线陡峭(JSX、Hooks规则、依赖数组、手动优化)
- 包体积较大(react-dom ~40KB,加上react ~120KB)
- 频繁更新需手动优化(
React.memo、useMemo、useCallback) - 默认的渲染行为是"父组件更新导致所有子组件重渲染",需要额外优化
五、做过哪些性能优化
回答思路:从加载优化、渲染优化、网络优化、运行时优化四个维度回答,每个维度列举2-3个具体手段。
| 方向 | 具体手段 | 效果 |
|---|---|---|
| 加载优化 | 代码分割(React.lazy + Suspense)、图片懒加载、路由懒加载、资源预加载(<link rel="preload>) |
减少首屏资源体积,提升FCP/LCP |
| 渲染优化 | React.memo避免子组件不必要重渲染、useMemo/useCallback缓存值和函数、虚拟滚动(react-window)、避免匿名函数作为props |
减少渲染次数,提升滚动帧率 |
| 网络优化 | HTTP缓存(强缓存Cache-Control、协商缓存ETag)、CDN加速、Gzip/Brotli压缩、接口聚合 |
减少网络传输时间,降低TTFB |
| 运行时优化 | 防抖节流(输入框搜索、滚动加载)、Web Worker处理耗时计算、避免内存泄漏(清理定时器、解绑事件) | 保持UI响应流畅 |
可拓展回答:如果项目中有具体的性能优化案例,可以举例说明。例如:"在实习项目中,我通过React.memo和useCallback优化了一个渲染2000条数据的列表,渲染时间从300ms降到50ms。"
六、场景题:抖音视频左右滑动,初始化有很多个视频,怎么保证首次快速加载
问题分析:类似抖音的短视频滑动场景,用户打开页面时可能有几十甚至上百个视频。如果一次性加载所有视频,会导致首屏加载极慢、内存爆炸、网络请求堆积。
核心策略 :首屏只加载可视区内容,其他内容懒加载。
完整优化方案:
| 策略 | 具体实现 | 效果 |
|---|---|---|
| 首屏只渲染可视区域视频 | 使用虚拟滚动(react-window或自定义实现),只渲染当前屏幕的1-2个视频 | DOM节点数从100+降到10以内 |
| 视频源懒加载 | 视频的src属性先用占位符,IntersectionObserver检测进入可视区后再设置真正的src |
减少首屏网络请求 |
| 缩略图占位 | 先用低质量图片(poster)占位,用户播放时再加载高清视频 | 视觉上快速填充,避免白屏 |
| 预加载下一个视频 | 用户滑动的瞬间,提前加载下一个视频的元数据或低码率流 | 滑动后快速播放,无缝体验 |
| 分页加载数据 | 初始只请求前10条视频元数据,滚动触底后再加载下一页 | 减少首屏数据量,降低接口压力 |
javascript
// 视频懒加载示例
const videoObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const video = entry.target
video.src = video.dataset.src // 设置真正的视频源
videoObserver.unobserve(video)
}
})
}, { rootMargin: '100px' }) // 提前100px预加载
document.querySelectorAll('video[data-src]').forEach(video => {
videoObserver.observe(video)
})
七、左右滑动保证视频加载不卡顿 + 下次打开快速加载
7.1 保证滑动不卡顿
问题分析:左右滑动时,如果用户滑动速度很快,当前视频尚未加载完成就要切换下一个,容易出现卡顿或黑屏。
优化策略:
| 策略 | 具体实现 |
|---|---|
| 预加载后续视频 | 监听滑动方向,提前加载下一个视频的元数据或低码率流(如仅加载音频或首帧) |
| 视频元素复用 | 维护一个视频元素池,滑动时只更换src而非重建DOM节点 |
| 离开可视区暂停播放 | 监听页面可见性API(document.visibilityState),离开时暂停播放,释放资源 |
| 限制同时播放数量 | 只允许1-2个视频同时播放,其他暂停,避免资源竞争 |
| 降低码率 | 移动网络下使用低码率视频流,减少带宽压力 |
javascript
// 预加载下一个视频
let currentIndex = 0
let preloadTimer = null
function onSwipe(direction) {
const nextIndex = direction === 'right' ? currentIndex + 1 : currentIndex - 1
// 延迟预加载,避免频繁触发
clearTimeout(preloadTimer)
preloadTimer = setTimeout(() => {
preloadVideo(nextIndex) // 预加载下一个视频
}, 300)
}
7.2 下次打开网页快速加载
问题分析:用户第二次访问时,如果每次都要重新下载视频,浪费流量且加载慢。
优化策略:
| 策略 | 具体实现 | 优先级 |
|---|---|---|
| Service Worker缓存 | 拦截视频请求,缓存到CacheStorage,下次优先从缓存读取 | 最高 |
| IndexedDB存储视频分片 | 将视频分片存储到IndexedDB,实现离线播放 | 高 |
| 预判用户行为预缓存 | 根据用户历史观看数据,提前缓存可能观看的视频 | 中 |
| 智能预加载 | 在WiFi环境下,提前缓存接下来的5-10个视频 | 中 |
javascript
// Service Worker缓存视频
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
if (url.pathname.endsWith('.mp4')) {
event.respondWith(
caches.open('video-cache-v1').then(cache => {
return cache.match(event.request).then(response => {
if (response) {
console.log('从缓存返回视频')
return response
}
return fetch(event.request).then(networkResponse => {
// 只缓存完全加载的视频,避免缓存分片
if (networkResponse.ok) {
cache.put(event.request, networkResponse.clone())
}
return networkResponse
})
})
})
)
}
})
八、LocalStorage上限及其他存储方式
LocalStorage上限:约5-10MB(不同浏览器略有差异,移动端通常更小)。
其他存储方式:
| 存储方式 | 容量 | 是否异步 | 适用场景 |
|---|---|---|---|
| IndexedDB | >250MB | 异步(Promise) | 大量结构化数据、视频元数据、离线数据 |
| CacheStorage | 取决于配额 | 异步 | Service Worker配套,缓存网络请求(视频、API响应) |
| File System Access API | 大 | 异步 | 读写本地文件,需用户授权 |
| sessionStorage | 5-10MB | 同步 | 临时数据,标签页关闭即清空 |
| Cookies | 4KB | 同步 | 会话标识、少量用户偏好 |
IndexedDB使用示例:
javascript
// 打开数据库
const request = indexedDB.open('VideoCacheDB', 1)
request.onupgradeneeded = (event) => {
const db = event.target.result
// 创建对象存储空间
db.createObjectStore('videos', { keyPath: 'id' })
db.createObjectStore('metadata', { keyPath: 'videoId' })
}
// 存储视频元数据
const tx = db.transaction(['metadata'], 'readwrite')
const store = tx.objectStore('metadata')
store.put({ videoId: '123', title: '搞笑视频', cacheTime: Date.now() })
九、Tailwind CSS
回答思路:Tailwind是一个原子类CSS框架,通过组合预定义的类名快速构建UI,无需手写CSS。
优点:
- 开发效率高:不用起类名,在HTML/JSX中直接写样式
- 打包体积小:配合PurgeCSS,生产环境只保留使用到的类,通常<10KB
- 设计约束统一:颜色、间距、字体等基于预设的scale,保持一致性
- 响应式内置 :通过
sm:、md:前缀轻松实现响应式 - 暗黑模式支持 :
dark:前缀
缺点:
- HTML臃肿:大量类名导致可读性下降
- 学习成本 :需要记忆类名规则(如
p-4表示padding: 1rem) - 调试困难:浏览器DevTools中看到的是一堆类名,难以定位
- 与组件框架结合 :需要配置插件(如
@tailwindcss/typography)
适用场景:快速原型开发、独立开发者项目、团队有统一设计规范
十、多端适配方案
回答思路:用户回答的"媒体查询、flex/grid响应式布局、vw/vh相对单位"是正确的,可进一步补充。
| 方案 | 说明 | 示例 |
|---|---|---|
| 媒体查询 | 根据屏幕宽度断点,调整布局/字体/隐藏元素 | @media (max-width: 600px) { ... } |
| Flex/Grid | 弹性布局,自动适应容器宽度 | display: flex; flex-wrap: wrap; |
| vw/vh | 相对视口宽高百分比,实现比例适配 | width: 50vw; height: 30vh; |
| REM适配 | 动态设置根字体大小,所有尺寸用rem | document.documentElement.style.fontSize = (clientWidth / 750) * 100 + 'px' |
| 响应式图片 | srcset + sizes,根据屏幕宽度加载不同尺寸图片 |
<img srcset="small.jpg 400w, large.jpg 800w" sizes="(max-width: 600px) 100vw, 50vw"> |
| 容器查询(CSS Container Queries) | 根据父容器大小而非视口调整样式 | @container (min-width: 400px) { ... } |
移动端特有适配:
- 1px边框问题 :
transform: scale(0.5) - 安全区域 :
env(safe-area-inset-bottom)适配iPhone刘海/底部黑条 - 点击延迟 :
touch-action: manipulation或使用fastclick库(已过时)
十一、保证元素宽高比一致
回答思路 :用户回答的aspect-ratio是当前最佳方案,可补充降级方案。
方案1:CSS aspect-ratio(推荐,现代浏览器)
css
.video-container {
aspect-ratio: 16 / 9;
width: 100%;
}
方案2:padding-top百分比(传统方案,兼容性好)
css
.aspect-box {
position: relative;
width: 100%;
padding-top: 56.25%; /* 16:9 = 9/16*100% = 56.25% */
}
.aspect-box > .content {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
}
方案3:object-fit(用于图片/视频)
css
.video {
width: 100%;
height: 100%;
object-fit: cover; /* 或 contain */
}
方案4:响应式图片搭配source
html
<picture>
<source srcset="landscape.jpg" media="(min-aspect-ratio: 16/9)">
<source srcset="square.jpg" media="(min-aspect-ratio: 1/1)">
<img src="portrait.jpg" alt="">
</picture>
方案5:@media按宽高比加载不同资源
css
@media (min-aspect-ratio: 16/9) {
.hero { background-image: url('wide.jpg'); }
}
@media (max-aspect-ratio: 4/3) {
.hero { background-image: url('tall.jpg'); }
}
📚 知识点速查表
| 知识点 | 核心要点 |
|---|---|
| React理解 | 虚拟DOM、Fiber、Diff、JSX、Hooks;声明式+组件化 |
| 性能优化 | 代码分割、懒加载、memo/useMemo、缓存策略、防抖节流 |
| 视频首屏加载 | 可视区懒加载、虚拟滚动、缩略图占位、预加载下一个 |
| 滑动不卡顿 | 预加载后续视频、视频元素复用、离开可视区暂停、限并发 |
| 二次访问加速 | Service Worker缓存、IndexedDB存储、智能预判 |
| IndexedDB | 大容量(>250MB)、异步、适合视频元数据/离线数据 |
| Tailwind | 原子类、快速开发、体积小、HTML臃肿、学习成本 |
| 多端适配 | 媒体查询、flex/grid、vw/vh、REM、响应式图片、容器查询 |
| 固定宽高比 | aspect-ratio、padding-top技巧、object-fit |
📌 最后一句:
质谱华章这场一面,最大的亮点是围绕"抖音式短视频滑动"场景的连续追问。对这类场景不够熟悉的同学,建议动手实现一个简易版短视频滑动组件,将上述优化策略逐一落地,才能真正内化为自己的技能。