百度 前端开发岗面经 两轮 42 题汇总
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
第一轮(2026 年 9 月 3 日)
- 闭包是什么?追问:应用场景有哪些?
- 箭头函数和普通函数有什么区别?
- 深拷贝和浅拷贝有什么区别?
- 跨域有哪些解决方案?追问:除了这些还有没有其他方案?
- 不同 Web 项目 / 页面之间如何通信?追问:还有没有其他方式?
- 浏览器有哪些数据存储方式?
- 事件委托是什么?
- 事件委托有哪些优点、缺点?
- 哪些场景不适合使用事件委托?
- 如何阻止事件冒泡?
- 箭头函数的 this 有什么特殊之处?
- 状态更新过程中有没有遇到过异常情况?追问:如何处理?
- 如果一次批更新中同时存在函数式更新和替换式更新,最终 state 会是什么?
- 有没有自己封装过 Hooks?
- Diff 中 key 的作用是什么?
- Fiber 的数据结构是什么?
- React 16 为什么要引入 Fiber?
- Fiber 解决了之前 React 的什么问题?
- React Diff 和 Vue 双端 Diff 有什么区别?
- useEffect 和 useLayoutEffect 有什么区别?
- React 18 有哪些新特性?
- React 18 有没有增加新的 API / Hooks?
- interface 和 type 有什么区别?
- 怎么理解泛型?泛型有什么作用?
- 联合类型和交叉类型有什么区别?
- any、unknown、never 有什么区别?
- Vite 为什么快?
- Vite 插件的底层原理是什么?
- 除了 Webpack 和 Vite,还使用/了解过哪些构建工具?
第二轮(2026 年 8 月 21 日)
- 你在项目中做过性能优化吗?
- 能讲一下从性能问题发现到问题解决的完整链路吗?
- 流式问答的技术选型会考虑哪些维度?
- 为什么选择基于 Fetch 的 Server-Sent Events,而不是原生 SSE 或 WebSocket?
- 你的多会话流式管理主要是怎么实现的?
- 流式过程中渲染大量 Markdown 时,如何优化卡顿问题?
- 你的 RAG 召回率提升了大约 20%,主要做了哪些工作?
- 如果向量库中的文档被更新或删除,你会怎么做版本管理?
- 你了解 Web Coding 或 AI Coding 吗?
- 项目中大概有多少代码是手写的,多少代码是 AI 辅助完成的?
- 如何保证 AI 生成代码的可维护性、正确性和编写逻辑?
《参考解析》
闭包要说清「引用被保留」这件事:闭包是函数和它定义时所在词法环境的组合,只要内部函数还被引用,外层函数的变量就不会被回收。应用场景很多:防抖节流里保存 timer、模块化里的私有变量、柯里化、React 里用 useRef / 闭包捕获旧值做对比、事件回调里携带上下文。要主动补一句坑:循环里用 var 定义回调会共享同一个变量(用 let 或 IIFE 解决),以及闭包长期持有大对象会造成内存泄漏,需要手动置空。
跨域方案按「谁改得了」来分类:前端能主导的是 CORS(简单请求直接发,带自定义头或非简单方法会先发 OPTIONS 预检,服务端要回 Access-Control-Allow-Origin、Allow-Methods、Allow-Headers,带 cookie 时两个 origin 都不能是通配符且要带 credentials);后端/运维层的方案是 Nginx 反向代理把前后端放同源;老项目的兜底 JSONP(只能 GET,靠 script 标签);页面间通信还有 postMessage、document.domain(同主域)、iframe + hash 传参。另外要能说出「跨域是浏览器同源策略的限制,服务端之间调用和 curl 不受影响」——很多候选人会把这句说反。
批更新里函数式更新和替换式更新混用:React 18 的 setState 会按调用顺序进队列,函数式更新(setCount(c => c + 1))基于前一步的最新值计算,替换式更新(setCount(0))直接覆盖,后面的会盖掉前面的。把队列按顺序跑一遍就能得到结果:setCount(c => c + 1) 后接 setCount(5) 结果是 5;反过来先 setCount(5) 再 setCount(c => c + 1) 结果是 6。同一事件里的多次更新只会触发一次渲染,所以中间值不会上屏。真出问题的场景是在异步回调或原生事件里拿旧 state 计算,所以依赖前值时必须用函数式写法。
Fiber 的本质是把递归改成可中断的链表遍历:React 15 的栈调和是递归的,一旦开始就必须一次走完,长列表渲染会长时间霸占主线程,掉帧且无法响应输入。Fiber 把每个节点变成一个带 child / sibling / return 三个指针的工作单元,渲染过程拆成两阶段:render 阶段可中断、可让出主线程、可复用或丢弃已做的工作(所以有双缓冲的 current 和 workInProgress 两棵树),commit 阶段则同步一次性完成 DOM 操作。Diff 里 key 的作用就是给同层节点一个稳定身份,让 React 能判断是移动还是重建,用 index 当 key 会在列表插入/排序时导致状态错乱和多余重建。
useEffect 和 useLayoutEffect 只差一个时机:useLayoutEffect 在 DOM 变更之后、浏览器绘制之前同步执行,会阻塞绘制,适合需要读取布局并在用户看到之前修正的场景(测量元素尺寸、同步滚动位置);useEffect 在绘制之后异步执行,不阻塞首屏,适合数据请求、订阅、日志这类副作用。服务端渲染时 useLayoutEffect 会报警告,因为它没有 DOM 可测。实践原则是默认用 useEffect,只有当出现「闪一下」的视觉错误时才换成 useLayoutEffect。
Vite 快在两个不同的阶段:开发阶段不做打包,靠浏览器原生 ESM 按需请求模块,冷启动从「打包整个应用」变成「启一个静态服务 + 依赖预构建」;依赖预构建用 esbuild(Go 写的,比 JS 打包器快一个数量级)把 CommonJS/UMD 的第三方包装成 ESM 并合并成单文件,避免一个包几百个请求的水瀑;源码改动用浏览器缓存 + 304,只让改动的模块及其边界重新请求。生产构建还是走的 Rollup(插件原理也是基于 Rollup 的插件接口),所以「Vite 快」主要指开发态,构建态靠 Rollup 的 tree-shaking 和代码分割。
流式渲染卡顿要分「渲染成本」和「渲染频率」两条线治:渲染成本上,Markdown 转 HTML 走增量解析,只对新增的片段做解析和 diff,不要每次 chunk 到达都全文重解析;代码高亮对未闭合的代码块做降级(先纯文本、闭合后再高亮),避免高亮器在半个代码块上反复失败。渲染频率上,用 rAF 或 50~100ms 的节流批量合并多个 chunk 再 setState,让 React 每次提交的增量可控;长会话用虚拟列表只渲染可视区域,并给 React.memo + 稳定 key 避免整棵树重渲染。更彻底的做法是把流式文本写进一个独立的、和主应用渲染解耦的 DOM 节点(比如直接操作 ref 的 textContent + 定期整体替换),牺牲一点声明式换来顺滑。
为什么选 Fetch 版 SSE 而不是原生 EventSource 或 WebSocket:EventSource 只能 GET、不能自定义请求头、不能带 body,遇到需要鉴权头或 POST 传参的场景就废了,而且断线重连行为是浏览器写死的。WebSocket 是双向全双工协议,问答场景只需要服务端单向推,用它要额外维护心跳、重连、更复杂的网关和鉴权,链路也更重。基于 fetch + ReadableStream 手写 SSE 解析(按 \n\n 切事件、处理 data: 前缀)能拿到 POST + 任意请求头 + 主动中止(AbortController),代价是自己实现重连和增量解析。多会话流式管理则要给每个会话独立的 AbortController 和消息缓冲,切换会话时把流继续写入对应会话的状态而不是当前视图,避免切走就丢 token。
RAG 文档更新删除的版本管理:每条文档带稳定 doc_id + 内容哈希 + 版本号,chunk 的 id 用 doc_id#版本#序号;更新时先写新版本再删旧版本,用软删标记保证检索立刻不再命中旧内容;缓存按 doc_id 精确失效。向量库只做召回、真值仍以数据库为准,命中后回表校验版本,这样即使索引残留脏数据也不会答出过期内容。