面灵AI→

携程秋招前端开发一面面经(10.08)

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

  1. 请做一下自我介绍。
  2. 数组和链表的区别是什么?
  3. 数组怎么去重?
  4. 聊天框里的逐字打字效果,怎么用 map 实现?
  5. flex 有哪些属性?
  6. 如何实现响应式布局?
  7. CSS 怎么画三角形?
  8. 在地址栏输入 URL 之后发生了什么?
  9. 浏览器的渲染机制是怎样的?
  10. TypeScript 的类型守卫是什么?
  11. 实际开发中如果传入的类型是 number 和 undefined,对变量做 a > 1 比较结果是什么?应该怎么改?
  12. encodeURIComponent 是什么?
  13. 防抖和节流有什么区别?
  14. 以携程 App 用户频繁点击图片为例,该用防抖还是节流?为什么?
  15. Vue 和 React 有什么区别?你更熟悉哪个?
  16. React 的原理是什么?
  17. React 的渲染机制是怎样的?
  18. 为什么要用虚拟 DOM?它和直接操作 DOM 有什么区别?
  19. 长列表卡顿有哪些解决方案?
  20. 追问:不定高的虚拟列表如何计算每项的位置?
  21. 开发过程中遇到性能瓶颈,你会怎么排查?
  22. webpack 和 vite 用过吗?简单讲讲。
  23. 谈谈你对「为什么需要这类打包构建工具」的理解。
  24. 大模型对两次同样的问题回答不一致,为什么?
  25. 追问:RAG 的召回机制是怎样的?
  26. 工作中你如何确认 prompt 优化真的让 agent 变好了?具体看哪些点?

《参考解析》

数组与链表:考的是内存布局和访问模式,不是背诵。 数组是一段连续内存,靠首地址加偏移量做 O(1) 随机访问,CPU 预取和缓存行命中率高,所以顺序遍历极快;代价是插入删除要搬移元素,扩容时要整体拷贝。链表把节点分散在堆上,插入删除只改指针,但每次访问都要跳指针,缓存不友好,节点里的 next 指针本身也是额外开销。工程上的落点通常是:读多写少、要按下标或要顺序扫描用动态数组(Java 的 ArrayList、C++ 的 vector 都是首选);写多读少、要在已知节点附近频繁插入删除、或者对内存碎片敏感才用链表。面试里最好补一句现实场景——大多数业务代码里 ArrayList 就是默认答案,Deque 承担队列职责,链表真正出场的地方是 LRU、链表式内存分配器这类需要 O(1) 摘除节点的结构。

小题里藏的是边界意识:去重与 undefined 参与比较。 去重按元素类型分:基本类型用 Set 一趟 O(n),对象数组不能靠 Set 引用去重,要按业务主键用 Map 收敛、或者排序后双指针;大数组还要考虑内存峰值,流式处理比一次性建集合更稳。a > 1 这道题问的是类型收窄:如果 a 的类型是 number 或 undefined,运行时 undefined 参与数值比较会得到 false(不是报错),这类「静默为假」的分支最容易漏掉,正确做法是在比较前用类型守卫挡掉——typeof a === 'number'、可选链加默认值、或者用带判别的联合类型让编译器强制收窄;打开 strict 与 strictNullChecks 时,TypeScript 会直接拒绝这个比较,等于把运行期的坑挪到了编译期。类型守卫本身也是这几类写法:typeof、instanceof、in 判断、以及返回 x is T 的自定义守卫和可辨识联合。

从输入 URL 到页面呈现:要把网络、构建产物、渲染管线三段串起来。 网络段是 DNS 解析(先查各级缓存)、TCP/TLS 握手(HTTP/3 走 QUIC)、发请求、命中 CDN 或服务端返回 HTML;解析段是 HTML 构建 DOM、CSS 构建 CSSOM、两者合成渲染树,遇到 script 默认阻塞解析,所以现代构建产物用 defer/module、关键样式内联、非关键资源懒加载;渲染段是布局(layout)算几何、绘制(paint)出绘制指令、合成(composite)交给 GPU 分层上屏,改几何属性触发重排、只改颜色透明度可以只走重绘或合成。回到构建工具:开发阶段 vite 利用原生 ESM 做按需编译,只编译当前请求到的模块,启动和热更新都快;生产阶段仍要打包,vite 用 Rollup、webpack 有自己的 chunk 与 loader 生态,webpack 的优势在于对老浏览器、复杂 loader 链和长期沉淀的构建配置支持更成熟。面试时把「为什么 dev 和 build 用两套策略」讲清楚,比背 API 更加分。

防抖与节流的选择取决于触发源的语义,不是频率高低。 防抖是「最后一次触发后再等 N 毫秒才执行」,适合输入框联想、窗口 resize 结束后重算这类「中间态没有意义、只关心最终态」的场景;节流是「固定时间窗口内最多执行一次」,适合滚动监听、拖拽、以及高频但每一次都需要有响应的场景。用户频繁点击图片,如果目的是「别重复发同一个请求」,用防抖(或更直接的做法:请求中禁用按钮、按资源 ID 去重、加请求缓存)比节流更贴合意图;如果目的是「点多少次都要给出至少一次反馈」,就该节流。排查性能瓶颈的通用顺序是先测再改:用 Performance 面板录一段,看是长任务(long task)卡主线程、还是布局抖动、还是网络瀑布太长;定位到环节后再对症下药——脚本层面拆长任务、事件委托、减少重复计算与 DOM 操作;渲染层面减少重排、用 transform/opacity 做动画、长列表虚拟化;资源层面压图片、拆包、预加载关键资源。

虚拟列表的难点在不定高,定高只是特例。 定高虚拟列表很简单:容器高度除以行高得到可视条数,滚动时用 scrollTop 除以行高算出起始下标,只渲染可视区加上下缓冲区,用一条撑高的占位元素保持滚动条长度。不定高的难点是「没渲染过就不知道高度」,常见解法有三档:一是估算加测量——先给每项一个估算高度渲染出来,用 ResizeObserver 量到真实高度后写回缓存,并修正总高度与偏移量,代价是滚动时可能出现小幅跳动;二是二分查找——把已测量的高度做前缀和数组,用二分定位当前 scrollTop 落在哪一项,未测量的项仍用估算值;三是直接要求业务侧提供高度或固定行数截断。生产上还要处理图片加载后高度变化、快速滚动白屏(提高缓冲区、加骨架屏)、以及滚动事件用 passive 监听或 requestAnimationFrame 节流。核心思想是把「位置计算」和「真实渲染」解耦,让未知高度只影响精度、不影响功能。

大模型输出不一致、RAG 召回与 prompt 效果验证,其实是同一个问题的三面。 两次同样的问题回答不同,根因是解码阶段有随机性:temperature、top-p、top-k 采样会按概率分布抽词,加上上下文里历史消息不同、服务端批处理与浮点运算的非确定性、模型版本或路由到不同部署,都会放大差异。要稳定就降采样随机性(temperature 调低甚至贪心)、固定 prompt 与上下文、锁版本;但对创意类任务完全确定性反而不好,取舍按业务定。RAG 的召回机制可以拆成三件事:查询侧做改写、扩展或 HyDE 生成假设答案;索引侧把文档切块、向量化(或稀疏检索 BM25),存进向量库或用混合索引;检索侧用 ANN 近似最近邻取 top-k,再用 reranker 精排,配合多路召回融合(向量加关键词、RRF 之类)以及元数据过滤。召回质量差通常不是模型问题,而是切块粒度、query 改写和 top-k 设置的问题。至于怎么确认 prompt 优化有效:先固定一个评测集(真实问题加标注答案或评分要点),把每次改动后的输出跑同一批集合并对比通过率、人工评分、以及延迟和 token 成本;线上再看任务成功率、追问率、用户重试率这类行为指标。绝对不能说「感觉变好了」——没有基线就无法区分是 prompt 的功劳还是模型随机性。