携程秋招前端开发一面面经(10.08)
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 请做一下自我介绍。
- 数组和链表的区别是什么?
- 数组怎么去重?
- 聊天框里的逐字打字效果,怎么用 map 实现?
- flex 有哪些属性?
- 如何实现响应式布局?
- CSS 怎么画三角形?
- 在地址栏输入 URL 之后发生了什么?
- 浏览器的渲染机制是怎样的?
- TypeScript 的类型守卫是什么?
- 实际开发中如果传入的类型是 number 和 undefined,对变量做 a > 1 比较结果是什么?应该怎么改?
- encodeURIComponent 是什么?
- 防抖和节流有什么区别?
- 以携程 App 用户频繁点击图片为例,该用防抖还是节流?为什么?
- Vue 和 React 有什么区别?你更熟悉哪个?
- React 的原理是什么?
- React 的渲染机制是怎样的?
- 为什么要用虚拟 DOM?它和直接操作 DOM 有什么区别?
- 长列表卡顿有哪些解决方案?
- 追问:不定高的虚拟列表如何计算每项的位置?
- 开发过程中遇到性能瓶颈,你会怎么排查?
- webpack 和 vite 用过吗?简单讲讲。
- 谈谈你对「为什么需要这类打包构建工具」的理解。
- 大模型对两次同样的问题回答不一致,为什么?
- 追问:RAG 的召回机制是怎样的?
- 工作中你如何确认 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 的功劳还是模型随机性。