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

温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。
面经原文内容
📍面试公司:字节跳动AIDP部门
🕐面试时间:近期
💻面试岗位:前端一面
📝面试体验:被拷打爆了
❓面试问题:
- 断点续传具体实现思路
- 切片具体用的什么API
- 请求报文的contenttype有哪些常见字段
- 第二个音视频通话项目里面怎么集中式处理错误的
- ajax底层原理是什么
- 第一个项目为什么用sse而不用websocket
- http协议和ws协议啥区别
- 怎么处理接口请求错误
- 双向绑定的底层原理实现
- diff算法底层原理是啥
- 怎么处理websocket连接不稳定问题
- websocket重连逻辑会执行几次
- TCP和UDP区别
- TCP三次挥手四次握手
- 怎么实现码率自适应调整 具体怎么实现的
- 什么交换媒体信息时的安全问题还是什么的 忘记了
- 怎么判断网卡 从而调整码率
- 写节流防抖
- 在防抖里面 如果我不想执行这次函数了 想做其他的事 怎样终止掉这次函数执行
- 说一下事件循环
- 写一个事件循环代码输出题
- 说一下agent loop
- 上下文管理怎么搞(我说的rag被否定了)
- 那就讲讲你对rag的理解吧
来源:牛客网 欢是欢喜的喜
💡 木木有话说(刷前先看)
字节AIDP部门这场一面,如果实习或者校招的话,算是一份"地狱难度"的面经了。从断点续传API细节、Content-Type字段,到WebRTC码率自适应、网卡判断,再到agent loop、上下文管理......问题跨度极大,深度极深。
巧了,对于码率自适应调整(ABR)我之前写了鸿蒙相关的内容,有图例被官方加精了,可以辅助大家理解
自适应码率播放文章1
自适应码率播放文章2
📝 字节AIDP前端一面·深度解析
🎯 面试整体画像
| 维度 | 特征 |
|---|---|
| 面试风格 | 底层原理型 + 实战细节型 + 跨领域深挖型 |
| 难度评级 | ⭐⭐⭐⭐⭐(五星,涵盖网络、音视频、框架、算法) |
| 考察重心 | 断点续传、HTTP协议、WebSocket、WebRTC、Vue原理、事件循环、AI上下文 |
| 特殊之处 | 涉及WebRTC码率自适应、网卡判断等音视频底层知识 |
🔍 逐题深度解析
二、断点续传具体实现思路
回答思路:断点续传的核心是分片上传 + 记录已传分片。
实现步骤:
- 文件分片 :使用
Blob.prototype.slice将文件切成多个chunk - 上传前检查:请求服务端获取已上传的分片列表
- 并发上传:跳过已上传分片,上传缺失分片
- 合并通知:所有分片上传完成后,通知服务端合并
- 失败重传:单个分片失败时单独重传
javascript
async function resumeUpload(file, uploadId) {
const chunkSize = 2 * 1024 * 1024 // 2MB
const chunks = Math.ceil(file.size / chunkSize)
// 获取已上传分片
const uploaded = await getUploadedChunks(uploadId)
for (let i = 0; i < chunks; i++) {
if (uploaded.includes(i)) continue
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize)
await uploadChunk(uploadId, i, chunk)
}
await completeUpload(uploadId)
}
三、切片具体用的什么API
回答 :Blob.prototype.slice() 或 File.prototype.slice()(File继承自Blob)。
javascript
const blob = file.slice(start, end, contentType)
const blob = file.webkitSlice() // 旧版Chrome
const blob = file.mozSlice() // 旧版Firefox
参数:
start:起始字节end:结束字节(不包含)contentType:可选,新Blob的MIME类型
四、请求报文的Content-Type常见字段
| Content-Type | 说明 | 示例 |
|---|---|---|
application/json |
JSON数据 | {"name":"Tom"} |
application/x-www-form-urlencoded |
表单URL编码 | name=Tom&age=18 |
multipart/form-data |
文件上传 | 包含boundary分隔符 |
text/plain |
纯文本 | Hello World |
text/html |
HTML文档 | <html>... |
application/xml |
XML数据 | <user><name>Tom</name></user> |
六、ajax底层原理
回答思路 :基于XMLHttpRequest对象实现异步通信。
核心步骤:
- 创建
XMLHttpRequest实例 - 调用
open(method, url, async)初始化请求 - 设置请求头(可选)
- 绑定
onreadystatechange监听状态变化 - 调用
send()发送请求 - 根据
readyState和status处理响应
javascript
const xhr = new XMLHttpRequest()
xhr.open('GET', '/api/data', true)
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
xhr.send()
readyState取值:
- 0: UNSENT(未调用open)
- 1: OPENED(已调用open)
- 2: HEADERS_RECEIVED(已收到响应头)
- 3: LOADING(接收响应体中)
- 4: DONE(完成)
八、HTTP协议和WS协议的区别
| 维度 | HTTP | WebSocket |
|---|---|---|
| 协议 | 应用层,请求-响应 | 独立协议(ws/wss),全双工 |
| 连接 | 短连接或Keep-Alive | 长连接,保持打开 |
| 通信方向 | 单向(客户端→服务端) | 双向 |
| 数据格式 | 文本(HTTP报文) | 文本/二进制帧 |
| 头部开销 | 每次请求携带头部 | 握手后头部小 |
WebSocket握手 :通过HTTP升级协议,Upgrade: websocket。
十二、处理WebSocket连接不稳定问题
回答思路:心跳保活 + 断线重连。
策略:
- 心跳机制:定时发送ping,超时未收到pong则判定断开
- 指数退避重连:1s → 2s → 4s → 8s,最大间隔30s
- 状态同步:重连后重新订阅房间/会话
- 消息缓存:断开期间的消息存队列,重连后发送
javascript
class StableWebSocket {
constructor(url) {
this.url = url
this.heartbeatInterval = 30000
this.reconnect()
}
reconnect() {
this.ws = new WebSocket(this.url)
this.ws.onopen = () => this.startHeartbeat()
this.ws.onclose = () => this.scheduleReconnect()
this.ws.onerror = () => this.ws.close()
}
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
this.ws.send('ping')
}, this.heartbeatInterval)
}
scheduleReconnect() {
setTimeout(() => this.reconnect(), this.getDelay())
}
}
十三、WebSocket重连逻辑会执行几次
回答思路:取决于实现,理论上可以无限重连,但通常会设置最大次数(如10次)。
重连次数控制:
- 设置
maxRetries,超过后不再重连 - 用户主动断开时不应重连(
close(code)中code为1000或1001) - 应用关闭或页面卸载时停止重连
十四、TCP和UDP区别
| 维度 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠(确认+重传) | 不可靠 |
| 顺序 | 保证 | 不保证 |
| 拥塞控制 | 有 | 无 |
| 速度 | 慢 | 快 |
| 适用场景 | HTTP、文件传输 | 实时音视频、DNS |
十五、TCP三次握手和四次挥手
三次握手(建立连接):
- Client → Server:SYN(seq=x)
- Server → Client:SYN+ACK(seq=y, ack=x+1)
- Client → Server:ACK(seq=x+1, ack=y+1)
四次挥手(断开连接):
- Client → Server:FIN(主动关闭)
- Server → Client:ACK
- Server → Client:FIN
- Client → Server:ACK
十六、码率自适应调整(ABR)
回答思路:WebRTC中的码率自适应(ABR)根据网络状况动态调整视频编码码率。
实现方式:
- 带宽估计:通过丢包率、延迟变化(RTT)、接收端反馈估计可用带宽
- 码率决策:GCC(Google Congestion Control)算法决定目标码率
- 编码器调整:通知编码器(如VP8/H.264)改变码率
- 平滑切换:避免码率突变导致画面闪烁
javascript
// 简单模拟:根据丢包率调整
function adjustBitrate(packetLoss) {
if (packetLoss > 0.1) {
currentBitrate *= 0.9 // 丢包率高,降低码率
} else if (packetLoss < 0.02) {
currentBitrate *= 1.05 // 网络好,尝试提高
}
sender.setParameters({ encodings: [{ maxBitrate: currentBitrate }] })
}
十八、怎么判断网卡,从而调整码率
回答思路:通过Network Information API或WebRTC的统计信息。
方法:
- Network Information API :
navigator.connection获取网络类型(wifi/cellular) - WebRTC统计 :
getStats()获取丢包率、往返时间(RTT)、可用带宽
javascript
// 获取网络类型
const connection = navigator.connection || navigator.mozConnection
if (connection) {
console.log(connection.type) // 'wifi', 'cellular', 'ethernet'
if (connection.type === 'cellular') {
setBitrate(500) // 4G网络限制码率
}
}
// WebRTC统计
const stats = await peerConnection.getStats()
stats.forEach(report => {
if (report.type === 'candidate-pair' && report.nominated) {
const rtt = report.currentRoundTripTime
const availableBitrate = report.availableOutgoingBitrate
}
})
十九、写节流防抖
javascript
// 防抖
function debounce(fn, delay) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
// 节流
function throttle(fn, delay) {
let lastTime = 0
return function(...args) {
const now = Date.now()
if (now - lastTime >= delay) {
lastTime = now
fn.apply(this, args)
}
}
}
二十、防抖中如何终止函数执行
回答思路 :通过clearTimeout取消定时器,并可选地设置标志位。
javascript
function debounceWithCancel(fn, delay) {
let timer = null
let cancelled = false
const debounced = function(...args) {
if (cancelled) return
clearTimeout(timer)
timer = setTimeout(() => {
if (!cancelled) fn.apply(this, args)
}, delay)
}
debounced.cancel = () => {
cancelled = true
clearTimeout(timer)
}
return debounced
}
// 使用
const handler = debounceWithCancel(() => console.log('exec'), 1000)
handler()
handler.cancel() // 取消执行
二十一、事件循环
回答思路:参考之前面经。
流程:同步代码 → 清空微任务(Promise.then) → 执行一个宏任务(setTimeout) → 再清空微任务 → 循环。
二十二、事件循环代码输出题
javascript
console.log('1')
setTimeout(() => console.log('2'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('4')
// 输出:1,4,3,2
二十三、Agent Loop
回答思路:AI Agent的核心循环(感知→思考→行动→观察)。
流程:
- 感知:接收用户输入或环境状态
- 思考:LLM推理,决定下一步动作(调用工具、生成回复)
- 行动:执行动作(搜索、计算、API调用)
- 观察:获取行动结果,反馈到下一步
python
while not task_complete:
thought = llm.think(context)
action = execute(thought.tool, thought.input)
context += action.result
二十四~二十五、上下文管理与RAG
回答思路:用户说"rag被否定",说明面试官想要更深层的答案。
上下文管理:在AI对话中,管理历史消息、token限制、记忆压缩。
RAG(检索增强生成):
- 将用户问题向量化,从知识库检索相关文档
- 将文档作为上下文注入Prompt
- 让LLM基于文档回答,减少幻觉
RAG流程:
- 用户提问 → Embedding → 向量检索
- 召回Top-K相关文档 → 组合Prompt
- LLM生成 → 返回答案 + 引用来源
📚 知识点速查表
| 知识点 | 核心要点 |
|---|---|
| 断点续传 | Blob.slice分片、记录已传、并发上传、失败重传 |
| Content-Type | json/form-data/urlencoded/text/html/xml |
| ajax原理 | XMLHttpRequest、readyState状态机 |
| HTTP vs WS | 请求-响应/全双工、短连接/长连接、头部开销 |
| WebSocket稳定性 | 心跳ping/pong、指数退避重连、消息缓存 |
| TCP三次握手 | SYN→SYN+ACK→ACK |
| 码率自适应 | WebRTC GCC、丢包率/RTT估计、编码器调整 |
| 网卡判断 | navigator.connection、getStats |
| 防抖节流 | 定时器重置、时间戳比较 |
| 事件循环 | 同步→微任务→宏任务 |
| Agent Loop | 感知→思考→行动→观察 |
| RAG | 检索增强生成,向量检索+Prompt注入 |
📌 最后一句:
字节AIDP这场一面,是一场"降维打击"式的面试。从断点续传API细节到WebRTC码率自适应,从TCP三次握手到Agent Loop,面试官仿佛在说:"你不是说你会吗?那咱们来聊聊底层。"用户反馈"被拷打爆了",但这正是大厂核心部门的常态。想进这样的团队,光会写业务代码远远不够------你要懂协议、懂音视频、懂AI,更要懂它们背后的原理。