面灵AI→

小红书前端开发岗面经合集:Vue3 重写、构建工具与 Agent 工程实践

轮次
多轮面试合集
时间
2026-10
来源
牛客网

《面试题目》

面经 01 · 小红书 前端开发岗(2026-09-06)

  1. CORS 的时候前端需要做什么吗?
  2. 为什么 JSONP 不行?
  3. SSR 了解吗?它解决了什么问题?
  4. SSR 和 SPA 的区别是什么?
  5. SPA 和 MPA 的区别是什么?
  6. 现在是手写代码还是用大模型写?
  7. 代码输出题:说出输出结果。

面经 02 · 小红书 前端开发岗(2026-07-29)

  1. 如何开发一个 tool?
  2. 如何开发一个 skill?
  3. Agent 的设计范式有哪些?

面经 03 · 小红书 前端开发岗(2026-03-30)

  1. Vue 和 React 的区别、技术选型与各自适用的场景是什么?
  2. HTTP 2.0 和 HTTP 1.1 的主要区别是什么?
  3. skills 和 rules 的区别是什么?(聊到 MCP 与平时业务中的使用场景)
  4. React 的 fiber 机制是什么?
  5. TypeScript 的 interface 和 type 的区别是什么?

面经 04 · 小红书 前端开发岗(2026-03-19)

  1. 上来两道算法题(力扣链接),共享屏幕写完并解释。
  2. TCP 三次握手四次挥手。
  3. TCP 和 UDP 的区别是什么?
  4. TCP 的「可靠」机制是什么(拥塞控制、流量控制、慢启动、快速重传)?
  5. 进程和线程的区别是什么?
  6. 介绍一下 scss,它在项目中起到什么作用?
  7. 了解 less 吗?
  8. 打包工具有了解吗?
  9. webpack 比 vite 各自的优缺点是什么?
  10. React 和 Vue 的区别是什么?

面经 05 · 小红书 前端开发岗(2026-03-19)

  1. 介绍其中一个项目的流程。
  2. 该项目中某一环的逻辑是什么?
  3. TCP 三次握手四次挥手。
  4. HTTP 缓存。
  5. 手撕一道算法题(面试官提示用回溯做)。

面经 06 · 小红书 前端开发岗(2026-03-18)

  1. 如何理解当前主流的 Agent 开发?
  2. 项目中使用 Web Worker 的主要作用是什么?该技术调研是你独立完成的吗?
  3. 大模型相关能力建设的难点是什么?项目中遇到的线上问题及解决方案?
  4. 设计组件时如何考虑通用性和扩展性?
  5. 如何评判网页性能是否合格?前端性能优化的核心方向有哪些?
  6. 项目中虚拟滚动/懒加载具体是怎么实现的?
  7. 对 Vue 和 React 框架的理解?
  8. 对 Vue 组件通信的理解?
  9. Webpack 与 Vite 的核心区别是什么?
  10. Tree-shaking 的执行原理是什么?
  11. 自我定位与职业规划是怎样的?
  12. 你的核心优势是什么?
  13. Vue2 与 Vue3 的响应式原理差异是什么?
  14. 为什么 Vue3 还要进行重写?
  15. 为什么 ref 需要 .value,而 reactive 不需要?
  16. 如何处理 reactive 复杂对象的重新赋值?(业务场景,封装函数)
  17. Vue3 中 Proxy 的核心缺陷是什么?(点在数据类型)
  18. Vue 数据通信中,兄弟组件通信的前提是什么?
  19. git 场景实操:两个人的代码在不同分支,现在要在测试环境测试,如何操作?
  20. 追问:冲突怎么解决?Merge 过程中想要终止 git 命令是什么?rebase 呢?
  21. 口述当前面试的赛码网如何拆分组件?
  22. 赛码网对于「用户信息」这种多个组件都可能用到的数据如何处理?
  23. 赛码网代码编辑器的不同 tab 栏思路,不同的 tab 都是一个组件吗?
  24. 事件循环代码输出题。

面经 07 · 小红书 前端开发岗(2026-03-13)

  1. ES6 新特性有哪些?
  2. 性能优化方面做了些什么?
  3. HTTP 和 HTTPS 的区别是什么?
  4. TCP 和 UDP 的区别是什么?
  5. 什么是用户态和内核态?
  6. 算法:二叉树层序遍历。
  7. 思维题:一个 3L 和一个 5L 的容器,怎么量出 4 升水?

面经 08 · 小红书 前端开发岗(2026-03-12)

  1. CSS 盒模型是什么?
  2. CSS 的选择器机制是什么?
  3. CSS 中内联样式与选择器哪个优先级更高?
  4. React 中的 hook 有哪些?
  5. React 中的 state 和 props 是什么?
  6. React 中的 useEffect 怎么用?
  7. JS 的事件机制/EventLoop?
  8. JS 的垃圾回收机制?
  9. 介绍一下跨域问题如何解决?
  10. 介绍一下冒泡机制?
  11. 如何停止冒泡?
  12. 开发中如何检查内存泄漏?
  13. 介绍一下 OpenGL 的渲染管线。
  14. 介绍几种着色器(Shader)。
  15. 着色器是如何渲染的?
  16. 算法题:判定括号是否合法。
  17. JS 的基本类型有哪些?
  18. JS 的类型转换是怎样的?
  19. 为什么 null 会是 object?
  20. 说说你对 JS 静态方法的了解。
  21. == 与 === 的实现机制是什么?
  22. 函数式组件的生命周期?
  23. 类组件的生命周期?
  24. React-Diff 过程是怎样的?
  25. React 从渲染到实际浏览器的过程是怎样的?
  26. 如何理解 useEffect,它的作用是什么?
  27. 为什么 useState 中要使用专门的 set 函数来改变状态?
  28. React 的作用是什么?
  29. 常见 Hook 有哪些?
  30. 设计模式有哪些?用一句话概括。
  31. 现在要你实现一个列表组件,你会怎么做?

面经 09 · 小红书 前端开发岗(2026-03-12)

  1. 性能优化的具体落地动作(资源加载、渲染、运行阶段)?如何量化优化效果?
  2. 如何理解 CDN?哪些资源适合放 CDN、哪些不适合?是否了解 CDN 动态加速?
  3. 用大模型提效(cursor rules)的具体实践是什么?
  4. skills 与 rules 的核心区别是什么?
  5. 大模型与 2020 年前传统模型的本质区别是什么?催生大模型出现的核心原因是什么?
  6. 前端开发中常用的设计模式有哪些?单例模式的应用场景及潜在问题?发布订阅模式的核心逻辑?
  7. Vue3 的渲染原理(响应式底层、依赖收集、视图更新流程)?
  8. Vue 单文件组件(.vue)的编译流程是怎样的?loader 如何将其转化为浏览器可识别的代码?
  9. Vite 为何比 Webpack 启动更快?核心优势是什么?

面经 10 · 小红书 前端开发岗(2026-02-03)

  1. 你提到的项目:庆快校园开发过程中遇到了什么困难?
  2. Vue3 响应式原理?
  3. Vue 父子组件通信?
  4. 平时怎么写代码?
  5. 手写三栏布局,多种实现(追问 grid 和 flex 的写法,以及如果是平均分要怎么写)。
  6. 代码输出题:宏任务和微任务。
  7. 手写 Promise.all。

面经 11 · 小红书 前端开发岗(2026-01-28)

  1. 学了多久前端?
  2. 垂直居中怎么写?
  3. JS 的继承有哪些?
  4. 防抖和节流怎么实现?(提到了 lodash 工具库)
  5. JS 的 Promise 你了解多少?
  6. JS 的 Promise.all 有什么特点?
  7. async 和 await 是什么?
  8. Vue 的双向绑定原理?
  9. Vue 的响应式你知道多少?
  10. Vue 的生命周期?
  11. pinia 持久化怎么做?
  12. TCP 三次握手的过程?
  13. 浏览器输入 URL 之后发生了什么?

《参考解析》

Vue3 的重写与响应式细节

「为什么 Vue3 还要重写」要从 Vue2 的结构性问题答起:Vue2 的响应式靠 Object.defineProperty 递归改写属性,新增/删除属性与数组索引监听不到(要靠 $set 和重写数组方法补),初始化时必须把所有层级都转换一遍,而且它把响应式、模板编译、渲染器都耦合在同一个运行时里,导致服务端渲染、跨平台(小程序、原生)适配都要打补丁。Vue3 换成 Proxy 做了懒递归、用 @vue/reactivity 独立成包、渲染器与编译器分层,顺手解决了类型支持差(Vue2 的 this 上下文对 TS 极不友好)和包体积问题(组合式 API 让逻辑可以按需摇树)。Proxy 的缺陷要按数据类型答:它只能代理对象,原始值必须有 ref 这层容器;对 Map/Set/WeakMap 这类内部槽对象要单独写 handler;Date 之类的内置对象和 class 私有字段的拦截行为要小心;而且不能降级到旧浏览器。

由此就能推出「为什么 ref 要 .value、reactive 不要」:ref 是一个 RefImpl 实例,值存在内部字段上,get/set 全部走 .value,所以它天然能装原始值、能整体替换、能跨函数传递;模板里 Vue 帮你自动解包,但 JS 里必须显式写。reactive 返回的是对象本身的 Proxy,访问属性就是访问原对象的属性,不需要也没有 .value。它的代价正好是 ref 的优点:解构出基本类型会丢响应(要配 toRefs),state = newObj 这种整体替换会让 Proxy 指向旧对象而失效。业务里处理 reactive 整体赋值有两种常规写法:一是 Object.assign(state, newObj),二是把对象再包一层(state.data = newObj)或干脆改用 ref;如果要在封装函数里做,就写成 setState(next) { Object.assign(state, next) } 并对外只暴露这个函数,避免有人直接赋值。渲染原理那条线要串起来讲:渲染函数执行时访问响应式数据触发 track,把当前的渲染 effect 按 target/key 收进依赖表;数据变化时 trigger 把 effect 推进调度队列,队列去重、按 id 排序(父先于子),在微任务里批量 flush,再走 patch 做 diff 更新真实 DOM。

.vue 单文件组件的编译流程是另一条高频追问:@vue/compiler-sfc 把文件拆成 template、script、style 三块,template 交给 @vue/compiler-dom 编译成渲染函数(Vue3 会做静态提升、补丁标记等编译期优化),script 走正常的 TS/JS 编译,style 交给 CSS 处理器并按 scoped 生成属性选择器;最终由构建工具的 loader/plugin(vite 用 @vitejs/plugin-vue,webpack 用 vue-loader)把这些产物拼成一个浏览器可执行的 ES 模块。被追问「编译期优化有什么用」,就讲静态提升(不变节点提到渲染函数外只创建一次)、补丁标志(给动态节点打标记,diff 时直接跳过静态子树)和事件缓存这三点。

React 运行时与组件生命周期

函数式组件的「生命周期」实际是 useEffect 的依赖与清理时机:挂载后执行一次,依赖变化时先跑上一次的清理函数再执行,卸载时跑清理;依赖数组为空就是只挂载跑一次,不传依赖就是每次渲染都跑。类组件则按 constructor → render → componentDidMount → shouldComponentUpdate/componentDidUpdate → componentWillUnmount 这条线答,注意 getDerivedStateFromProps 基本应避免使用。useState 要专门用 set 函数而不是直接改值,原因是 React 靠 Object.is 的比较来判断状态是否变化、以及批处理与渲染调度都依赖「setState 被调用」这个信号;直接改对象属性(state.count++)不会触发重渲染,还会破坏不可变更新的语义(并发特性下的渲染可被打断重放,可变状态会串)。而且 set 支持函数式更新 setCount(c => c + 1),在连续更新和闭包捕获旧值的场景下才是对的。React.memo / useMemo / useCallback 的取舍见另一场解析,这里只补一句:useEffect 的依赖里出现每次新建的对象或函数,会让 effect 每次都跑,这是「无限请求」最常见的成因。

React 从渲染到上屏要分成两阶段讲:触发(首次 createRoot().render、setState、父组件重渲染)→ render 阶段(从根开始走 fiber 树,执行函数组件、调用 beginWork/completeWork 做 diff,产出 effect list,这一步可被打断)→ commit 阶段(beforeMutation 跑 getSnapshotBeforeUpdate、mutation 真正改 DOM、layout 跑 useLayoutEffect 与 componentDidMount,这一步同步且不可中断)→ 浏览器绘制,随后 useEffect 在绘制之后异步执行。fiber 的双缓存树(current 与 workInProgress)在这里值得点一句:新的工作树构建完、commit 时一次性切换,中断和重做才不会把页面改坏。diff 的启发式(同层比较、type 不同直接重建、key 匹配复用、列表顺序变化时移动节点)是「列表 key 为什么不能用下标」的根因。

工程化与构建:webpack、vite 与依赖体积

webpack 与 vite 的差别本质是「开发态怎么提供模块」:webpack 需要先把整个依赖图打包好再起服务,模块多了冷启动与热更新都跟着变慢;vite 开发态直接让浏览器按原生 ESM 请求模块,依赖部分用 esbuild 预构建并缓存,改一个文件只失效对应模块,所以 HMR 几乎瞬时。生产构建上 vite 默认用 rollup,仍然要打包,因为浏览器加载几千个模块会产生请求瀑布。选型的分界要说得具体:需要复杂 loader 链、特殊构建能力(Module Federation、微前端产物)、或历史项目已经深度绑定 webpack 插件的,继续用 webpack;新项目、现代浏览器目标、追求开发体验的选 vite。两边各自的优缺点要能对称地说:webpack 生态成熟、可定制性强、但配置复杂且慢;vite 快且约定好,但插件生态相对薄、SSR 与 legacy 场景配置更绕。tree-shaking 的原理是 ESM 的静态可分析性:导入导出关系在编译期就能确定,打包器据此标记未使用的导出并在压缩阶段删掉;它要求用 ESM 语法、避免 require 与动态属性访问,sideEffects: false 是告诉打包器「这个包的文件没有副作用、可以安全删除」,写错会导致样式或 polyfill 被摇掉——这正是「按需引入、减小体积」的追问点:组件库要发布保留 ESM 的产物、把样式与逻辑分离并按入口拆分,业务侧则要避免 import * as _ from 'lodash' 这类整体引入,改用细粒度导入或 babel/vite 插件的按需转换。

CDN 那题要答成「缓存与就近」两件事:CDN 把静态资源复制到边缘节点,用户就近取,同时用长缓存与回源收敛减轻源站压力。适合放 CDN 的是带内容 hash 的静态资源(JS/CSS/图片/字体/视频分片),不适合的是个性化接口响应、带用户态的 HTML(除非做了边缘渲染与按用户分片)、以及强一致计数这类写路径。CDN 动态加速(全站加速)针对的是 API 与动态页面:靠就近接入、长连接复用回源、智能路由与 TCP 优化降低 RTT,而不是靠缓存。做 SSR 时 CDN 还要注意 Cache-Control 与 Vary(带 Cookie 的响应基本不能共享缓存)。

网络与浏览器:从 HTTP 版本到事件循环

HTTP 1.1 与 2.0 的核心差异按四点评:一是多路复用,1.1 一个连接上同时只能有一个请求在途(靠六个并发连接缓解,还有队头阻塞),2.0 用二进制分帧在一条连接上并行交错多个流;二是头部压缩(HPACK,1.1 每个请求都重复发一大坨头);三是服务端推送与流的优先级(推送已被实践淘汰,优先级也简化了);四是 2.0 依赖 TLS(实践中几乎都是 h2 over TLS),所以握手成本仍在。守住 1.1 的优化手段(域名分片、雪碧图、资源合并)在 2.0 下反而有害——这是「换到 HTTP 2.0 后哪些老优化要撤掉」的加分回答。HTTP 与 HTTPS 的区别除了加密,还要提完整性校验、身份认证(证书链)、以及握手增加的一到两个 RTT 和 TLS 1.3 带来的改进。

TCP 的「可靠」机制拆成序号与确认、超时重传与快速重传、滑动窗口流量控制、拥塞控制(慢启动、拥塞避免、快恢复)四块讲,前面已述。用户态与内核态则要讲清楚「为什么要分」:CPU 指令集里有特权级,内核态能访问全部内存与硬件、用户态只能访问自己的地址空间,分层的目的是隔离与安全。系统调用(read/write/sendfile)、中断、异常都会让 CPU 从用户态切到内核态,这次切换有保存现场的开销——网络编程里 epoll 与零拷贝(sendfile、mmap)之所以重要,就是在减少拷贝次数和用户态/内核态之间的来回。ping 一个域名要把 DNS 解析(先查 hosts 与本地缓存,再向递归解析器逐级问根、顶级域、权威)与 ICMP(ping 走的是 ICMP,不是 TCP)讲清楚,再顺带说「换成 curl 访问 URL」会加上 ARP、TCP 握手、TLS 与 HTTP 请求,这是常见的对照组。

事件循环与垃圾回收:宏任务(script、定时器、I/O 回调、UI 事件)与微任务(Promise.then、queueMicrotask、MutationObserver)的调度规则是「每执行完一个宏任务就把微任务队列清空,然后才是渲染机会」,所以微任务里写死循环会卡死页面且拿不到渲染机会,而 setTimeout 嵌套过深会被浏览器钳到最小 4ms。代码输出题要按这个顺序逐行推:同步代码 → 微任务 → 宏任务,注意 async 函数里 await 之后的部分也是微任务。垃圾回收讲标记清除(从根可达性出发,现代引擎主流)与引用计数(循环引用会泄漏,已不用),分代与增量/并发回收是为了控制停顿;常见泄漏是意外的全局变量、未清理的定时器与监听、闭包长期持有大对象、以及脱离 DOM 的引用。检查手段见另一场解析里讲的堆快照对比与 Detached DOM 排查。

性能优化与量化

回答性能题先给「指标 + 采集 + 分层」,再给动作,否则容易变成罗列。指标按加载(TTFB/FCP/LCP)、稳定(CLS)、交互(INP/TBT)分组;采集分实验室(Lighthouse、Performance 面板、PerformanceObserver)与线上(web-vitals 上报,看 p75 并按机型/网络/地区分层)。落地动作按阶段归:资源加载阶段做代码分割与懒加载、preload/preconnect、图片压缩与响应式尺寸、CDN 与 HTTP/2;渲染阶段做关键 CSS 内联、避免同步脚本阻塞、减少层级与重排、列表虚拟滚动;运行阶段做长任务切分(requestAnimationFrame/requestIdleCallback/Worker)、事件节流、内存与监听器治理。量化效果要给观测口径(同一页面、同一机型与网络、p75 前后对比)和归因(是资源变小了还是渲染提前了),不然「优化了」无法验证。

LCP 单独追问时要按它的四个组成拆(TTFB、资源加载延迟、资源加载耗时、元素渲染延迟),对应手段是 preconnect、fetchpriority 与 preload 首屏图、别给首屏图加 loading="lazy"、字体 font-display: swap、减少阻塞渲染的 CSS。虚拟滚动与懒加载的实现见另一场的解析,这里补两个常被追问的点:懒加载图必须给固定宽高比或宽高,否则 CLS 会爆;虚拟滚动要处理滚动位置恢复与快速滚动时的白屏(缓冲区大小与占位骨架),以及 key 不能用下标。

Agent 与工程实践类问题

「如何理解当前主流的 Agent 开发」这类题,答法是先给结构再给取舍:主流做法是「大模型 + 工具调用 + 循环 + 记忆」,循环里是「观察—决策—行动」的迭代,工程上关心的是工具定义(schema 描述清晰、粒度合适、错误信息可读)、上下文管理(什么进 prompt、什么时候压缩或检索回填)、以及终止条件(步数上限、超时、成果判定)。设计范式可以分三档说:单 Agent 加工具(最实用,覆盖大多数任务)、固定编排的流水线(规划—执行—评估,可控可测)、多 Agent 协作(按角色或子任务拆分,代价是上下文同步与成本翻倍)。追问「你觉得你们公司的场景适合这么做吗」时,要先问清任务是否有明确验收标准、出错代价多大、延迟预算多少,再判断该不该上循环和多智能体——能主动说出「很多任务单 Agent 加工具就够了,多智能体不是越多越好」是加分项。

tool 与 skill 的区别、skills 与 rules 的区别是同一族题目:tool 是暴露给模型调用的单个能力(有入参 schema、有明确副作用,一次调用做一件事),skill 是把一类任务的做法、步骤与脚本封装成的可复用能力包(会描述何时使用、按什么流程做、用到哪些工具),rules 则是常驻的约束与约定(编码规范、目录约定、禁止事项),它不描述流程、而是限制模型的行为边界。区别可以一句话概括:rules 回答「不许怎么做」,skill 回答「这类活该怎么做」,tool 回答「这一步具体怎么做」。被问到「怎么开发一个 skill」时讲清四件事:触发条件(什么时候该加载)、步骤与判据(做到什么程度算完成)、随附资源(脚本、模板、参考文档)、以及边界与失败处理。MCP 则是把工具与资源用统一协议暴露给任意客户端,解决的是「工具与宿主耦合」的问题,答的时候要能区分「协议层」与「具体工具实现」。

「大模型与 2020 年前传统模型的本质区别」要落到三点:一是规模带来的能力涌现与通用性(同一套参数不换模型结构就能做翻译、摘要、代码、对话,之前是每个任务一个模型加一套特征工程);二是训练范式的变化,从「标注数据 + 监督学习」转向「大规模自监督预训练 + 指令微调/对齐」,让模型学会的是「按指令完成任务」而不是「预测某个标签」;三是交互接口成了自然语言,带来上下文学习与工具调用能力。催生的核心原因是 Transformer 的注意力结构让训练可以并行、能吃下海量数据,加上算力与数据规模同时够到了阈值。Web Worker 的作用则要按「为什么需要」答:JS 单线程,重计算会阻塞渲染与交互,Worker 跑在独立线程,适合大文件解析、加解密、图片处理、复杂计算,通信靠 postMessage 传结构化克隆(大对象有拷贝开销,所以用 Transferable 转移 ArrayBuffer),代价是不能直接操作 DOM、调试与打包更麻烦。

手撕与场景题:从三栏布局到 Promise.all

三栏布局要按「两边定宽、中间自适应」给多种实现并说明差异:float + margin(老方案,要注意清除浮动与高度塌陷)、绝对定位(脱离文档流,中间列要留出两边宽度)、flex(两侧 flex: 0 0 200px、中间 flex: 1,注意 min-width: 0 否则内容会把中间撑破)、grid(grid-template-columns: 200px 1fr 200px 一行搞定)。追问「平均分」就是 repeat(3, 1fr) 或三等分 flex;再追问响应式就加媒体查询或 minmax。这题真正考的是你知不知道每种方案的副作用,而不是能不能写出来。

Promise.all 手写是高频题,但坑不在实现而在边界:要返回一个新的 promise、维护结果数组与 index(不能用 push,否则完成顺序会被打乱)、用计数器判断是否全部完成、任一 reject 就整体 reject,并且空数组要立即 resolve。那场面试里用 forEach 同步遍历、没判断完成数量就直接 resolve,正是最典型的错法——forEach 里的异步回调什么时候回来完全不确定,必须靠计数或 Promise.all 语义本身来收敛。扩展问题要准备:allSettled(永远成功、结果带 status)、race(第一个落定就定)、any(第一个成功就成功、全失败才失败),以及「并发上限」怎么办(自己写一个按批或按信号量调度的 pool)。括号合法性用栈,注意提前返回的剪枝(右括号进来时栈空即非法)和长度奇数的快速判断;二叉树层序遍历用队列(BFS),要按层收集就用「当前层长度」控制内层循环;3L 与 5L 量 4 升水是经典倒水题,答法是给可行步骤而不是背答案(装满 5L 倒进 3L 剩 2L、倒空 3L 把 2L 转过去、再装满 5L 补满 3L,5L 里剩 4L),被追问「最少几步」就说明这是最短路径搜索(BFS 遍历状态空间)。Git 场景题(两人不同分支要在测试环境联调)要给出可操作的流程:各自推到远端特性分支、用 merge 或 rebase 合到一条集成分支并解决冲突、用 git merge --abort / git rebase --abort 终止走错的合并,再部署这条集成分支;回答里要体现「不要直接改别人的分支、也不要在共享分支上 rebase」这条纪律。