跳到主要内容JavaScriptNode.js大前端算法
字节跳动音视频前端一面面经与深度解析
字节跳动音视频前端岗位面试真题及深度解析。内容涵盖首屏加载优化(FCP)、ESM 兼容性处理、代码分割策略、不定高虚拟列表原理、Next.js 渲染模式选择及应用场景、图片组件优化及 WebP 格式降级方案。通过实际代码示例和架构分析,帮助开发者理解大厂对性能优化与工程化能力的考察重点。
CloudNative31 浏览 面经原文内容
📍面试公司:字节跳动
🕐面试时间:3 月 3 日
💻面试岗位:音视频前端(春招)
❓面试问题:
- 自我介绍
- 用了哪些方法使 FCP 渲染耗时缩短近 1s
- 有没有遇到过哪个包不支持 ESM 的,你是怎么处理的
- 你进行代码分割的思路是什么
- 不定高的虚拟列表的实现原理是什么
- NextJS 有哪些渲染模式,分别介绍一下
- 你们的应用用的是哪种
- NextJS 中有一个内置的图片优化组件,它做了哪些优化
- 让你优化图片显示你怎么优化
- webp 格式的图片有兼容性问题吗,怎么做降级处理
📝 字节跳动音视频前端一面·深度解析
🎯 面试整体画像
| 维度 | 特征 |
|---|
| 公司定位 | 字节跳动 - 音视频方向 |
| 面试风格 | 项目深潜型 + 场景追问型 |
| 难度评级 | ⭐⭐⭐⭐(四星) |
| 考察重心 | 性能优化、工程化、Next.js、图片处理 |
💡 面经关键点解读
面试官的潜台词:音视频业务对性能要求极高,所以 FCP 优化要问到具体数值;项目中遇到过的工程化问题能看出你的实战深度;虚拟列表是长列表场景的必备技能;Next.js 是字节很多中台项目的首选框架;图片优化直接影响用户体验。这些问题环环相扣,考察的是一个前端工程师的全链路优化能力。
🔍 逐题深度解析
一、FCP 优化
问题:用了哪些方法使 FCP 渲染耗时缩短近 1s
FCP(First Contentful Paint)是衡量首屏加载体验的核心指标。
<style>
.header {height: 60px; background: #fff;}
.hero {height: 400px; background:url('hero-small.jpg');}
</style>
<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
二、ESM 兼容性问题
问题:有没有遇到过哪个包不支持 ESM 的,你是怎么处理的
在工程化实践中,经常会遇到一些老包只提供 CommonJS 版本,导致 Tree Shaking 失效或 Next.js 报错。
const { debounce } = require('some-old-package')
const withTM = require('next-transpile-modules')(['some-old-package', 'another-cjs-pkg'])
module.exports = withTM({})
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
}
}
const somePackage = await import('some-old-package').then(m => m.default || m)
实际案例:曾遇到 marked 库某个版本 ESM 导出有问题,最终锁定到兼容版本并配置 transpile 解决。
三、代码分割思路
问题:你进行代码分割的思路是什么
代码分割的核心是'按需加载',让用户只下载当前页面所需的代码。
const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const routes = [{
path: '/',
component: () => import('@/pages/Home.vue')
}]
const Editor = dynamic(() => import('@/components/Editor'), {
loading: () => <Loading />, ssr: false
})
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\/]/,
name: 'vendors',
priority: 10
},
ui: {
test: /[\\/]node_modules[\/](antd|element-ui)[\/]/,
name: 'ui-libs',
priority: 20
}
}
}
if(userHasPermission('edit')){
import('./editor.js').then(module => {
module.initEditor()
})
}
<link rel="prefetch" href="next-page.js">
observer.observe(linkElement)
分割思路总结:先路由后组件,三方库单独打包,动态功能按需加载,空闲时间预加载。
四、不定高虚拟列表
问题:不定高的虚拟列表的实现原理是什么
虚拟列表的核心是只渲染可视区域内的元素。不定高比定高复杂,因为无法预知每个元素的高度。
class VirtualList {
constructor() {
this.positions = []
this.totalHeight = 0
}
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
}
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
this.positions[i].bottom += diff
}
this.totalHeight += diff
}
}
getStartIndex(scrollTop) {
let left = 0
let right = this.positions.length - 1
while(left <= right){
const mid = Math.floor((left + right)/2)
const pos = this.positions[mid]
if(pos.bottom < scrollTop){ left = mid + 1 }
else if(pos.top > scrollTop){ right = mid - 1 }
else{ return mid }
}
return left
}
getVisibleItems(scrollTop, containerHeight) {
const start = this.getStartIndex(scrollTop)
const end = this.getStartIndex(scrollTop + containerHeight)
return this.positions.slice(start, end + 1).map(pos => ({...pos,
}
}
function useVirtualList(items, itemHeight = 100) {
const [positions, setPositions] = useState([])
useEffect(() => {
let top = 0
const newPositions = items.map((_, i) => {
const pos = { top, height: itemHeight, bottom: top + itemHeight }
top += itemHeight
return pos
})
setPositions(newPositions)
}, [items])
const updateItemHeight = (index, height) => {
setPositions(prev => {
const newPositions = [...prev]
const diff = height - newPositions[index].height
newPositions[index].height = height
newPositions[index].bottom += diff
for(let i = index + 1; i < newPositions.length; i++){
newPositions[i].top += diff
newPositions[i].bottom += diff
}
return newPositions
})
}
return { positions, updateItemHeight }
}
五、Next.js 渲染模式
问题:NextJS 有哪些渲染模式,分别介绍一下
Next.js 提供了多种渲染模式,适应不同场景需求:
| 模式 | 原理 | 特点 | 适用场景 |
|---|
| SSR | 每次请求时在服务器渲染 | 实时性好、SEO 友好 | 个性化页面、需实时数据 |
| SSG | 构建时生成静态 HTML | 极快、可 CDN 缓存 | 博客、文档、营销页 |
| ISR | 静态生成 + 定期更新 | 兼顾速度与实时性 | 产品页、新闻站 |
| CSR | 客户端渲染 | 强交互 | 后台管理、个人中心 |
export async function getServerSideProps(context) {
const data = await fetch(`/api/user/${context.params.id}`)
return { props: { data } }
}
export async function getStaticProps() {
const posts = await fetchPosts()
return { props: { posts } }
}
export async function getStaticProps() {
const data = await fetchData()
return { props: { data }, revalidate: 60
}
export async function getStaticPaths() {
const posts = await fetchPostIds()
const paths = posts.map(p => ({ params: { id: p.id } }))
return { paths, fallback: 'blocking' }
}
function Page() {
const [data, setData] = useState(null)
useEffect(() => {
fetchData().then(setData)
}, [])
return <div>{data}</div>
}
六、应用渲染模式选择
问题:你们的应用用的是哪种
export getStaticProps(){...}
export getStaticProps({revalidate:3600}){...}
export getServerSideProps(){...}
export getStaticProps(){...}
export getStaticPaths(){...}
export getStaticProps({revalidate:60}){...}
- SEO 要求高 → 用 SSG/SSR
- 数据实时性要求高 → 用 SSR
- 速度要求高、数据变化慢 → 用 SSG
- 需要登录态 → 用 CSR
- 数据变化快但能接受延迟 → 用 ISR
七、Next.js 图片优化组件
问题:NextJS 中有一个内置的图片优化组件,它做了哪些优化
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/..."/>
<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%)
// 加载:原本需要手动处理的工作自动完成
八、图片显示优化
问题:让你优化图片显示你怎么优化
<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 " >
<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)
}
})
})
九、WebP 兼容性降级
问题:webp 格式的图片有兼容性问题吗,怎么做降级处理
<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 |
字节的这场面试,从性能优化到工程化再到框架原理,考察非常全面。音视频业务对性能的极致追求,决定了这些问题都是真实工作中每天要面对的。能把这些讲清楚,说明你已经具备了应对复杂业务场景的能力。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online