前言
大家好,我是木斯佳。
相信很多人都感受到了,在AI浪潮的席卷之下,前端领域的门槛在变高,纯粹的"增删改查"岗位正在肉眼可见地减少。曾经热闹非凡的面经分享,如今也沉寂了许多。但我们都知道,市场的潮水退去,留下的才是真正在踏实准备、努力沉淀的人。学习的需求,从未消失,只是变得更加务实和深入。
这个专栏的初衷很简单:拒绝过时的、流水线式的PDF引流贴,专注于收集和整理当下最新、最真实的前端面试资料。我会在每一份面经和八股文的基础上,尝试从面试官的角度去拆解问题背后的逻辑,而不仅仅是提供一份静态的背诵答案。无论你是校招还是社招,目标是中大厂还是新兴团队,只要是真实发生、有价值的面试经历,我都会在这个专栏里为你沉淀下来。
温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。
面经原文内容
📍面试公司:字节跳动
🕐面试时间:3月3日
💻面试岗位:音视频前端(春招)
❓面试问题:
- AI问答应用有出现页面抖动的情况吗,你是怎么处理的
- 应用用的什么markdown库
- AI问答应用有出现返回中断导致的markdown显示错误吗,是怎么处理的
- 浏览器插件有哪些组成部分
- 各个部分怎么进行通信
- 插件的sidePanel
- AI应用的思考模式是怎么实现的
- 怎么使用AI进行全项目开发流程的提效
- 写题:超时重试函数
来源:牛客网 Ryan188
📝 字节跳动音视频前端一面·深度解析(下)
🎯 面试整体画像
| 维度 | 特征 |
|---|---|
| 公司定位 | 字节跳动 - 音视频方向 |
| 面试风格 | AI场景实战型 + 浏览器扩展型 + 工程提效型 |
| 难度评级 | ⭐⭐⭐⭐(四星) |
| 考察重心 | AI应用体验优化、浏览器插件开发、AI提效能力、手写能力 |
木木有话说:现在AI确实是各种面试里的热点必问了,所以我把下篇单独抽出来,这更像AI岗位的面试题,另外很多题其实没有标准答案,就是根据自己的项目,整理自己的回答模型,这里只是提供一些回答的思路和方向。仅供参考。另外,建议大家自己去动手实操一下AI输出Markdown这个demo
🔍 逐题深度解析
一、AI问答页面抖动处理
问题:AI问答应用有出现页面抖动的情况吗,你是怎么处理的
javascript
// 1. 抖动原因分析
// - 内容动态插入导致滚动条出现/消失
// - 图片加载导致高度变化
// - 流式输出导致频繁重排
// 2. 解决方案
// 2.1 预留占位(稳定布局)
<div class="message-item" style="min-height: 60px;">
<div class="avatar"></div>
<div class="content">
{{显示内容}}
<div v-if="loading" class="typing-indicator">
<span></span><span></span><span></span>
</div>
</div>
</div>
// 2.2 固定容器尺寸
.chat-container {
height: calc(100vh - 200px); /* 固定高度 */
overflow-y: auto;
}
// 2.3 图片预留空间
.image-container {
aspect-ratio: 16/9; /* 固定宽高比 */
width: 100%;
background: #f0f0f0;
}
// 2.4 锁定滚动位置(防止新消息打断)
function appendMessage(newMessage) {
const container = chatRef.value
const shouldAutoScroll = container.scrollHeight - container.scrollTop - container.clientHeight < 50
messages.push(newMessage)
nextTick(() => {
if (shouldAutoScroll) {
container.scrollTop = container.scrollHeight
}
})
}
// 2.5 使用骨架屏
<div class="message-skeleton">
<div class="skeleton-avatar"></div>
<div class="skeleton-line" style="width: 80%;"></div>
<div class="skeleton-line" style="width: 60%;"></div>
</div>
// 2.6 字体预加载
<link rel="preload" href="font.woff2" as="font">
// 2.7 过渡动画(平滑变化)
.message-enter-active {
transition: all 0.3s ease;
}
.message-enter-from {
opacity: 0;
transform: translateY(20px);
}
二、Markdown库选择
问题:应用用的什么markdown库
这个比较灵活,最好可以去看看开源方案里的markdown库方案,我觉得这块可能需要单独整理,不在这里提出了,最近因为AI的火热,这个问题又重新被提出来了。
javascript
// 简单场景用marked,需要扩展用markdown-it,需要复杂处理用remark
// AI问答场景推荐marked + DOMPurify + hljs
三、返回中断导致的Markdown错误
问题:AI问答应用有出现返回中断导致的markdown显示错误吗,是怎么处理的
javascript
// 1. 问题场景
// AI生成中途中断,导致Markdown语法不完整
// 例如:**加粗内容(没有结束符号)或 [链接文本(缺少括号)
// 2. 解决方案
// 2.1 安全解析(捕获错误)
function safeMarkdown(content) {
try {
return marked.parse(content)
} catch (e) {
console.warn('Markdown解析失败,使用纯文本', e)
return escapeHtml(content) // 转义后返回纯文本
}
}
function escapeHtml(text) {
const div = document.createElement('div')
div.textContent = text
return div.innerHTML
}
// 2.2 延迟渲染(等待更多内容)
let timer
let buffer = ''
function onChunk(chunk) {
buffer += chunk
clearTimeout(timer)
timer = setTimeout(() => {
renderMarkdown(buffer)
}, 200) // 等待200ms,可能收到更多内容
}
// 2.3 增量解析(保留未完成部分)
class IncrementalMarkdown {
constructor() {
this.buffer = ''
}
append(text) {
this.buffer += text
return this.parsePartial()
}
parsePartial() {
// 尝试按行解析,保留最后一行
const lines = this.buffer.split('\n')
const complete = lines.slice(0, -1).join('\n')
this.buffer = lines[lines.length - 1]
try {
return marked.parse(complete)
} catch {
return escapeHtml(complete)
}
}
}
// 2.4 状态恢复
const state = {
content: '',
lastPosition: 0
}
// 中断后重连时恢复
async function reconnect() {
const response = await fetch('/api/continue', {
method: 'POST',
body: JSON.stringify(state)
})
// 从断点继续
}
// 2.5 前端降级显示
function renderMessage(content, isComplete) {
if (isComplete) {
return <div dangerouslySetInnerHTML={{ __html: marked.parse(content) }} />
} else {
// 未完成时显示纯文本 + 光标
return <div>{content}<span class="cursor">|</span></div>
}
}
四、浏览器插件组成部分
问题:浏览器插件有哪些组成部分
javascript
// 1. Manifest(配置文件)
// manifest.json
{
"manifest_version": 3,
"name": "AI Assistant",
"version": "1.0.0",
"description": "AI助手",
"permissions": [
"storage",
"activeTab",
"sidePanel"
],
"host_permissions": [
"https://api.example.com/*"
],
"background": {
"service_worker": "background.js"
},
"content_scripts": [{
"matches": ["https://*/*"],
"js": ["content.js"],
"css": ["styles.css"]
}],
"action": {
"default_popup": "popup.html",
"default_icon": "icon.png"
},
"side_panel": {
"default_path": "sidepanel.html"
},
"options_page": "options.html",
"icons": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
}
}
// 2. Background Script(后台脚本)
// background.js - 长期运行,管理状态
chrome.runtime.onInstalled.addListener(() => {
console.log('插件已安装')
})
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'API_REQUEST') {
fetch(message.url).then(res => res.json()).then(data => {
sendResponse(data)
})
return true // 异步响应
}
})
// 3. Content Script(内容脚本)
// content.js - 访问DOM,与页面交互
console.log('注入页面成功')
// 监听来自popup的消息
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.type === 'GET_SELECTION') {
sendResponse({ text: window.getSelection().toString() })
}
})
// 4. Popup(弹出页)
// popup.html - 点击图标显示
<!DOCTYPE html>
<html>
<body style="width: 300px;">
<h3>AI助手</h3>
<button id="analyze">分析页面</button>
<script src="popup.js"></script>
</body>
</html>
// popup.js
document.getElementById('analyze').addEventListener('click', () => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, { type: 'GET_SELECTION' }, (res) => {
console.log('选中文本:', res.text)
})
})
})
// 5. Options Page(设置页)
// options.html - 插件设置
// 6. Side Panel(侧边栏)
// sidepanel.html - Chrome 114+支持
五、插件通信方式
问题:各个部分怎么进行通信
javascript
// 通信架构图
// Popup ←→ Background ←→ Content Script ←→ Web Page
// ↕ ↕
// Options Page Side Panel
// 1. Popup → Content Script
// popup.js
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, { action: 'ping' }, (response) => {
console.log('content响应:', response)
})
})
// content.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
sendResponse({ status: 'ok' })
})
// 2. Content Script → Background
// content.js
chrome.runtime.sendMessage({ action: 'fetchData', url: '/api' }, (res) => {
console.log('background返回:', res)
})
// background.js
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
if (req.action === 'fetchData') {
fetch(req.url).then(r => r.json()).then(sendResponse)
return true
}
})
// 3. Popup → Background
// popup.js
chrome.runtime.sendMessage({ action: 'getState' }, (state) => {
console.log('background状态:', state)
})
// 4. 长连接(持久通信)
// content.js
const port = chrome.runtime.connect({ name: 'content' })
port.postMessage({ action: 'start' })
port.onMessage.addListener((msg) => {
console.log('收到:', msg)
})
// background.js
chrome.runtime.onConnect.addListener((port) => {
if (port.name === 'content') {
port.onMessage.addListener((msg) => {
port.postMessage({ response: 'ack' })
})
}
})
// 5. Storage通信(共享数据)
// 任何部分都可以读写storage
chrome.storage.sync.set({ key: 'value' })
chrome.storage.sync.get(['key'], (result) => {})
六、插件的sidePanel
问题:插件的sidePanel
javascript
// 1. Side Panel是什么?
// Chrome 114+ 引入的侧边栏API,固定在浏览器侧边
// 2. 配置
// manifest.json
{
"manifest_version": 3,
"name": "Side Panel Demo",
"permissions": ["sidePanel"],
"side_panel": {
"default_path": "sidepanel.html"
},
"background": {
"service_worker": "background.js"
}
}
// 3. sidepanel.html
<!DOCTYPE html>
<html>
<head>
<style>
body { width: 300px; padding: 16px; }
</style>
</head>
<body>
<h3>AI助手</h3>
<textarea id="input" placeholder="输入问题..."></textarea>
<button id="submit">发送</button>
<div id="response"></div>
<script src="sidepanel.js"></script>
</body>
</html>
// 4. sidepanel.js
document.getElementById('submit').addEventListener('click', async () => {
const text = document.getElementById('input').value
const response = await fetch('https://api.example.com/chat', {
method: 'POST',
body: JSON.stringify({ text })
})
const data = await response.json()
document.getElementById('response').innerText = data.answer
})
// 5. 控制显示
// background.js
// 在所有页面启用
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true })
// 只在特定域名启用
chrome.sidePanel.setOptions({
enabled: true,
path: 'sidepanel.html'
})
// 6. 与Background通信
// sidepanel.js
chrome.runtime.sendMessage({ action: 'sidePanelReady' })
七、AI应用的思考模式实现
问题:AI应用的思考模式是怎么实现的
javascript
// 1. 思考模式定义
// 让AI展示推理过程,而不是直接给答案
// 2. 实现方案
// 2.1 提示词工程
const prompt = `
请按照以下格式回答:
思考过程:逐步推理
最终答案:结论
问题:${userQuestion}
`
// 2.2 流式解析
class ThoughtParser {
constructor() {
this.buffer = ''
this.inThought = false
}
parse(chunk) {
this.buffer += chunk
// 检测思考过程开始
if (this.buffer.includes('思考过程:') && !this.inThought) {
this.inThought = true
this.buffer = this.buffer.replace('思考过程:', '')
}
// 检测思考过程结束
if (this.inThought && this.buffer.includes('最终答案:')) {
const parts = this.buffer.split('最终答案:')
this.thought = parts[0]
this.answer = parts[1]
this.inThought = false
return {
thought: this.thought,
answer: this.answer
}
}
return null
}
}
// 使用
const parser = new ThoughtParser()
stream.on('data', (chunk) => {
const result = parser.parse(chunk)
if (result) {
renderThought(result.thought)
renderAnswer(result.answer)
}
})
// 2.3 界面分层
<template>
<div class="ai-response">
<div class="thought-process" v-if="showThought">
<h4>思考过程</h4>
<div class="thought">{{ thought }}</div>
</div>
<div class="answer">
<h4>回答</h4>
<div class="content">{{ answer }}</div>
</div>
<button @click="toggleThought">
{{ showThought ? '隐藏' : '显示' }}思考过程
</button>
</div>
</template>
// 2.4 实时展示
// 一边接收一边展示思考过程
let thoughtBuffer = ''
function onToken(token) {
if (!thoughtComplete) {
thoughtBuffer += token
updateThought(thoughtBuffer)
// 检测思考结束标志
if (thoughtBuffer.includes('最终答案:')) {
thoughtComplete = true
const parts = thoughtBuffer.split('最终答案:')
setThought(parts[0])
setAnswer(parts[1])
}
} else {
appendAnswer(token)
}
}
八、AI辅助开发提效
问题:怎么使用AI进行全项目开发流程的提效
javascript
// 1. 需求分析阶段
// 用AI梳理需求
输入:帮助分析这个需求文档,列出技术要点和潜在风险
// 2. 技术选型
输入:需要开发一个AI聊天界面,对比Next.js和Vite+React的优缺点
// 3. 代码生成
// 生成组件模板
输入:生成一个带防抖搜索的React组件
// 生成单元测试
输入:为这个函数生成Jest测试用例
// 4. 代码审查
输入:审查这段代码,指出潜在的性能问题和bug
// 5. 问题排查
// 复制错误信息让AI分析
输入:遇到这个错误 [粘贴错误],可能是什么原因?
// 6. 文档编写
// 生成README
输入:为这个组件生成README文档
// 生成JSDoc
/**
* @param {string} url - 请求地址
* @param {object} options - 请求选项
* @returns {Promise} 响应结果
*/
// AI自动补全参数说明
// 7. 性能优化
输入:这个列表渲染有10000条数据,如何优化?
// 8. 重构建议
输入:这个组件有500行,如何拆分?
// 9. 实际提效数据
// 开发效率提升40%
// Bug率降低30%
// 代码审查时间减少50%
// 10. 常用工具
- GitHub Copilot:代码补全
- Cursor:AI优先的编辑器
- ChatGPT:问答、调试
- Claude:长上下文理解
- v0/Verce:AI生成UI
九、手写超时重试函数
问题:写题:超时重试函数
javascript
// 1. 基础版:带超时的fetch
function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController()
const { signal } = controller
const timeoutId = setTimeout(() => controller.abort(), timeout)
return fetch(url, { ...options, signal })
.finally(() => clearTimeout(timeoutId))
.catch(err => {
if (err.name === 'AbortError') {
throw new Error(`请求超时 (${timeout}ms)`)
}
throw err
})
}
// 2. 带重试的fetch
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, options)
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`)
}
return response
} catch (err) {
console.log(`第${i + 1}次尝试失败:`, err.message)
if (i === retries - 1) {
throw err // 最后一次失败则抛出
}
// 指数退避
const delay = Math.min(1000 * Math.pow(2, i), 10000)
await new Promise(r => setTimeout(r, delay))
}
}
}
// 3. 完整版:超时+重试+指数退避
async function fetchWithRetryAndTimeout(
url,
options = {},
{
retries = 3,
timeout = 5000,
backoffFactor = 2,
initialDelay = 1000
} = {}
) {
let lastError
for (let i = 0; i < retries; i++) {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
try {
const response = await fetch(url, {
...options,
signal: controller.signal
})
clearTimeout(timeoutId)
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`)
}
return response
} catch (err) {
clearTimeout(timeoutId)
lastError = err
console.log(`尝试 ${i + 1}/${retries} 失败:`, err.message)
if (i < retries - 1) {
// 计算延迟时间
const delay = initialDelay * Math.pow(backoffFactor, i)
const jitter = Math.random() * 200 // 随机抖动,避免同时重试
await new Promise(r => setTimeout(r, delay + jitter))
}
}
}
throw lastError
}
// 4. 使用示例
try {
const response = await fetchWithRetryAndTimeout('https://api.example.com/data', {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
}, {
retries: 5,
timeout: 3000
})
const data = await response.json()
console.log('成功:', data)
} catch (err) {
console.error('最终失败:', err)
}
// 5. 带取消功能的版本
function createCancellableFetch() {
let currentController = null
const fetchWithCancel = async (url, options = {}, retryConfig = {}) => {
// 取消前一次请求
if (currentController) {
currentController.abort()
}
currentController = new AbortController()
return fetchWithRetryAndTimeout(url, {
...options,
signal: currentController.signal
}, retryConfig)
}
const cancel = () => {
if (currentController) {
currentController.abort()
currentController = null
}
}
return { fetchWithCancel, cancel }
}
📚 知识点速查表
| 知识点 | 核心要点 |
|---|---|
| 页面抖动 | 固定尺寸、预留占位、滚动锁定、过渡动画 |
| Markdown库 | marked(轻量)、markdown-it(可配置)、remark(可扩展) |
| Markdown错误 | 安全解析、延迟渲染、增量解析、降级显示 |
| 插件组成 | manifest、background、content、popup、options、sidePanel |
| 插件通信 | 单向消息、长连接、storage共享 |
| sidePanel | 侧边栏API、独立页面、与background通信 |
| 思考模式 | 提示词工程、流式解析、界面分层 |
| AI提效 | 需求分析、代码生成、审查、调试、文档 |
| 超时重试 | 指数退避、抖动、AbortController、可取消 |
📌 最后一句:
字节的这场面试下半场,聚焦在AI应用落地和浏览器扩展开发。偏向项目实战
