字节面经-字节跳动前端开发岗面经-08
- 轮次
- 面试合集
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
(面经 01)
- 自我介绍。
- 请从你的实习经历里面挑一个你认为比较有亮点的项目来介绍一下。
- 主要是在对话框那一部分的,包括这种 SSE 对话处理是吧?
- 就是 SSE 对话续传虚拟列表这部分是吧?
- 虚拟列表的基本原理。
- 从你的这个场景来看的话,什么情况下会用到虚拟列表?
- 然后你是怎么做的?
- 你是从前端来去控制的,30 条以上开启虚拟列表的是吗?
- 那你是从前端控制,相当于切换了虚拟列表的组件?
- 未到 v-if 的情况下,那你在 30 条的时候,不会有一个整体的类似于组件切换的这种,对用户体验上应该有感觉吧?
- 那就相当于是如果我在你整个系统中对话了 30 条以上,也就相当于你根本就是没有这虚拟列表的逻辑,对吧?
- 屏幕区域和那个缓冲区是怎么设置的?
- 那个断点续传是怎么做的?
- Tool Calling、MCP 和 CoI 的概念区别是什么?
- 那什么情况下我应该直接去做这个 Tool Calling,或者是走 Tools?
- 什么情况下你的判断要走到 MCP 呢?
- 我看你简历上写了 RAG,然后 LangChain、LangGraph。那如果要搭一个基础的 RAG 链路,你觉得会包含哪些流程?
- 你刚刚提到检索,一般检索策略会怎么做?
- 你提到 Rerank 重排,那这个阶段里面常用的策略有哪些?
- 你整个 rerank 是怎么排的?
- 就是你拿到这些片段之后,你怎么认为它的相关性比较高,为什么能够得到一个相对精准的结果?
- 那如果你用模型来拿到一个具体的估分,那你的整个 RAG 链路的成本会不会特别高?
- 怎么解决?
- 什么情况下适合用 LangChain,什么情况下适合用 LangGraph?
- 你刚才在说的这种 LangGraph 持久化的逻辑,它具体是怎么实现的?
- 说出它们的输出顺序以及打印的结果(如图)。
- 在这个文档里面,我不希望这个 script 脚本阻塞它的执行,有哪些方法?
- 那它们两个有什么区别?
- 那请你从浏览器渲染的角度来介绍一下,这一段 HTML 到浏览器之后,到最终渲染出来像素,中间分别会经过哪些阶段?
- stacking context 这个概念有了解过吗?
- 在刚才聊这个流程里面,元素谁在上谁在下,是在你刚才说的哪个阶段发生?
- 什么情况下会被提升到合成层?
- 什么情况下会出现层爆炸呢?
- HTTP 3 和 HTTP 2 主要有哪些特性?
- 但是如果我 HTTP 2 都二进制分帧呢?就相当于是请求数据多路复用加二进制分帧,为什么还会有阻塞?
- 你 10 个请求可能都是以帧包的形式依次传过来,那为什么还会有阻塞?
- 你说的阻塞是什么问题?
- 那 HTTP 3 里面换成了 UDP,UDP 怎么样保证原有 TCP 的可靠传输?
- 然后继续聊 TCP,TCP 的拥塞控制怎么做的?
- 那如果遇见错误了呢?就比如像你刚才说的,它一直翻倍,翻倍完成之后,如果碰见错误了,或者是到达一个实际的拥塞了,之后会怎么样?
- 在这个流程里面,快重传和快恢复是什么?
(面经 02) 42. 自我介绍。 43. 项目来源。 44. 挑项目或实习中的难点讲一下。 45. canvas 与 x6 具体怎么渲染的? 46. 怎么分层设计的? 47. 为什么要做视口裁切? 48. 树节点数量有多少? 49. 节点数量也不是很多,有这个必要吗? 50. 1000 节点压力测试具体怎么做的,怎么判断视口裁切优化生效? 51. 怎么预加载的? 52. 拖拽视口界面表现如何? 53. 为什么用 webworker? 54. 具体讲讲业务上要计算什么? 55. 最小割集是去计算什么? 56. 连续发送多个请求,如何保证最新结果? 57. 具体讲讲这个 await new Promise((resolve) => { setTimeout(resolve, 0) }) 是怎么让出事件循环的? 58. 这个项目是你个人开发的吗? 59. 为什么会想到做这样一个项目? 60. 有真实用户使用吗? 61. Monorepo 的范围? 62. 为什么选双 Token? 63. 怎么获得这两个 token 的,存在哪里? 64. 刷新后会返回新的 refreshToken 吗? 65. 怎么无感刷新的? 66. 为什么选双 token,单个 token 不行吗? 67. 爬虫爬到你的 refreshToken 怎么办? 68. 具体讲讲 langgraph 的 checkpoint 机制。 69. 代码里面怎么注册 checkpoint? 70. 在什么时候存入消息到上下文? 71. 它不是有多个节点吗? 72. 为什么会想到做一个埋点? 73. 每块都是怎么统计的(pv、uv、performance 指标)? 74. 讲讲每个性能指标的含义? 75. 怎么上报 fcp 的? 76. 比如你用 vue 写,在哪个地方记录 fcp 的? 77. 项目有用 ai 吗? 78. 纯跟着课程手搓? 79. 并发任务调度器。 80. 依赖任务并行执行批次。 81. 大二学校放实习吗,学校课程怎么办? 82. 什么时候能入职?
(面经 03) 83. 项目是个人项目还是公司项目? 84. 为什么不用 LangGraph 等开源框架,要自研? 85. 讲一下 ReAct 循环的完整过程,用户发一个任务到完成中间经过哪些处理? 86. 工具调用输出不是标准 JSON 怎么办? 87. 多 Agent 怎么拆分? 88. 子 agent 是预定义的还是主 agent 动态创建的? 89. 超步数熔断是固定步数吗? 90. 如果 agent 陷入循环(没到最大步数,但前 10 步一直在重复同一操作),怎么解决? 91. 上下文怎么管理? 92. 怎么避免超出模型上限、注意力被稀释? 93. SSE 协议,用户断网了怎么重连? 94. MCP client 和 server 的职责划分? 95. 怎么看待 MCP 和 Skill 的关系? 96. 用户反馈 Agent 卡死不动了,怎么排查?
《参考解析》
虚拟列表与阈值切换
核心是只渲染视口内的项、上下各留一段缓冲。实现先按项高算总高度撑起滚动容器(固定高度最省事,不定高要测量并缓存高度),监听滚动拿 scrollTop 反算可见区间,叠加缓冲区后只渲染这一段,用 transform 或占位保持滚动条不跳。以 30 条为阈值是合理取舍:条目少时全量渲染在滚动、搜索、锚点跳转上都更简单,上虚拟列表反而引入测量与「跳到某条历史消息」的对齐问题。切换体验容易崩在两点:两种模式的行高与滚动位置必须可换算,切换瞬间按 scrollTop 还原;更平滑的是同一个组件内部切换渲染方式,而不是 v-if 换掉整个组件。
SSE 流式对话与断点续传
SSE 是一条长连接、服务端单向推送 text/event-stream,浏览器侧可用 EventSource,也可用 fetch 加 ReadableStream 自己解析(后者才能自定义 header 和用 POST)。它比 WebSocket 轻,自带重连和 Last-Event-ID 语义。断点续传要两端配合:每条消息带自增序号或 event id,服务端保留已生成内容或能从存储恢复;客户端断开时记下最后收到的序号与已渲染长度,重连时把位置带上,服务端从该点继续推,前端把新片段追加进同一条消息而不是新建气泡。配套还要有指数退避重连、切后台节流、重复片段按序去重合并,以及生成失败时的终止事件,让这条消息可重试。
Tool Calling、MCP 与 CoI 的边界
Tool Calling 是模型能力:把函数签名以 schema 交给模型,模型决定调哪个并产出结构化参数,应用执行后回灌结果。MCP 是协议标准:定义 client 与 server 的通信,把工具、资源、提示词以统一形式暴露,一个 client 接上任意 server 就能用那批工具,解决 M 个模型接 N 个工具要写 M×N 份适配的问题。CoI 属于推理与指令这一层,是提示策略,不解决工具接入。选型上:应用内几个稳定工具直接 Tool Calling 链路最短;工具来源多、要跨应用复用、需要动态发现和权限隔离时,才值得上 MCP server。MCP 不负责让模型变强,鉴权、超时、结果裁剪仍要自己做。
RAG 链路与重排成本
基础链路是「解析与切块 → 建索引(倒排加向量)→ 查询改写 → 混合召回 → 融合 → 重排 → 组装上下文 → 生成并带引用」。BM25 与向量互补,用 RRF 按排名融合,避免两套分数量纲对不齐。重排常用 Cross-Encoder 精排、模型打分(pointwise 或 listwise)、规则加权(时间、来源、点击反馈)。用模型逐条打分确实贵,降本组合是:先用便宜召回和 RRF 把候选压到 20~50 条只重排这一批;在业务数据上微调或蒸馏一个小 Cross-Encoder 替代大模型打分;高频 query 缓存重排结果;再退一步用 listwise 一次给多条候选让模型输出排序。要能说清精度与成本的取舍点。
LangChain、LangGraph 与 checkpoint
LangChain 面向线性调用链:Prompt、Model、Parser、Retriever 用 LCEL 串起来,适合固定流水线。LangGraph 面向有状态、有分支和循环的图:显式定义 State、节点、边与条件跳转,能表达重试、回退、人工审批。Checkpoint 是它的持久化:每个 superstep 结束把 State 快照写进 checkpointer(内存、SQLite、Postgres 均可),thread_id 区分会话、checkpoint_id 标版本,于是能断点续跑、回放、等人工输入后继续。代码上就是编译图传 checkpointer、调用带 thread_id,写入时机在节点更新完 State 之后。
脚本阻塞、事件循环与并发调度
同步执行的 script 会阻塞 HTML 解析,因为它可能改 DOM。不阻塞的办法:defer(并行下载、解析完按序执行)、async(下载完即执行、顺序不保证)、动态创建 script 或动态 import、type=module(默认 defer 语义),重计算搬进 Web Worker。await new Promise(r => setTimeout(r, 0)) 让出的原理是把后续代码作为宏任务放进定时器队列,同步栈清空后事件循环先清微任务再取宏任务,浏览器因此有机会渲染——它是让出主线程,不是睡 0 毫秒。并发调度器搭在这之上:队列加并发上限,任务结束释放槽位取下一个;带依赖的任务按拓扑排序分批,批内并行、批间串行。
渲染流水线、stacking context 与合成层
HTML 到像素大致经过:解析 HTML 与 CSS 建 DOM、CSSOM,合成渲染树、布局、绘制、分层光栅化、GPU 合成输出。谁盖住谁发生在绘制与合成阶段:同层按 stacking context 与 z-index 排序,跨层由合成顺序决定。stacking context 由定位加 z-index、transform、opacity 小于 1、filter、will-change 等创建,子元素 z-index 只在该上下文内比较。提升到合成层通常因为 3D transform、will-change、video 或 canvas、opacity 动画;层爆炸即滥用 will-change 让大量小元素各自成层,导致内存暴涨、合成变慢。
HTTP/3、QUIC 与 TCP 拥塞控制
HTTP/2 的多路复用解决了 HTTP/1.1 的队头阻塞,但阻塞跑到 TCP 层:所有流共用一个连接,丢一个包后续所有流都要等重传,因为 TCP 保证字节流有序交付。HTTP/3 把传输换到基于 UDP 的 QUIC,在用户态实现流级可靠性与拥塞控制:每个流独立序号与重传,一条流丢包不影响其他流;TLS 1.3 并入握手,1-RTT 甚至 0-RTT 建连;连接迁移靠 connection id 而非四元组。TCP 这边慢启动按 RTT 指数扩窗,到阈值转拥塞避免线性增长;超时会降到 1 重走慢启动,收到三个重复 ACK 则快重传立即重发,快恢复把窗口减半、不跌回 1。
双 Token 无感刷新与安全
单 token 的困境是过期短则频繁掉线、长则泄露后长期可用,双 token 把两者拆开:access token 短期、随请求走,refresh token 长期、只用于换 access token。落位是 access token 放内存(刷新即丢、靠 refresh 恢复),refresh token 放 HttpOnly、Secure、SameSite 的 Cookie,让 JS 读不到,压缩 XSS 面。无感刷新的关键是并发去重:遇到 401 或临近过期,不能每个失败请求各刷一次,要用共享的刷新 Promise 让同批请求一起等,成功后重放原请求,失败则清状态跳登录。刷新时轮换 refresh token、检测重复使用即判定泄露并吊销会话。
Canvas 与 X6 渲染树的性能优化
分层通常是:X6 负责图模型、交互、事件和节点边的业务渲染,canvas 用于高频定制绘制或整体位图渲染,分层的目的是让交互层与绘制层解耦、各自独立重绘。视口裁切的意义在于节点数增长后全量绘制会把每帧开销拉到不可接受,只绘制视口加缓冲内的节点和边,每帧成本就只跟可见量相关。1000 节点压测要造随机拓扑数据,用 performance 或自写帧计时对比开关裁切前后的平均帧时长与长任务数,判据是绘制次数与帧耗时随总节点数近似不变。预加载提前准备视口外一圈的数据与图片;Web Worker 跑不该占主线程的计算——这里是最小割集,求使源点与汇点分离所需移除的最小边集合。
埋点与性能指标采集
埋点分三类:事件(点击、曝光,聚合出 PV/UV)、业务漏斗、性能指标。性能侧 FCP 是首次内容绘制、LCP 是最大内容元素绘制完成、TTFB 是首字节到达、INP 衡量交互响应、CLS 衡量布局偏移;采集优先用 PerformanceObserver 订阅 paint、LCP、layout-shift,老浏览器退回 performance.timing。Vue 里记录 FCP 一般放在全局初始化处:入口挂载前注册 observer,或根组件 mounted 后读 getEntriesByType,但要说明 SPA 里这只反映首屏。上报用 sendBeacon 避免阻塞卸载;离散度大时报 P75/P95 而非均值,并按设备与网络分层。
自研 Agent 框架:ReAct、容错与上下文
ReAct 是「思考 → 行动 → 观察 → 再思考」,一轮完整处理是:拼上下文与工具 schema → 模型输出 → 解析思考与工具调用 → 校验参数并执行 → 结果写回 → 判断终止条件。工具输出不是标准 JSON 是常态,容错顺序是严格 JSON 解析、失败再抽取代码块或首个 JSON 子串、仍失败就把原文与解析错误一起回给模型重发,同时限次数并记样本调 prompt。多 Agent 按职责或数据边界拆,子 Agent 预定义还是动态创建取决于任务是否可枚举:固定流水线用预定义,开放任务由主 Agent 派生,产物用统一格式交接,避免中间推理污染主上下文。熔断不能只看步数,同工具同参数连续出现或状态无变化就要强制换策略或收尾。