字节跳动音视频前端一面面经与性能优化深度解析
字节跳动音视频前端岗位的一方面试真题,涵盖性能优化、工程化及框架原理。内容包括 FCP 渲染耗时缩短方案、ESM 兼容性问题处理、代码分割策略、不定高虚拟列表实现原理、Next.js 多种渲染模式(SSR/SSG/ISR/CSR)对比与应用选择、图片组件优化细节以及 WebP 格式兼容性降级方案。文章通过代码示例和表格解析了面试官考察重点,旨在帮助开发者提升全链路优化能力以应对大厂面试。

字节跳动音视频前端岗位的一方面试真题,涵盖性能优化、工程化及框架原理。内容包括 FCP 渲染耗时缩短方案、ESM 兼容性问题处理、代码分割策略、不定高虚拟列表实现原理、Next.js 多种渲染模式(SSR/SSG/ISR/CSR)对比与应用选择、图片组件优化细节以及 WebP 格式兼容性降级方案。文章通过代码示例和表格解析了面试官考察重点,旨在帮助开发者提升全链路优化能力以应对大厂面试。

| 维度 | 特征 |
|---|---|
| 公司定位 | 字节跳动 - 音视频方向 |
| 面试风格 | 项目深潜型 + 场景追问型 |
| 难度评级 | ⭐⭐⭐⭐(四星) |
| 考察重心 | 性能优化、工程化、Next.js、图片处理 |
面试官的潜台词:音视频业务对性能要求极高,所以 FCP 优化要问到具体数值;项目中遇到过的工程化问题能看出你的实战深度;虚拟列表是长列表场景的必备技能;Next.js 是字节很多中台项目的首选框架;图片优化直接影响用户体验。这些问题环环相扣,考察的是一个前端工程师的全链路优化能力。
FCP(First Contentful Paint)是衡量首屏加载体验的核心指标。
// 1. 关键 CSS 内联
// 将首屏所需 CSS 直接内联到 HTML,避免网络请求
<style>
.header {height: 60px; background: #fff;}
.hero {height: 400px; background:url('hero-small.jpg');}
/* 只包含首屏样式,约 2KB */
</style>
// 2. 预加载关键资源
// 告诉浏览器优先下载关键资源
<link rel="preload" href="critical.js" as="script">
<link rel="preload" href="hero.webp" as="image">
<link rel="preconnect" href="https://fonts.googleapis.com">
// 3. 图片优化
// 使用 WebP 格式 + 响应式图片
<picture>
<source srcset="hero-320.webp 320w, hero-640.webp 640w" type="image/webp">
<img src="hero-640.jpg" srcset="hero-320.jpg 320w, hero-640.jpg 640w" loading="eager">
</picture>
// 4. 字体优化
@font-face {
font-family:'CustomFont';
src:url('font.woff2') format('woff2');
font-display: swap;
// 文本立即用降级字体显示,加载后替换
}
// 5. 骨架屏
<div class="skeleton">
<div class="skeleton-header"></div>
<div class="skeleton-content"></div>
</div>
// 6. 移除阻塞渲染的 JS
<script src="analytics.js" async defer></script>
// 7. 服务端渲染/静态生成
// Next.js 的 SSG/SSR 直接返回 HTML
在工程化实践中,经常会遇到一些老包只提供 CommonJS 版本,导致 Tree Shaking 失效或 Next.js 报错。
// 问题示例:some-old-package 只提供 CommonJS
const { debounce } = require('some-old-package')
// ❌
// 解决方案 1:next-transpile-modules(Next.js 专用)
// next.config.js
const withTM = require('next-transpile-modules')(['some-old-package', 'another-cjs-pkg'])
module.exports = withTM({ /* 其他配置 */ })
// 解决方案 2:webpack 配置(通用)
module.exports = {
webpack: (config) => {
config.module.rules.push({
test: /\.js$/,
include: /node_modules\/(some-old-package|another-pkg)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-modules-commonjs']
}
}
})
return config
}
}
// 解决方案 3:动态导入(运行时降级)
const somePackage = await import('some-old-package').then(m => m.default || m)
// 解决方案 4:patch-package 直接修改
// 创建补丁文件 patches/some-old-package+1.2.3.patch
实际案例:曾遇到 marked 库某个版本 ESM 导出有问题,最终锁定到兼容版本并配置 transpile 解决。
代码分割的核心是'按需加载',让用户只下载当前页面所需的代码。
// 1. 路由级别分割(最常用)
// React
const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
// Vue
const routes = [{
path: '/',
component: () => import('@/pages/Home.vue')
}]
// 2. 组件级别分割
const Editor = dynamic(() => import('@/components/Editor'), {
loading: () => <Loading />,
ssr: false // 不需要服务端渲染的组件
})
// 3. 三方库分割(webpack 配置)
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\/]/,
name: 'vendors',
priority: 10
},
ui: {
test: /[\\/]node_modules[\/](antd|element-ui)[\/]/,
name: 'ui-libs',
priority:
}
}
}
(()){
().({ .()})
}
observer.(linkElement)
分割思路总结:先路由后组件,三方库单独打包,动态功能按需加载,空闲时间预加载。
虚拟列表的核心是只渲染可视区域内的元素。不定高比定高复杂,因为无法预知每个元素的高度。
// 核心数据结构:维护每个元素的位置信息
class VirtualList {
constructor() {
this.positions = [] // [{ top, bottom, height }]
this.totalHeight = 0
}
// 1. 初始化:使用预估高度
initPositions(items, estimatedHeight = 100) {
let top = 0
this.positions = items.map((_, index) => {
const height = estimatedHeight
const position = { index, height, top, bottom: top + height }
top += height
return position
})
this.totalHeight = top
}
// 2. 动态更新:渲染后测量真实高度
updateHeight(index, realHeight) {
const pos = this.positions[index]
const diff = realHeight - pos.height
if(diff !== 0){
pos.height = realHeight
pos.bottom += diff // 更新后续所有元素的位置
for(let i = index +1; i <this.positions.length; i++){
this.positions[i].top += diff
.[i]. += diff
}
. += diff
}
}
() {
left =
right = .. -
(left <= right){
mid = .((left + right)/)
pos = .[mid]
(pos. < scrollTop){ left = mid + }
(pos. > scrollTop){ right = mid - }
{ mid }
}
left
}
(){
start = .(scrollTop)
end = .(scrollTop + containerHeight)
..(start, end +).(({...pos}))
}
}
(){
[positions, setPositions] = ([])
({
top =
newPositions = items.({
pos = { top, : itemHeight, : top + itemHeight }
top += itemHeight
pos
})
(newPositions)
},[items])
= ()=>{
({
newPositions = [...prev]
diff = height - newPositions[index].
newPositions[index]. = height
newPositions[index]. += diff
( i = index + ; i < newPositions.; i++){
newPositions[i]. += diff
newPositions[i]. += diff
}
newPositions
})
}
{ positions, updateItemHeight }
}
Next.js 提供了多种渲染模式,适应不同场景需求:
| 模式 | 原理 | 特点 | 适用场景 |
|---|---|---|---|
| SSR | 每次请求时在服务器渲染 | 实时性好、SEO 友好 | 个性化页面、需实时数据 |
| SSG | 构建时生成静态 HTML | 极快、可 CDN 缓存 | 博客、文档、营销页 |
| ISR | 静态生成 + 定期更新 | 兼顾速度与实时性 | 产品页、新闻站 |
| CSR | 客户端渲染 | 强交互 | 后台管理、个人中心 |
// SSR - Server-Side Rendering
export async function getServerSideProps(context){
// 每次请求执行
const data = await fetch(`/api/user/${context.params.id}`)
return { props: { data } }
}
// SSG - Static Site Generation
export async function getStaticProps(){
// 构建时执行
const posts = await fetchPosts()
return { props: { posts } }
}
// ISR - Incremental Static Regeneration
export async function getStaticProps(){
const data = await fetchData()
return { props: { data }, revalidate: 60 } // 每 60 秒重新生成
}
// 动态路由的静态生成
export async function getStaticPaths(){
const posts = await fetchPostIds()
const paths = posts.map(p => ({ params: { : p. } }))
{ paths, : }
}
(){
[data, setData] = ()
({
().(setData)
}, [])
}
根据业务场景混合使用:
// 1. 官网/营销页:SSG
// 原因:内容相对固定,需要极致速度和 SEO
export function getStaticProps(){...}
// 2. 博客/文档:SSG + ISR
// 原因:内容更新不频繁,但需要实时发布
export function getStaticProps({revalidate:3600}){...}
// 3. 用户个人中心:CSR
// 原因:强交互,需要登录态
// 直接客户端渲染
// 4. 详情页:SSR 或 ISR
// 如果内容动态变化快:SSR
// 如果内容变化慢:ISR
export function getServerSideProps(){...}
// 5. 混合示例
// pages/index.js - SSG
export function getStaticProps(){...}
// pages/product/[id].js - ISR
export function getStaticPaths(){...}
export function getStaticProps({revalidate:60}){...}
// pages/user/profile.js - CSR
// 不导出任一函数,默认 CSR
选择标准:
import Image from 'next/image'
<Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority quality={75} placeholder="blur" blurDataURL="data:image/jpeg;base64,/9j/..."/>
// 1. 响应式图片
// 自动生成多尺寸,设置 srcset
// 输出:<img srcset="/_next/image?w=640 640w,/_next/image?w=750 750w,/_next/image?w=828 828w" />
// 2. 格式优化
// 根据浏览器支持自动选择 WebP/AVIF
// Chrome 收到 WebP,Safari 收到 JPEG
// 3. 懒加载
// 默认启用 IntersectionObserver
// 进入视口才加载
// 4. 预加载关键图片
// priority 属性会添加到预加载清单
// <link rel="preload" as="image" href="/hero.jpg">
// 5. 防止布局偏移
// 必须指定宽高,自动计算 aspect-ratio
<div style="aspect-ratio: 1200/600"><img ...></div>
// 6. 占位图 placeholder="blur"
// 显示模糊占位 blurDataURL
// 自定义 base64 占位图
// 7. 图片域名配置
// next.config.js
module.exports = {
images: {
domains: ['example.com','cdn.example.com'],
formats: ['image/webp','image/avif']
}
}
// 8. 优化效果
// 体积:JPEG 200KB → WebP 60KB(-70%)
// 加载:原本需要手动处理的工作自动完成
// 1. 格式选择
// WebP > AVIF > JPEG/PNG
// WebP 比 JPEG 小 25-35%,比 PNG 小 80%
// 2. 响应式图片
<img src="image-800.jpg"
srcset=" image-400.jpg 400w, image-800.jpg 800w, image-1200.jpg 1200w "
sizes=" (max-width: 500px) 400px,(max-width: 900px) 800px, 1200px "
>
// 3. 懒加载
<img loading="lazy" src="image.jpg">
// 4. 渐进式加载
function ProgressiveImage({ src, placeholder }){
const [currentSrc, setCurrentSrc] = useState(placeholder)
useEffect(()=>{
const img = new Image()
img.src = src
img.onload=()=>setCurrentSrc(src)
},[src])
return(
<img src={currentSrc} style={{filter: currentSrc === placeholder ?'blur(10px)':'none'}}/>
)
}
// 5. 使用 CDN
// 加上图片处理参数 https://cdn.example.com/image.jpg?w=800&q=75&format=webp
// 6. 缓存策略
// 强缓存 + hash 文件名 Cache-Control: max-age=31536000
// image-8f3c9d.jpg
// 7. 预加载关键图片
<link rel="preload" as="image" href="hero.jpg">
// 8. 使用 Intersection Observer
const observer = new IntersectionObserver((entries)=>{
entries.forEach(entry=>{
if(entry.isIntersecting){
const img = entry.target
img.src = img.dataset.src
observer.unobserve(img)
}
})
})
// 1. picture 标签(最推荐)
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.jp2" type="image/jp2">
<img src="image.jpg" alt="fallback">
</picture>
// 2. 特性检测
function supportsWebP(){
const canvas = document.createElement('canvas')
if(canvas.toDataURL){
return canvas.toDataURL('image/webp').indexOf('image/webp')===0
}
return false
}
// 使用
const src = supportsWebP()?'image.webp':'image.jpg'
// 3. 服务端检测
app.get('/image',(req, res)=>{
const accept = req.headers['accept']
if(accept && accept.includes('image/webp')){
res.setHeader('Content-Type','image/webp')
res.sendFile('image.webp')
}else{
res.sendFile('image.jpg')
}
})
// 4. Modernizr
if(Modernizr.webp){
// 使用 WebP
}else{
// 使用降级格式
}
// 5. Next.js 自动处理
<Image src="/image.jpg"/>
// 自动检测 Accept 头,返回对应格式
// 6. 批量转换工具
// 构建时生成 webp 版本
const imagemin = require('imagemin')
const imageminWebp = require('imagemin-webp')
await imagemin(['images/*.{jpg,png}'],{
destination:'public/images',
plugins:[imageminWebp({quality:75})]
})
// 7. 动态加载
const webpSupported = await checkWebPSupport()
const imageUrl = webpSupported ?'/img.webp':'/img.jpg'
| 知识点 | 核心要点 |
|---|---|
| FCP 优化 | 关键 CSS、预加载、图片优化、字体优化、骨架屏 |
| ESM 兼容 | transpile、webpack 配置、动态导入、patch-package |
| 代码分割 | 路由级、组件级、三方库、动态导入、预加载 |
| 不定高虚拟列表 | 预估高度、动态更新、二分查找、位置数组 |
| Next 渲染模式 | SSR(实时)、SSG(静态)、ISR(增量)、CSR(客户端) |
| 图片优化组件 | 响应式、格式转换、懒加载、预加载、布局稳定 |
| 图片优化 | 格式选择、响应式、懒加载、CDN、缓存 |
| WebP 降级 | picture 标签、特性检测、服务端判断、Modernizr |
字节的这场面试,从性能优化到工程化再到框架原理,考察非常全面。音视频业务对性能的极致追求,决定了这些问题都是真实工作中每天要面对的。能把这些讲清楚,说明你已经具备了应对复杂业务场景的能力。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online