面经 4.9
整理自 jiangxu.net · 2026.4.9 已剔除文件内重复条目(即原作者标注「见上方回答」的题目)
字节一面 --- 超高难度
- Reconciler 如何遍历 fiber 树(先序遍历)
答:Reconciler 通过
performUnitOfWork方法进行深度优先遍历(先序遍历),分为「递」和「归」两个阶段:递阶段(beginWork)从 hostRootFiber 开始向下遍历,为每个 Fiber 节点创建子节点;当无法继续向下时触发归阶段(completeWork),处理当前节点并收集副作用。如果有兄弟节点(sibling),进入兄弟节点的递阶段;否则返回父节点(return)的归阶段。
- 为什么要这么设计(这我上哪知道了)
答:因为 Fiber 架构使用链表结构(child/sibling/return 三个指针),链表遍历天然适合先序遍历,且最大的好处是可中断。不像 Stack 架构的递归调用栈一旦开始就必须执行完毕,链表结构可以随时记住当前遍历到的
workInProgress节点,当时间切片用完时暂停,下一帧继续从同一位置恢复。这就是 Fiber 架构解决 CPU 瓶颈的核心手段。
- DOM 树和 fiber 树的区别
答:DOM 树是浏览器维护的真实渲染树,操作开销大;Fiber 树是 React 在内存中维护的 JS 对象链表,用于描述 UI 结构。区别:①DOM 树是树形结构,Fiber 树是链表结构(通过 child/sibling/return 指针连接);②DOM 操作直接触发浏览器重排重绘,Fiber 树的计算在内存中完成不影响页面;③Fiber 架构有双缓冲机制(current 树和 workInProgress 树),更新完成后一次性切换。
- diff 算法是怎么比较新旧两个树的(还没学到 diff 算法呢)
答:React 的 diff 基于三个策略将 O(n³) 降到 O(n):①同层比较:只比较同一层级的节点,不跨层级;②类型判断:节点类型不同直接销毁旧树创建新树;③key 标识:同类型子元素列表通过 key 识别哪些元素移动/新增/删除。具体流程:单节点 diff 先比较 key 再比较 type,都相同则复用;多节点 diff 分两轮遍历——第一轮处理更新(key 相同的节点),第二轮处理新增/删除/移动。
- 浏览器从拿到渲染树以后都经过了哪些阶段(布局→分层→分块→光栅化→直接显示(其实是合成))
答:拿到渲染树后:①布局(Layout):计算每个节点的几何信息(位置、大小);②分层(Layer):根据层叠上下文、transform 等属性生成图层树;③绘制(Paint):为每个图层生成绘制指令列表;④分块(Tiling):将图层划分为更小的图块(tile),优先处理视口附近的图块;⑤光栅化(Rasterize):将图块转为位图(像素信息),由 GPU 执行;⑥合成(Composite):合成线程将光栅化后的图块合成为最终画面,提交给 GPU 显示。
- 为什么光栅化要由 GPU 去做(GPU 擅长光栅化的操作)
答:GPU 拥有大量并行计算核心(数千个),擅长同时处理大量简单的像素级运算。光栅化本质上是将矢量图形转换为像素点阵,每个像素的计算相互独立,天然适合并行处理。CPU 虽然单核性能强但核心数少,处理这类大规模并行任务效率远不如 GPU。
- 为什么会这样呢(疯了吧问这个)
答:这由硬件架构决定。CPU 是"少量核心 + 强大的控制逻辑 + 大缓存",适合串行复杂逻辑;GPU 是"海量核心 + 简单控制逻辑 + 小缓存",适合并行简单计算。图形渲染中每个像素的颜色计算公式基本相同(纹理采样、混合等),属于 SIMD(单指令多数据)场景,正是 GPU 的设计目标。
- Webpack 和 Vite 有什么区别
答:核心区别在开发模式:①Webpack:开发时打包所有模块再启动,项目越大启动越慢;用 CommonJS/ESM 皆可,通过 loader/plugin 处理各种资源;②Vite:开发时利用浏览器原生 ESM,不打包直接启动,按需编译请求的模块,启动速度极快;生产构建用 Rollup 打包。HMR 方面 Vite 也更快,因为只需要让修改的模块失效,浏览器重新请求该模块即可。
- Vite 打包用的什么(我想也是 ESM 吧)
答:Vite 开发模式用原生 ESM(浏览器直接 import),生产打包用的是 Rollup(不是 ESM)。原因是生产环境需要 tree-shaking、代码分割、压缩等优化,直接用 ESM 按需加载会产生大量 HTTP 请求影响性能,所以仍需要打包工具。Rollup 对 ESM 支持好、输出体积小,所以 Vite 选了它。
- ESM 和 CJS 区别(提到同步导入和异步导入)
答:①加载方式:CJS 同步加载(
require执行时立即读取文件),ESM 异步加载(import是静态声明,编译时确定依赖关系);②导出方式:CJS 导出的是值的拷贝(修改不互相影响),ESM 导出的是值的引用(live binding);③执行时机:CJS 运行时加载(可以动态 require),ESM 编译时静态分析(import 必须在顶层);④使用环境:CJS 是 Node.js 默认模块系统,ESM 是 ES6 标准,浏览器原生支持。ESM 的静态特性使得 tree-shaking 成为可能。
- 微任务队列和宏任务队列都是什么
答:微任务:Promise.then/catch/finally、MutationObserver、queueMicrotask。在当前宏任务执行完后、下一个宏任务之前全部清空。宏任务:setTimeout、setInterval、I/O、UI 渲染、MessageChannel、requestAnimationFrame(严格说 rAF 在渲染前执行,归类有争议)。事件循环流程:执行一个宏任务 → 清空所有微任务 → 浏览器判断是否需要渲染 → 下一个宏任务。
- 任务循环在浏览器和 Node 有什么区别(没研究过 Node 啊)
答:浏览器事件循环:一个宏任务 → 清空微任务 → 渲染 → 下一个宏任务,相对简单。Node 事件循环(基于 libuv)分为 6 个阶段:timers → pending callbacks → idle/prepare → poll → check → close callbacks。每个阶段之间都会清空微任务队列。Node 还有
process.nextTick(优先级高于 Promise 微任务)和setImmediate(check 阶段执行)。主要区别:Node 的事件循环更精细,有阶段划分;浏览器更简单但多了渲染步骤。
- Message channel 是什么(我猜是跨线程通信的手段还是什么玩意)
答:
MessageChannel是浏览器提供的 API,创建一个消息通道,包含两个MessagePort。向一个 port 发消息(postMessage),另一个 port 会触发onmessage回调。它的回调是宏任务,但比setTimeout(fn, 0)更快(没有 4ms 最小延迟)。既可以用于同页面的异步任务调度,也可以用于 Web Worker / iframe 间通信。
- 为什么 React 用了 Message channel 调度没用 setTimeout(React 本身是 Web worker 之类的多线程的吗?真不了解这个)
答:React 的 Scheduler 需要在一帧的空闲时间里执行任务,然后尽快把控制权还给浏览器。
setTimeout(fn, 0)有一个问题:嵌套超过 5 层后浏览器会强制最小延迟 4ms,这会白白浪费时间。而MessageChannel的回调是宏任务但没有 4ms 限制,能在当前帧渲染完成后尽快触发,实现更精确的时间切片。React 不是多线程的,它只是利用 MessageChannel 实现「让出主线程 → 浏览器渲染 → 继续 React 工作」的调度循环。
- 听说过 React 时间分片吗(没有)
答:时间分片(Time Slicing)是 Fiber 架构的核心能力。React 将渲染工作拆分为多个小单元(每个 Fiber 节点的处理),每处理一个节点就通过
shouldYield()检查当前时间切片是否还有剩余时间(默认 5ms)。如果时间用完,就暂停工作,通过 MessageChannel 发一个宏任务,让浏览器先去渲染和处理用户输入,然后在下一个宏任务中继续未完成的工作。这样即使有大量 Fiber 节点需要处理,页面也不会卡顿。
- 说一下 JavaScript 是不是单线程的语言(说了 Chrome 浏览器多进程架构和单标签页有哪些线程(主线程、Web worker、网络线程、Service worker))
答:JS 引擎本身是单线程执行的(一个调用栈),但浏览器是多线程的。Chrome 采用多进程架构(浏览器进程、渲染进程、GPU 进程、网络进程等),每个标签页的渲染进程中包含:主线程(执行 JS + 布局 + 绘制)、合成线程、光栅化线程等。开发者可以通过 Web Worker 开启独立 JS 线程处理计算密集任务,通过 Service Worker 实现离线缓存和网络代理。但 DOM 操作只能在主线程进行,这是为了避免多线程竞争导致 UI 不一致。
- 用过哪些设计模式(说了自己遇到的双重扩展问题)
答:可以结合项目说:①发布订阅模式:EventBus 用于组件间解耦通信;②策略模式:比如在我的 GenBI 项目中,不同图表类型的校验策略可以用
z.enum配合策略映射;③单例模式:全局状态管理 Store 就是单例;④装饰器/高阶组件模式:React.memo、ErrorBoundary 包裹组件增强功能;⑤观察者模式:React 的 useState/useEffect 本质上就是状态变化通知 UI 更新的观察者机制。
- 手撕:同时允许 2 个任务执行的异步调度器(现场没撕出来,面试结束以后加微信发了一个实现过去)
手撕代码:
class Scheduler {
constructor(maxCount) {
this.maxCount = maxCount;
this.running = 0;
this.queue = [];
}
add(promiseCreator) {
return new Promise((resolve) => {
this.queue.push(() => promiseCreator().then(resolve));
this._run();
});
}
_run() {
while (this.running < this.maxCount && this.queue.length) {
const task = this.queue.shift();
this.running++;
task().then(() => {
this.running--;
this._run();
});
}
}
}- 手撕:两个有序数组合并成一个有序数组
手撕代码:
function merge(arr1, arr2) {
const result = [];
let i = 0, j = 0;
while (i < arr1.length && j < arr2.length) {
if (arr1[i] <= arr2[j]) result.push(arr1[i++]);
else result.push(arr2[j++]);
}
while (i < arr1.length) result.push(arr1[i++]);
while (j < arr2.length) result.push(arr2[j++]);
return result;
}字节暑期一面 --- 中国广告交易
自我介绍
项目的来源
项目中的难点以及解决的方法
流式输出的方案的时候呢,服务端给不是你要的数据格式时候,怎么处理
答(引导到项目):在我的 GenBI 项目中也遇到类似问题——AI 返回的不是标准 JSON,可能前面带
```json标记或多余文字。我的处理方案是容错解析:先尝试JSON.parse,失败后用extractJsonObject提取第一个{到最后一个}之间的子串再解析。如果仍然失败,返回降级兜底配置。关键是不能信任 AI 输出,必须有防御层。
Markdown 格式的话出错或者说它格式不符合你要求,你是怎么处理的?(这里详细追问了好几个问题,直到问的答不上来)
答:对于 Markdown 格式异常,通用策略是:①设计容错 parser,允许不完整的 Markdown(比如流式输出时
```还没闭合);②对关键内容做 sanitize 过滤,防止注入恶意 HTML/JS;③设置兜底渲染——解析失败时降级显示纯文本。在我的项目中,AI 返回 ECharts JSON 时的格式问题,我用 Zod Schema 做结构校验 + sanitize 做安全清洗,任何异常都有 fallback。
虚拟列表解决的性能问题,性能问题是怎么发现的,怎么排查性能问题(详细追问,直到答不上来)
答(结合项目):我的 GenBI 项目中有一个"查看原始数据"功能,用户上传的 Excel 可能有几千行。最初用普通
map渲染所有行,3000 行数据点击查看时 Modal 弹出后明显 1-2 秒卡顿。排查过程:打开 Chrome DevTools 的 Performance 面板录制,发现 Rendering 阶段耗了 2000ms+,Long Task 占满主线程。切到 Elements 面板看到 DOM 树有 3000 个行节点——这就是瓶颈。解决:引入 react-window 的 FixedSizeList,只渲染可视区域约 15 个 DOM 节点,容器固定高度 + 占位元素撑滚动高度 + absolute 定位每行,DOM 节点从 3000 降到 ~15,渲染耗时 <50ms。
项目中的登录鉴权是怎么做的?(追问)
答:前端用 JWT Token 方案。用户登录后后端返回 token,存在 localStorage 中。封装 axios 请求拦截器,在每次请求的 header 中自动携带
Authorization: Bearer token。响应拦截器中判断 401 状态码自动跳转登录页。Umi Max 还提供了initialState全局状态管理用户信息 +access.ts做权限控制,未登录用户的路由会被自动重定向。
项目中最终有部署到线上吗?
有没有部署到上线的项目
答:GenBI 项目部署在个人服务器上(Docker 容器化)。我的个人主页 jiangxu.net 基于 Next.js,部署在 Vercel 上,用 GitHub Actions 实现 CI/CD——push 到 main 分支自动触发构建和部署。
项目完整的构建流程是怎样
答:以 GenBI 为例:
npm run build→ Umi 4 调用 Webpack 打包 → 按路由做代码分割生成多个 chunk → 输出到 dist 目录 → Docker 构建镜像(Nginx 作为静态资源服务器)→ docker-compose 部署。Umi 默认配置了 hash 文件名、代码压缩、Tree-shaking。
项目中的静态资源是怎么处理的?
答:图片等静态资源放在
public/目录直接引用,或src/assets/中通过 import 引入(Webpack 会处理成 base64 或独立文件)。构建后文件名带 contenthash,配合 CDN + 强缓存策略(Cache-Control: max-age=31536000),保证更新时浏览器能拿到新文件。
项目里的图片是怎么压缩的?
答:可以用 Webpack 插件(如 image-webpack-loader)在构建时自动压缩,或者使用在线工具(TinyPNG)预处理。对于大图片可以转 WebP 格式减少体积,小图标用 SVG 或 base64 内联减少请求。
nextTick 的作用是什么?
答:
nextTick是 Vue 提供的 API,将回调延迟到下一次 DOM 更新循环之后执行。Vue 的响应式更新是异步的(批量更新),修改 data 后 DOM 不会立即变化,nextTick确保你能在回调中拿到更新后的 DOM。底层实现优先用Promise.then(微任务),降级到MutationObserver,再降级到setTimeout。
伪元素有什么作用?
答:
::before和::after是 CSS 伪元素,在元素内容前后插入虚拟内容(通过content属性),不存在于 DOM 中。常用于:①装饰性内容(图标、分割线);②清除浮动(clearfix);③CSS 绘制三角形、气泡箭头等;④配合绝对定位做遮罩层、角标。好处是不增加 HTML 标签,保持语义清晰。
CSS 自定义变量有什么作用?
答:
--var-name定义,var(--var-name)使用。作用:①主题切换:通过修改根元素的 CSS 变量实现深色/浅色主题(我的个人主页项目就用这种方式);②减少重复:颜色、间距等统一管理;③动态计算:可以配合calc()使用;④运行时修改:通过 JSelement.style.setProperty('--color', 'red')动态更新,比 Sass 变量更灵活(Sass 是编译时)。
BFC 能解决什么问题?
答:BFC(块级格式化上下文)解决三个经典问题:①外边距塌陷:相邻元素的 margin 合并,BFC 可以隔离;②浮动高度塌陷:父元素不包含浮动子元素的高度,BFC 会包含;③浮动元素覆盖:非浮动元素被浮动元素覆盖,BFC 不会与浮动元素重叠。
怎样可以产生一个 BFC?
答:①
overflow不为visible(常用overflow: hidden);②display: flex/inline-block/grid/flow-root(flow-root最语义化);③float不为none;④position为absolute或fixed。推荐用display: flow-root,无副作用。
什么是暂时性死区?
答:暂时性死区(TDZ)是指在
let/const声明的变量从作用域顶部到声明语句之间的区域,在这个区域内访问该变量会抛出ReferenceError。虽然变量已经被"提升"(JS 引擎知道它的存在),但在声明之前不允许访问。
为什么会产生暂时性死区?
答:这是 ES6 的设计决策,目的是消除
var的变量提升带来的困惑和 bug。var声明会提升到函数顶部且初始化为undefined,容易导致意外使用未赋值变量。let/const虽然也会提升(引擎在编译阶段就知道有这个变量),但不会初始化,强制开发者先声明再使用。
用什么方式声明变量会存在暂时性死区?
答:
let和const声明的变量存在暂时性死区。var不存在(会提升并初始化为 undefined)。class声明也存在 TDZ。
讲一下生成器(Generator)和迭代器(Iterator)
答:迭代器:一个对象,拥有
next()方法,每次调用返回{ value, done }。任何实现了[Symbol.iterator]方法的对象都是可迭代的(如数组、Map、Set)。生成器:用function*定义的特殊函数,执行后返回一个迭代器。内部用yield暂停执行,调用next()恢复。可以用来实现惰性求值、异步流程控制(co 库)、无限序列等。
for…in 和 for…of 的区别是什么?
答:①
for...in遍历对象的可枚举属性的键名(包括原型链上的),适合遍历对象;②for...of遍历可迭代对象的值(数组、Map、Set、字符串等),不遍历普通对象。③遍历数组时for...in拿到的是索引字符串("0","1"),for...of拿到的是值。
自己写的普通对象能被 for…of 遍历吗?前提是什么?(没答上来)
答:可以,前提是对象实现
[Symbol.iterator]方法。这个方法需要返回一个迭代器对象(有next()方法)。例如:
const obj = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => ({
value: this.data[index],
done: index++ >= this.data.length
})
};
}
};
for (const v of obj) console.log(v); // 1 2 3手写:实现多个数组的全组合(笛卡尔积),如机型、颜色、存储全排列
手撕代码:
function cartesian(...arrays) {
return arrays.reduce((acc, arr) => {
const result = [];
acc.forEach(combo => {
arr.forEach(item => {
result.push([...combo, item]);
});
});
return result;
}, [[]]);
}
// cartesian(['iPhone','华为'], ['黑','白'], ['128G','256G'])
// → [['iPhone','黑','128G'], ['iPhone','黑','256G'], ...]手写:有效的括号(判断括号是否合法匹配)
手撕代码:
function isValid(s) {
const stack = [];
const map = { ')': '(', ']': '[', '}': '{' };
for (const c of s) {
if ('([{'.includes(c)) {
stack.push(c);
} else {
if (stack.pop() !== map[c]) return false;
}
}
return stack.length === 0;
}反问
对前端学习的建议,这里面试官直接说他们有面评 hr会给到(感觉有点不耐烦,不愿意回答)
面试结果多久出,面试官也是说直接联系hr
力竭了 拼尽全力无法战胜 感觉字节的面试是我面过最难受的一面
字节前端三面
- 技术发展趋势看法
答:前端正在经历三个大趋势:①AI 深度融合:AI Coding(Cursor/Claude Code)正在改变开发方式,前端需要掌握 Prompt Engineering、MCP、Agent 等概念;②全栈化:Next.js、Nuxt 等框架模糊了前后端边界,前端需要懂 SSR/SSG、数据库交互等;③Rust 化工具链:Vite、SWC、Turbopack 等用 Rust 重写的工具大幅提升构建性能。
职业规划
团队协作经验
遇到的最大技术挑战
答(结合项目):在 GenBI 项目中,最大的挑战是处理 AI 输出的不确定性。AI 返回的 ECharts 配置有 10-15% 的概率格式异常导致页面白屏。我通过设计三层防御(JSON 容错提取 → Zod Schema 校验 → sanitize 安全过滤)+ Error Boundary 兜底,最终将图表渲染崩溃率降为 0。这让我认识到:凡是不可控的外部数据源,都要用"不信任"的态度做多层防御。
- 如何解决线上紧急问题
答:①快速定位:查看监控告警 + 错误日志(Sentry 等),确定影响范围;②止血:能回滚就立即回滚到上一个稳定版本,不能回滚则用 feature flag 关闭问题功能;③修复:定位根因,本地复现,写修复代码并加测试;④复盘:记录问题原因、修复方案、预防措施,完善监控和告警规则。
- 对React Hooks的理解
答:Hooks 让函数组件拥有了状态和副作用管理能力,标志着 React 从面向对象向函数式编程的转变。核心 Hooks:
useState(状态管理)、useEffect(副作用处理,替代 componentDidMount/Update/Unmount)、useRef(不触发渲染的持久化值)、useMemo/useCallback(性能优化)。使用规则:只能在函数顶层调用,不能在条件/循环中——因为 React 靠调用顺序关联 Fiber 上的 Hook 链表。
- 状态管理工具选型
答:看项目规模:①简单项目:
useState+ Context 够用;②中型项目:Zustand(轻量、API 简洁)或 Jotai(原子化);③大型企业项目:Redux Toolkit(生态成熟、DevTools 强大、中间件丰富)。我的 GenBI 项目因为基于 Umi Max,内置了全局initialState,加上 Ant Design Pro 的约定,没有引入额外状态管理库。如果项目更复杂会选 RTK。
- 服务端渲染SSR实现
答:SSR 核心流程:①服务端运行 React 组件,调用
renderToString()生成 HTML 字符串返回给浏览器;②浏览器拿到 HTML 后直接展示(首屏快),同时下载 JS bundle;③JS 加载完成后执行hydrate()(注水),将事件绑定到已有的 DOM 上,页面变为可交互。我的个人主页用 Next.js App Router,博客页面用 SSG 静态生成(构建时生成 HTML),首屏性能最优。
- 前端安全如何保障
答(结合项目):①XSS 防护:对用户输入做转义;在我的 GenBI 项目中对 AI 返回的 ECharts 配置做
sanitize过滤,移除含function/=>的字符串和formatter/rich等危险字段,防止 AI 注入恶意 JS;②CSRF 防护:Token 机制 + SameSite Cookie;③CSP(内容安全策略):限制脚本来源;④HTTPS:加密传输;⑤HttpOnly Cookie:防止 XSS 窃取 Cookie。
- 反问环节
字节前端一面
全程大概是45分钟,面试官非常的和善,提问的时候会把自己的问题讲的很明白,会复述你的回答,double check问答是否有理解错误,感觉考察的是对于整个项目的了解程度,而不是简单的问你使用到了什么技术,所以还是要深挖自己的简历
自我介绍
实习项目穿插着八股
1、登录页面怎么做的
答:使用 Ant Design Pro 内置的登录页模板,表单收集用户名密码,调用后端登录接口。后端验证通过后返回 JWT Token,前端存入 localStorage。封装 axios 请求拦截器自动携带
Authorization: Bearer <token>。响应拦截器处理 401 跳转登录页。Umi Max 的initialState在应用初始化时请求用户信息,access.ts做路由级权限控制。
2、后端传过来的jwt存在哪里为什么放在cookie里,放在local storage里怎么携带的
答:两种方案各有利弊。Cookie:浏览器自动携带,可设 HttpOnly 防 XSS 窃取,但有 CSRF 风险(需 SameSite + Token 防御)、受同源限制。localStorage:手动在请求 header 携带(
Authorization: Bearer xxx),没有 CSRF 问题,但 XSS 攻击可以读取。我的项目用 localStorage 存储 + 请求拦截器携带,因为前后端分离场景更方便,XSS 风险通过 CSP 和输入过滤来缓解。
3、sessionStorage和localStorage有什么区别
答:①生命周期:localStorage 永久存储(除非手动清除),sessionStorage 页面关闭即清除;②作用域:localStorage 同源所有标签页共享,sessionStorage 仅当前标签页(新开标签不共享);③容量:都约 5MB;④使用场景:localStorage 适合持久化数据(Token、用户偏好),sessionStorage 适合一次性数据(表单暂存、页面状态)。
4、除了这种缓存还有其他的缓存手段(我答了协商缓存、强缓存)
答:浏览器缓存分多层:①HTTP 缓存:强缓存(Cache-Control/Expires,命中直接用本地副本,不发请求)+ 协商缓存(Last-Modified/ETag,发请求验证是否过期,304 = 继续用缓存);②本地存储:localStorage/sessionStorage/IndexedDB/Cookie;③Service Worker 缓存:可编程的离线缓存,拦截请求返回缓存响应;④内存缓存(Memory Cache):浏览器自动管理,页面关闭即释放。
5、协商缓存和强缓存下浏览器的请求行为,协商缓存的相关字段+状态码
答:强缓存:浏览器不发请求,直接读本地缓存,状态码 200(from disk/memory cache)。字段:
Cache-Control: max-age=31536000(优先级高)、Expires(绝对时间,已不推荐)。协商缓存:强缓存过期后浏览器发请求,带If-Modified-Since(对应Last-Modified)或If-None-Match(对应ETag),服务器判断:未修改返回 304 Not Modified(用缓存),修改了返回 200 + 新资源。ETag精确度更高(内容 hash),Last-Modified精确到秒。
6、大文件上传怎么做的
答:核心方案是分片上传:①前端将文件切割为固定大小的 chunk(如 2MB);②计算文件 hash(可用 Web Worker + SparkMD5 避免阻塞主线程);③并发上传各个 chunk,带上 hash + chunk 序号;④所有 chunk 上传完成后发送合并请求;⑤支持断点续传:上传前先请求后端已上传的 chunk 列表,跳过已有的。进度条通过
XMLHttpRequest.upload.onprogress实现。
7、为什么要用sse
答:SSE(Server-Sent Events)适合服务端单向推送场景,比如 AI 流式输出。相比 WebSocket,SSE 更简单:基于 HTTP,自动重连,浏览器原生
EventSourceAPI 即可使用。相比轮询,SSE 实时性更好、资源消耗更低。在我的 GenBI 项目的异步模式中,如果改用 SSE 替代轮询,可以实现图表生成状态的实时推送,不需要客户端反复请求。
8、sse连接断开怎么办
答:①自动重连:EventSource 自带重连机制,断开后默认 3 秒自动重连,服务端可以通过
retry:字段自定义间隔;②手动重连:监听onerror事件,判断readyState,如果是 CLOSED 则手动new EventSource重连;③心跳检测:服务端定时发送 ping 事件,前端设置超时计时器,超时未收到心跳则主动重连;④降级方案:多次重连失败后降级为轮询。
9、接口降级方案具体是怎么实现的
答:接口降级的核心是"兜底":①数据降级:请求失败时展示缓存数据或默认数据(我的项目中 Schema 校验失败返回 fallbackOption 兜底图表);②功能降级:核心功能异常时关闭非核心功能(如 AI 分析失败时只展示原始数据表格);③UI 降级:Error Boundary 捕获渲染异常,显示友好的错误提示而非白屏;④超时降级:设置请求超时,超时后走 fallback 逻辑。实现上通常在请求封装层(axios interceptor)统一处理。
10、主要观测的性能指标有哪些,LCP是怎么算的,具体LCP数值是多少
答:Core Web Vitals:①LCP(Largest Contentful Paint):最大内容元素绘制时间,衡量加载性能,2.5s 以内为好;②FID/INP(交互延迟):用户首次交互到响应的延迟;③CLS(Cumulative Layout Shift):累计布局偏移,衡量视觉稳定性。其他:FP、FCP、TTFB、TTI。LCP 的计算:浏览器在每次绘制后检查当前最大可见内容元素(图片、视频、大段文本块等),以其渲染完成时间为 LCP 值。通过
PerformanceObserverAPI 或 Lighthouse 测量。
11、性能优化做了哪些内容
答(结合项目):在 GenBI 项目中:①虚拟列表:react-window 渲染万行数据,DOM 从 N 降到 ~15 个;②路由级代码分割:Umi 默认按路由拆 chunk,首屏只加载当前页 JS,体积减少约 40%;③骨架屏:消除 CLS,用户感知加载更快;④条件轮询:指数退避 + Page Visibility API,减少 60% 无效请求;⑤useMemo/useCallback:缓存计算结果和函数引用,避免子组件无效重渲染。通用手段还有:图片懒加载、CDN、浏览器缓存、减少重排重绘等。
12、vite和webpack的流程、区别
答:Webpack 流程:入口分析 → 递归构建依赖图 → Loader 转换 → Plugin 优化 → 输出 bundle。开发时全量打包后启动 dev server。Vite 流程:启动 dev server(几乎即时)→ 浏览器请求模块 → Vite 按需编译返回 ESM → HMR 精确更新。生产构建用 Rollup。核心区别:Webpack 是 bundle-based(先打包再启动),Vite 是 unbundled(先启动再按需编译);Vite 利用浏览器原生 ESM + esbuild 预构建依赖,开发体验远快于 Webpack。
coding:
2、hot100中的括号生成,问了一下时间复杂度
手撕代码:
function generateParenthesis(n) {
const result = [];
function backtrack(str, open, close) {
if (str.length === 2 * n) { result.push(str); return; }
if (open < n) backtrack(str + '(', open + 1, close);
if (close < open) backtrack(str + ')', open, close + 1);
}
backtrack('', 0, 0);
return result;
}
// 时间复杂度 O(4^n / √n),第 n 个卡特兰数3、两个版本序列号排序问题
手撕代码:
function compareVersion(v1, v2) {
const a = v1.split('.'), b = v2.split('.');
const len = Math.max(a.length, b.length);
for (let i = 0; i < len; i++) {
const n1 = parseInt(a[i] || 0), n2 = parseInt(b[i] || 0);
if (n1 > n2) return 1;
if (n1 < n2) return -1;
}
return 0;
}字节前端一面
1.选一个项目介绍一下
答:介绍 GenBI——AI 驱动的智能 BI 平台。核心流程:用户上传 Excel → AI 自动生成 ECharts 图表和分析结论。技术栈 React 18 + TypeScript + Umi Max + Ant Design + ECharts + Zod。亮点:Zod Schema 校验层(图表崩溃率→0)、usePolling 指数退避轮询(减少 60% 请求)、react-window 虚拟列表(万行数据 <50ms)、react-grid-layout 拖拽看板。
2.Vue与React区别,组件式写法是什么
答:核心理念:React 是"不可变数据 + 全量 Diff"(应用级框架),Vue 是"响应式代理 + 精准追踪更新"(组件级框架)。模板 vs JSX:Vue 用 template,React 用 JSX(JS 表达能力更强)。状态更新:Vue 直接赋值自动响应(Proxy),React 必须 setState 返回新对象。组件式写法:将 UI 拆分为独立可复用的组件,每个组件管理自己的状态和逻辑,通过 props 通信。
3.Vue 2与Vue 3在的区别
答:①响应式:Vue2 用
Object.defineProperty(无法检测属性新增/删除、数组索引修改),Vue3 用Proxy(原生支持所有操作);②API:Vue3 引入 Composition API(setup 函数 + ref/reactive),替代 Options API,逻辑复用更好;③性能:Vue3 模板编译优化(静态提升、patchFlag 标记动态节点)、Tree-shaking 友好;④TypeScript:Vue3 用 TS 重写,类型支持更好;⑤其他:Vue3 支持多根节点(Fragment)、Teleport、Suspense。
5.项目打包的时候会在后面加一个哈希值,是什么
答:文件名后缀的 hash 是内容哈希(contenthash),基于文件内容生成。作用:①文件内容不变 → hash 不变 → 浏览器用缓存,避免重复下载;②文件内容改变 → hash 变 → 浏览器重新请求,拿到最新资源。这就是缓存失效策略,配合 CDN 强缓存使用,既保证缓存命中率,又保证更新及时性。Webpack 有三种 hash:
hash(整个构建)、chunkhash(chunk 级)、contenthash(文件级,最精确)。
6.前后端联调接口常见请求状态码
答:2xx 成功:200 OK、201 Created、204 No Content。3xx 重定向:301 永久重定向、302 临时重定向、304 协商缓存命中。4xx 客户端错误:400 Bad Request(参数错误)、401 Unauthorized(未登录)、403 Forbidden(无权限)、404 Not Found、405 Method Not Allowed。5xx 服务端错误:500 Internal Server Error、502 Bad Gateway、503 Service Unavailable(服务过载/维护)、504 Gateway Timeout。
10.promise.all和promise.allsettled的区别
答:
Promise.all:全部 fulfilled 才 resolve(返回结果数组),一个 rejected 就立即 reject。Promise.allSettled:等待所有 Promise 都 settled(无论成功/失败),返回每个结果的{ status, value/reason }数组,不会短路。场景:需要所有成功用 all,需要知道每个状态用 allSettled。
11.async await 和 Promise.then()的区别
答:本质上
async/await是 Promise 的语法糖,编译后就是 Promise + generator。区别:①写法:async/await 像同步代码,链式.then()是回调风格;②错误处理:async/await 用try/catch,Promise 用.catch();③调试:async/await 断点更友好,.then链调用栈难追踪;④并行:.then链天然串行,要并行需要Promise.all;async/await 也可以先发起再 await。
12.async await报错捕获方法
答:①try/catch(最常用):
try { await fn() } catch(e) { ... };②后置 .catch():await fn().catch(e => ...),可以单独处理某个请求的错误;③统一封装:将 async 函数包装成返回[error, data]元组的形式,避免大量 try/catch。
13.页面上有五六十个请求,希望能对所有请求进行统一报错拦截处理,怎么做
答:在 axios 的响应拦截器中统一处理。
axios.interceptors.response.use(res => res, error => { /* 统一错误处理 */ })。根据状态码分类处理:401 跳登录、403 提示无权限、500 提示服务器错误等。配合全局 message/notification 组件显示错误信息。还可以用window.addEventListener('unhandledrejection', ...)兜底捕获未处理的 Promise 异常。
14.用户反馈项目打开慢卡顿,怎么排查与优化
答:排查:①Chrome DevTools Performance 面板录制,找 Long Task 和瓶颈;②Network 面板查看请求瀑布图,找出大文件/慢请求;③Lighthouse 跑分,看 LCP/FCP/TBT 指标;④检查 JS bundle 大小(Webpack Bundle Analyzer)。优化:①代码分割(路由懒加载)减小首屏 JS;②图片压缩/懒加载/WebP;③CDN 加速静态资源;④浏览器缓存策略;⑤减少不必要的重渲染(React.memo/useMemo);⑥虚拟列表处理大数据;⑦SSR/SSG 提升首屏速度。
携程前端一面
1.开发前端是通过什么样的方式开发的?是有使用 AI 工具什么相关的吗?
答:日常开发使用 Cursor + Claude Code 辅助编码。Cursor 用于需求开发和代码补全,Claude Code 用于复杂重构和问题排查。AI 工具的使用心得:关键是提供足够的上下文(项目结构、需求背景、技术栈),配合 Rules 和 Skill 让 AI 理解项目规范。但核心设计决策和代码质量把控还是自己负责。
2.H5 跟原生的 APP 通信这个 jsbridge 你是怎么实现的
答:JSBridge 是 H5 和 Native 的通信桥梁。两种方案:①URL Scheme 拦截:H5 创建一个 iframe 设置
src=myapp://method?params=xxx,Native 拦截这个 URL 解析方法名和参数;②注入 JS 对象:Native 向 WebView 注入全局对象(如window.bridge),H5 直接调用window.bridge.callNative(method, params, callback)。回调通常通过window上注册回调函数,Native 执行完后调用evaluateJavascript触发回调。
7.瀑布流是怎么做的?
答:核心是多列等宽不等高布局。实现方案:①CSS multi-column:
column-count + column-gap,简单但顺序是先列后行;②绝对定位方案(最常用):JS 维护每列高度数组,新元素放到最矮的列,用 absolute 定位。top = 该列当前高度, left = 列索引 × (列宽 + 间距);③CSS Grid:grid-template-rows: masonry(实验性,支持度低)。
8.使用过虚拟列表吗
答(结合项目):在 GenBI 项目中用 react-window 的 FixedSizeList 做虚拟滚动展示万行 Excel 数据。原理:容器固定高度 + overflow:auto → 内部占位元素
height = itemCount × itemSize撑起真实滚动高度 → 每行用position:absolute; top: index × rowHeight定位 → 滚动时根据 scrollTop 计算可见范围,只渲染视口内 ~15 个节点。DOM 从 3000 降到 15,渲染耗时 <50ms。
12.有用过什么 AI 的开发工具吗?
答:主要用 Cursor(日常开发、代码补全、重构)和 Claude Code(复杂任务、终端操作)。也用过 GitHub Copilot。配合 MCP(Model Context Protocol)可以让 AI 工具访问外部数据源(数据库、API 文档等),提升上下文质量。
13.能不能实现全流程 AI 可以帮你把东西开发出来?如果说让你去把 AI 做你的一个员工,让他去帮你做这个事。你怎么让他去帮你完成的?
答:可以通过 AI 完成一个需求的大部分代码,但需要人来把控质量。我的流程:①先写清楚需求文档和技术方案(PRD → 拆分 task);②用 Rules 文件约束代码规范(ESLint 规则、项目结构、命名约定);③分步给 AI 下指令,每步验收后再进行下一步;④AI 生成代码后做 Code Review,关注边界情况、安全性、性能;⑤多个 AI 交叉 Review(Claude Review Cursor 的代码)。
携程好像是公司总部统一面试,之后才会细分部门,所以反问的时候问业务面试官都回答不了
南京小厂
介绍一下块级元素与行内元素 ✅
答:块级元素(div/p/h1-h6):独占一行,可设宽高/边距,默认宽度 100%。行内元素(span/a/em):不换行,宽高由内容决定,设 width/height 无效,水平方向 margin/padding 有效,垂直方向无效。行内块(img/input):不换行但可设宽高。可通过
display互相转换。
本地存储如果不用Pinia,除了LocalStorage还能用什么 ❌
答:①sessionStorage(页面级临时存储);②IndexedDB(大容量结构化数据存储,支持事务和索引,异步 API);③Cookie(4KB 限制,自动随请求携带);④Cache API(Service Worker 的缓存,适合缓存资源/响应);⑤内存变量(React 的 Context / Zustand 等状态管理)。
讲一下盒模型 ✅
答:每个元素由四层组成(从内到外):content → padding → border → margin。标准盒模型(
box-sizing: content-box):width/height 只包含 content,加 padding/border 总宽度变大。IE/怪异盒模型(box-sizing: border-box):width/height 包含 content + padding + border,更直观,是现代开发首选。
讲一下flex布局 ✅
答:弹性布局,父容器
display: flex,子元素按主轴排列。核心属性:容器:flex-direction(主轴方向)、justify-content(主轴对齐)、align-items(交叉轴对齐)、flex-wrap(换行)。子元素:flex-grow(放大)、flex-shrink(缩小)、flex-basis(基础大小)。flex: 1相当于flex-grow:1; flex-shrink:1; flex-basis:0%,占满剩余空间。
JS有哪些基础数据类型 ✅
答:7 种原始类型 + 1 种引用类型。原始:
string、number、boolean、null、undefined、symbol(ES6)、bigint(ES2020)。引用:object(包括数组、函数、Date、RegExp、Map、Set 等)。原始类型存栈,值不可变;引用类型存堆,变量存的是指针。
深拷贝和浅拷贝是什么,有哪些方法 ✅
答:浅拷贝:只复制第一层,嵌套对象仍是引用。方法:
Object.assign()、展开运算符{...obj}、Array.from()、slice()。深拷贝:完全独立的副本,所有层级都是新对象。方法:JSON.parse(JSON.stringify())(不支持函数/undefined/循环引用)、structuredClone()(原生深拷贝,支持循环引用)、手写递归(WeakMap 处理循环引用)。
你开发的项目有和后端对接吗
答:GenBI 项目和后端联调——后端是 Spring Boot,通过 Umi OpenAPI 插件根据 Swagger 文档自动生成 TypeScript 请求函数和类型定义,确保前后端类型一致。
遇到跨域问题怎么解决 ❌
答:①开发环境:dev proxy(Webpack devServer / Vite proxy),代理转发请求到后端,我的项目用 Umi 的
config/proxy.ts配置;②生产环境:CORS(后端设Access-Control-Allow-Origin响应头),或 Nginx 反向代理将前后端统一为同一域名;③其他:JSONP(仅 GET,基本淘汰)。
两个数组如果不用API方法怎么完成去重
答:
function uniqueMerge(arr1, arr2) {
const result = [];
const merged = [...arr1, ...arr2]; // 先合并(这里也可以手写循环)
for (let i = 0; i < merged.length; i++) {
let isDuplicate = false;
for (let j = 0; j < result.length; j++) {
if (merged[i] === result[j]) { isDuplicate = true; break; }
}
if (!isDuplicate) result.push(merged[i]);
}
return result;
}你用过vue全家桶吗,有哪些
答:Vue Router(路由管理)、Pinia/Vuex(状态管理)、Vue CLI/Vite(构建工具)、Vue DevTools(调试)。
怎么实现路由跳转
答:①声明式:
<router-link to="/path">(Vue)/<Link to="/path">(React);②编程式:router.push('/path')(Vue)/navigate('/path')(React useNavigate)。
路由的模式有哪些 ✅
答:①Hash 模式:URL 带
#(如/#/about),通过hashchange事件监听变化,兼容性好,不需要服务端配置;②History 模式:URL 干净(如/about),通过pushState/popstateAPI 实现,需要服务端配置所有路径返回index.html;③Memory 模式:路由状态存内存,不修改 URL,适用于非浏览器环境(如 React Native)。
路由守卫有哪些(忘了后置守卫干嘛用)
答:Vue Router 三类守卫:①全局守卫:
beforeEach(鉴权判断,最常用)、afterEach(后置,用于页面标题设置、埋点统计、回到顶部等,不接受 next)、beforeResolve(所有组件内守卫和异步路由组件解析之后);②路由独享守卫:beforeEnter(写在路由配置里);③组件内守卫:beforeRouteEnter/Update/Leave。
ref 和 reactive 的区别 ✅
答:①
ref:包装基本类型和对象,返回{ value: xxx },模板中自动解包不需要.value,JS 中需要;②reactive:只能用于对象/数组,返回 Proxy 代理对象,直接访问属性无需.value;③解构丢失响应式:reactive解构后会丢失响应式(因为解构是取值),ref不会;④一般建议:简单值用ref,复杂对象用reactive。
v-if和v-show的区别 ✅
答:
v-if:条件为 false 时完全不渲染 DOM(销毁/重建),适合不频繁切换的场景,有更高的切换开销。v-show:始终渲染 DOM,只切换display: none,适合频繁切换的场景,有更高的初始渲染开销。
LLM CHAT的接口是怎么样的(批量返回,JSON字符串) ✅
答(结合项目):在我的 GenBI 项目中,后端调用大模型的 OpenAI 兼容 API(POST
/chat/completions),请求体包含messages数组 +model+temperature。AI 返回 JSON 响应,choices[0].message.content是结果字符串。我们用特殊分隔符【【【【将 ECharts JSON 和文字结论分开,前端收到后拆分并分别处理。
用过webSocket吗?讲一下你对它的理解 ✅
答:WebSocket 是全双工通信协议,在 TCP 连接上实现客户端和服务端双向实时通信。与 HTTP 的区别:①HTTP 请求-响应模式,WebSocket 建立后双方可随时发送数据;②通过 HTTP 101 状态码升级协议(Upgrade: websocket);③适用场景:实时聊天、协同编辑、在线游戏、股票行情等需要双向推送的场景。API:
new WebSocket(url)→onopen/onmessage/onclose/onerror→send()/close()。
小厂前端
1.B端项目和C端项目的区别
答:B 端(面向企业):功能复杂、表单/表格为主、注重效率和稳定性、用户量小但使用频率高、技术选型偏 Ant Design/Element。C 端(面向消费者):UI/UX 要求高、注重性能和加载速度、用户量大需要做 SEO、技术选型偏 Tailwind/自研组件库。我的 GenBI 项目偏 B 端(数据分析平台),个人主页偏 C 端。
2.display: none、visibility: hidden 和 opacity: 0 的区别
答:①
display: none:完全不渲染,不占空间,不响应事件,子元素也不可见,会触发回流;②visibility: hidden:渲染但不可见,占据空间,不响应事件,子元素可以设visibility: visible单独显示,只触发重绘;③opacity: 0:渲染但透明,占据空间,可以响应事件(点击等),子元素也透明。
4.垂直水平居中布局,transform和translate区别
答:居中方案:①
flex: display:flex; justify-content:center; align-items:center;;②绝对定位 + transform: position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);;③grid: display:grid; place-items:center;。transform vs translate:transform是 CSS 属性,可以做translate/rotate/scale/skew等变换;translate是transform的一个函数值。CSS 新规范支持translate作为独立属性:translate: -50% -50%。
5.页面上线3s白屏分析可能的原因
答:①JS bundle 过大:首屏加载大量 JS,主线程被占用;②阻塞渲染的资源:head 中有未加 defer/async 的大 script 或 CSS;③接口请求慢:首屏依赖的数据接口响应慢;④DNS 解析/服务器响应慢:TTFB 过高;⑤CSR 白屏:SPA 需要 JS 执行后才有内容,可考虑 SSR/SSG;⑥重定向链过长:多次 302 跳转消耗时间。排查:Lighthouse + Performance 面板 + Network 面板。
6.输入url会发生什么
答:①URL 解析(协议/域名/路径)→ ②DNS 解析(缓存→递归查询→获取 IP)→ ③TCP 三次握手(HTTPS 还有 TLS 握手)→ ④发送 HTTP 请求 → ⑤服务器处理返回响应 → ⑥浏览器解析 HTML 构建 DOM 树 → ⑦解析 CSS 构建 CSSOM → ⑧合成渲染树 → ⑨布局(Layout)→ ⑩绘制(Paint)→ ⑪合成(Composite)→ ⑫页面展示。
7.了解CDN、nginx和服务器部署吗
答:CDN:将静态资源分发到全球各地的边缘节点,用户就近访问,减少延迟。Nginx:高性能 Web 服务器,常用于:反向代理(前后端统一域名解决跨域)、负载均衡、静态资源托管、gzip 压缩、HTTPS 证书配置。部署流程:
npm run build输出 dist → Nginx 配置root /path/to/dist; try_files $uri /index.html;(SPA history 模式需要)。
8.script标签的defer和async的区别
答:①无 defer/async:阻塞 HTML 解析,下载 + 执行完才继续解析;②async:异步下载不阻塞解析,下载完立即执行(会阻塞解析),执行顺序不确定,适合独立脚本(统计代码);③defer:异步下载不阻塞解析,在 HTML 解析完后、DOMContentLoaded 前按顺序执行,适合有依赖关系的脚本。
10.深拷贝和浅拷贝为什么会有这个概念的出现
答:因为 JS 中引用类型(对象/数组)存储的是堆内存地址,直接赋值只是复制指针,两个变量指向同一块内存,修改一个会影响另一个。为了获得独立的数据副本,需要拷贝。浅拷贝只复制第一层(嵌套对象仍共享引用),深拷贝递归复制所有层级(完全独立)。
11.const a=1 存在深拷贝和浅拷贝的问题吗
答:不存在。
1是原始类型,存在栈中,赋值就是直接拷贝值本身。深浅拷贝只针对引用类型(对象/数组),因为引用类型的"赋值"只是复制了指针。
12.const a={b:1} 和 const a = new Object({b = 1});的区别
答:功能上几乎没有区别(注意语法应该是
new Object({b: 1}))。两者都创建了一个包含属性 b 的对象。区别:字面量{}是语法糖,性能略好(引擎直接创建对象);new Object()是构造函数调用,多了一步函数调用开销。实际开发全用字面量。
13.new一个对象发生了什么
答:四步:①创建一个空对象
{};②将空对象的__proto__指向构造函数的prototype(建立原型链);③以这个空对象为this执行构造函数(属性赋值);④如果构造函数返回对象则用该对象,否则返回新创建的对象。
14.原型和原型链
答:每个对象有一个
__proto__属性指向它的原型对象,每个构造函数有一个prototype属性。当访问对象属性时,先在对象自身找,找不到就沿着__proto__向上查找——这条查找链路就是原型链。终点是Object.prototype.__proto__ === null。instanceof就是沿着原型链判断left.__proto__链上是否能找到right.prototype。
15.节流防抖,手撕一个防抖
答:防抖:事件触发后等待一段时间才执行,期间再次触发则重新计时(搜索框输入)。节流:规定时间内只执行一次,频繁触发不重置(滚动事件)。
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}腾讯前端暑期实习一面
- DNS解析详细描述
答:DNS 解析是将域名转为 IP 地址的过程,按缓存层级查找:①浏览器 DNS 缓存 → ②操作系统 DNS 缓存(hosts 文件)→ ③路由器缓存 → ④本地 DNS 服务器(ISP 提供)→ ⑤递归查询:本地 DNS → 根域名服务器(返回 .com 顶级域服务器地址)→ 顶级域服务器(返回权威 DNS 地址)→ 权威 DNS 服务器(返回最终 IP)。查到后各级缓存存储,下次直接命中。
- vite处理兼容性和polyfill
答:Vite 通过
@vitejs/plugin-legacy插件处理旧浏览器兼容:①使用@babel/preset-env将现代 JS 语法转为 ES5;②自动注入所需的 polyfill(基于 core-js);③生成两套产物:现代浏览器用原生 ESM 版本(更小),旧浏览器用 legacy 版本(带 polyfill);④通过<script type="module">和<script nomodule>让浏览器自动选择。
- 怎么评判是新浏览器还是旧浏览器
答:①browserslist 配置:在
package.json或.browserslistrc中定义目标浏览器范围(如"> 1%, last 2 versions, not dead"),构建工具据此决定转译程度;②User-Agent 检测:服务端根据 UA 字符串判断(不推荐,不准确);③特性检测:<script type="module">天然区分——支持 ESM 的就是现代浏览器(Chrome 61+/Firefox 60+/Safari 11+),不支持的降级到 nomodule。
- SSR和SSG
答:SSR(服务端渲染):每次请求时服务端运行 React/Vue 生成 HTML 返回,适合动态内容(用户个性化页面)。优点:首屏快、SEO 好。缺点:服务器压力大。SSG(静态站点生成):构建时预生成所有页面的 HTML,适合内容不常变的页面(博客、文档)。优点:性能最好(直接返回静态文件)、可 CDN 分发。缺点:内容更新需要重新构建。
- CSR
答:CSR(客户端渲染):服务器返回空 HTML + JS bundle,浏览器下载执行 JS 后渲染页面。优点:前后端分离、交互体验好。缺点:首屏慢(白屏时间长)、SEO 差(爬虫看到空页面)。SPA 默认就是 CSR。
- 应用场景
答:CSR:后台管理系统、不需要 SEO 的 Web App(如我的 GenBI 项目)。SSR:电商首页、社交媒体(需 SEO + 动态内容)。SSG:博客、文档站、营销落地页(如我的个人主页用 Next.js SSG)。现代框架(Next.js)支持混合使用,同一项目不同页面可以选不同策略。
- 用户访问量太大时怎么判断服务器正不正常,监控哪些指标
答:①服务端指标:CPU 利用率、内存占用、磁盘 I/O、网络带宽、请求队列长度、响应时间(P99/P95)、错误率(5xx 比例);②前端指标:TTFB(首字节时间)、LCP、FCP、接口响应时间、JS 错误率;③监控工具:Prometheus + Grafana(服务端)、Sentry(前端错误监控)、Lighthouse CI(性能监控)。
- 怎么优化这些指标
答:服务端:负载均衡(Nginx/K8s)、缓存(Redis)、CDN 分流静态资源、数据库优化(索引/读写分离)、限流熔断。前端:代码分割减小 bundle、SSR/SSG 加速首屏、资源压缩(gzip/brotli)、图片优化、Service Worker 缓存、预加载关键资源(preload/prefetch)。
- 用过ai么,怎么用的
- 了不了解用没用过mcp/skills
答:MCP(Model Context Protocol)是连接 AI 模型和外部数据源的协议标准,让 AI 工具能调用数据库查询、文档搜索等外部能力。Skill 是 AI 编码工具中的专项知识模块,定义特定领域的最佳实践和工作流。区别:MCP 是通信协议(解决 AI 怎么获取外部数据),Skill 是知识包(解决 AI 怎么做好特定任务)。
手写题:实现LRU缓存
手撕代码:
class LRU {
constructor(capacity) {
this.capacity = capacity;
this.map = new Map(); // Map 保持插入顺序
}
get(key) {
if (!this.map.has(key)) return -1;
const val = this.map.get(key);
this.map.delete(key);
this.map.set(key, val); // 重新插入=更新为最近使用
return val;
}
put(key, val) {
if (this.map.has(key)) this.map.delete(key);
this.map.set(key, val);
if (this.map.size > this.capacity) {
this.map.delete(this.map.keys().next().value); // 删最久未用(第一个)
}
}
}京东前端一面面筋2026.03.23
介绍AI Coding
介绍你的项目经历
常见的布局方式
答:①正常流布局(block/inline);②浮动布局(float,逐渐淘汰);③Flex 弹性布局(一维布局,最常用);④Grid 网格布局(二维布局);⑤绝对定位布局(position:absolute/fixed);⑥多列布局(column-count)。
- AI Coding有哪些应用场景
答:①代码补全与生成(Cursor/Copilot);②代码重构与优化;③Bug 定位与修复;④Code Review(多 AI 交叉审查);⑤测试用例生成;⑥文档生成(注释、API 文档);⑦需求分析与技术方案设计;⑧UI 组件快速开发。
- 水平居中的实现方式
答:①行内元素:
text-align: center;②块级元素:margin: 0 auto(需要有宽度);③Flex:display:flex; justify-content:center;;④绝对定位 + transform:left:50%; transform:translateX(-50%);⑤Grid:display:grid; place-items:center;。
- 重排和重绘
答:重排(Reflow/回流):元素几何属性(宽高/位置/显示状态)改变,触发重新计算布局,开销最大。重绘(Repaint):元素外观属性(颜色/背景/阴影)改变,不影响布局,只需重新绘制。重排一定触发重绘,重绘不一定触发重排。优化:批量修改 DOM、使用
transform/opacity做动画(只触发合成,不触发重排重绘)、will-change提示浏览器创建独立图层。
- watch和computed的区别
答:computed:计算属性,基于响应式依赖缓存,依赖不变就不重新计算,适合从现有数据派生新数据(如过滤列表)。watch:监听数据变化执行副作用(发请求、操作 DOM),不缓存,每次变化都执行。总结:需要返回值用 computed,需要执行操作用 watch。
手写:
- 将一维数组转换为二维数组
手撕代码:
function chunk(arr, size) {
const result = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
}
// chunk([1,2,3,4,5], 2) → [[1,2],[3,4],[5]]- Promise.all手写
手撕代码:
function PromiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) return reject(new Error('非数组'));
const result = [];
let count = 0;
const len = promises.length;
if (len === 0) return resolve(result);
promises.forEach((p, index) => {
Promise.resolve(p).then(res => {
result[index] = res;
if (++count === len) resolve(result);
}).catch(reject);
});
});
}美团前端一面面经
为什么选择学前端
个人学前端的优势
ES6新增特性
答:①
let/const(块级作用域);②箭头函数;③模板字符串;④解构赋值;⑤展开运算符;⑥Promise;⑦class语法糖;⑧import/export(ESM 模块化);⑨Symbol;⑩Map/Set;⑪for...of;⑫默认参数/剩余参数;⑬Proxy/Reflect;⑭async/await(ES2017)。
变量提升原理
答:JS 引擎在执行代码前有编译阶段,会将
var声明的变量提升到函数/全局作用域顶部并初始化为undefined;函数声明整体提升。let/const也会提升但不初始化(暂时性死区)。本质是 JS 的执行上下文创建阶段就收集了所有声明。
null与undefined区别
答:①
undefined:变量声明了但未赋值的默认值,函数没有返回值时也返回 undefined;②null:表示"有意设为空值",typeof null === 'object'(历史 bug),常用于初始化或清空引用。③undefined == null为 true,undefined === null为 false。
this绑定方式(call、apply、bind)
答:①
call(thisArg, arg1, arg2...):立即执行,逐个传参;②apply(thisArg, [args]):立即执行,数组传参;③bind(thisArg, arg1...):不立即执行,返回绑定好 this 的新函数。优先级:new>bind>call/apply> 隐式绑定(obj.fn())> 默认绑定(window/undefined)。
POST与GET请求区别
答:①语义:GET 获取数据,POST 提交数据;②参数位置:GET 在 URL query(有长度限制),POST 在请求体(无限制);③缓存:GET 可被缓存,POST 不会;④幂等性:GET 幂等(多次请求结果相同),POST 非幂等;⑤安全性:都不安全(HTTPS 才安全),但 GET 参数暴露在 URL 中更容易泄露。
cookie与localStorage区别
答:①容量:Cookie 4KB,localStorage 5MB;②携带:Cookie 每次请求自动携带,localStorage 需手动读取;③有效期:Cookie 可设过期时间,localStorage 永久;④作用域:Cookie 可设 domain/path 限制,localStorage 同源共享;⑤API:Cookie 原生操作繁琐(document.cookie),localStorage API 简洁(setItem/getItem)。
移动端localStorage清理
答:移动端浏览器(尤其 iOS Safari)在存储空间不足时可能自动清理 localStorage。应对:①重要数据同步到服务端;②使用前先检查数据是否存在,做好降级;③IndexedDB 相对更稳定。
JS继承方式及优缺点
答:①原型链继承:
Child.prototype = new Parent()——缺点:引用类型属性共享、无法传参;②构造函数继承:Parent.call(this)——缺点:方法不能复用;③组合继承(最常用):构造函数继承属性 + 原型链继承方法——缺点:Parent 构造函数调了两次;④寄生组合继承(最优):Child.prototype = Object.create(Parent.prototype)替代 new Parent();⑤ES6 class extends:语法糖,本质是寄生组合继承。
快速排序手写
手撕代码:
const quick = arr => {
if (arr.length <= 1) return arr;
const mid = arr[Math.floor(arr.length / 2)];
const left = [], right = [];
arr.forEach(v => {
if (v < mid) left.push(v);
else if (v > mid) right.push(v);
});
return [...quick(left), mid, ...quick(right)];
};南京某小厂前端实习面经
1.自我介绍
2.MCP(因为我在简历上写自己对agent知识有了解,其实我只是用过cc和cursor)
3.讲自己ai工具的使用
4.拷打项目
讲一下SSE
答:SSE(Server-Sent Events)是基于 HTTP 的服务端单向推送技术。客户端通过
new EventSource(url)建立长连接,服务端以text/event-stream格式持续推送数据。相比 WebSocket:协议更简单(基于 HTTP)、自动重连、浏览器原生支持。适合 AI 流式输出、实时通知等场景。
虚拟列表怎么实现的,是用的第三方库吗,了解底层原理吗
答(结合项目):用的 react-window(react-virtualized 的轻量版)。原理:容器固定高度 + overflow:auto,内部占位元素撑真实滚动高度(height = itemCount × itemSize),可见行用 absolute 定位(top = index × rowHeight),滚动时根据 scrollTop 计算 startIndex/endIndex,只渲染这个范围内的行 + overscan 缓冲行。
为什么用IndexedDB不用localstorage,是有好处还是为了用而用
答:IndexedDB 相比 localStorage 的优势:①容量大(数百 MB vs 5MB);②异步 API(不阻塞主线程);③支持索引和事务(可以按条件查询);④存储结构化数据(对象/二进制文件,不需要 JSON 序列化)。如果只是存简单的键值对(如 Token、主题偏好),localStorage 就够了;如果需要存大量结构化数据(如离线缓存、聊天记录),IndexedDB 更合适。
5.八股部分
1.讲watch和watcheffect
答:
watch:需要明确指定监听的数据源,接收新旧值,可设immediate/deep。watchEffect:自动追踪回调内部用到的响应式数据,组件初始化时立即执行一次(类似watch加immediate: true),不提供新旧值。用watchEffect更简洁,用watch更精确可控。
2.vue3的setup语法糖
答:
<script setup>是 Vue3 的编译时语法糖,替代显式定义setup()函数。好处:①顶层变量/函数自动暴露给模板(不需要 return);②defineProps/defineEmits声明 props 和事件;③代码更简洁,减少样板代码;④更好的 TypeScript 类型推导。
3.怎么遍历数组和对象
答:数组:
for循环、forEach、for...of、map/filter/reduce。对象:for...in(含原型链,需 hasOwnProperty 过滤)、Object.keys()(自身可枚举键)、Object.values()、Object.entries()(键值对数组,可配合for...of)。
4.响应式怎么设计
答:Vue3 用
Proxy实现响应式。核心三步:①reactive/ref:用 Proxy 包装对象,拦截 get/set 操作;②依赖收集(track):get 时记录"哪个 effect 读取了这个属性"(Dep 收集 subscriber);③触发更新(trigger):set 时通知所有订阅该属性的 effect 重新执行,从而更新 UI。
5.组件通信方式
答:①props / emit(父子通信,最基本);②provide / inject(跨层级);③Pinia/Vuex(全局状态管理);④Event Bus(任意组件,Vue3 需用第三方库 mitt);⑤ref + defineExpose(父调子方法);⑥v-model(双向绑定语法糖)。
- 之前也聊到过Skill,MCP,你可以讲一下MCP和Skill的区别吗?
答:MCP(Model Context Protocol):是一个通信协议/标准,定义了 AI 模型如何调用外部工具和数据源(数据库查询、API 调用、文件读写等),解决的是"AI 怎么获取信息"的问题。类似于 AI 的"USB 接口"。Skill:是 AI 编码工具内部的"专项知识包",包含特定领域的最佳实践、工作流指引、提示词模板,解决的是"AI 怎么做好这个任务"的问题。简而言之:MCP = 能力通道,Skill = 领域知识。
美团暑期前端一面面经
项目的整体流程是怎样的?
答(GenBI):5 步:①用户上传 Excel + 分析目标 → ②后端将数据转 CSV 拼接 prompt 调用 AI → ③AI 返回 ECharts JSON + 文字结论 → ④前端 Zod Schema 校验 + sanitize 过滤 + Error Boundary 兜底渲染 → ⑤用户在拖拽看板组合展示图表。支持同步(等结果)和异步(轮询获取状态)两种模式。
为什么用localstorage存储?Local Storage 本身有大小的限制吗?会不会丢失上下文?业界有没有比较好的做法?
答:localStorage 存看板布局数据,简单无后端依赖。限制:①5MB 大小限制;②只在当前浏览器有效,清缓存会丢失;③不能跨设备同步。业界更好的做法:①后端持久化(新增 dashboard 表存 layout JSON);②IndexedDB(容量大、支持结构化存储);③配合 Service Worker 做离线缓存。
必须要用虚拟滚动吗 为什么
答:不是必须,要看数据量。几十上百条数据普通渲染即可。但当数据量大(几千行以上),DOM 节点过多会导致:①布局计算耗时线性增长;②内存占用高;③滚动卡顿。我的项目中用户可能上传几千行 Excel,Performance 面板录制发现 3000 行 DOM 渲染耗时 2000ms+,所以必须用虚拟滚动。
基本类型与引用类型的区别
答:基本类型:存在栈中,值不可变,赋值是拷贝值。引用类型:值存在堆中,变量存的是堆地址(指针),赋值是拷贝指针(共享同一份数据)。所以引用类型才有深浅拷贝的问题。比较时,基本类型比较值,引用类型比较地址。
let 和 const 的区别
答:都是块级作用域,都有暂时性死区,不会提升。区别:
let可以重新赋值;const声明时必须初始化且不能重新赋值(但如果是对象/数组,可以修改其属性/元素,因为地址没变)。
如何检测对象类型
答:①
typeof:检测基本类型(注意typeof null === 'object'是 bug);②instanceof:检测是否在原型链上;③Object.prototype.toString.call():最准确,返回[object Type];④Array.isArray():专门判断数组。
instanceof 的作用
答:判断对象是否是某个构造函数的实例,本质是沿着
__proto__原型链查找,看是否能找到Constructor.prototype。
未知自定义实例如何判断类型
答:用
Object.prototype.toString.call(obj)获取[object Type],或者用obj.constructor.name获取构造函数名。
手写实现类型判断(Object.prototype.toString.call 逻辑)
手撕代码:
function getType(val) {
if (val === null) return 'null';
if (typeof val !== 'object') return typeof val;
return Object.prototype.toString.call(val).slice(8, -1).toLowerCase();
}
// getType([]) → 'array', getType({}) → 'object', getType(null) → 'null'手写防抖和节流
手撕代码:
// 防抖
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 节流
function throttle(fn, interval) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= interval) {
last = now;
fn.apply(this, args);
}
};
}防抖的作用与使用场景
答:防抖:在事件停止触发一段时间后才执行。场景:搜索框输入联想(停止打字后才请求)、窗口 resize 结束后重新计算布局、表单验证。节流场景:滚动加载、鼠标移动、按钮防重复点击。
一面(面试官是架构师):
了不了解跨端框架,说一下你对跨端框架的理解
答:跨端框架解决一份代码运行在多个平台的问题。主流方案:①React Native:用 JS 写逻辑 + 原生组件渲染,接近原生性能;②Flutter:Dart 语言 + Skia 引擎自绘 UI,一致性最好;③Taro/uni-app:编译时转换,一套代码编译为小程序/H5/App;④Electron/Tauri:Web 技术做桌面端。选型看场景:追求性能选 Flutter/RN,追求多端小程序覆盖选 Taro。
vue 的响应式原理,响应式数据变化时会发生什么
答:Vue3 用
Proxy代理对象。①初始化:reactive()将对象包裹为 Proxy;②依赖收集:组件渲染时读取属性,触发 get → track() 将当前 effect(渲染函数)记录到该属性的依赖集合中;③触发更新:修改属性触发 set → trigger() 通知所有依赖该属性的 effect 重新执行 → 组件重新渲染 → 生成新的虚拟 DOM → diff → 最小化更新真实 DOM。
vue 的生命周期,created 和 mounted 的差异
答:created:组件实例创建完成,data/methods 可用,但 DOM 尚未挂载($el 不存在),适合发请求初始化数据。mounted:DOM 挂载完成,可以操作真实 DOM($el 可用),适合依赖 DOM 的操作(初始化第三方库如 ECharts)。
vue 的 diff 算法
答:Vue 的 diff 采用双端比较:头头、尾尾、头尾、尾头四种比较,比 React 的单向遍历更高效。如果都不匹配,用 key 建立映射表查找。Vue3 进一步优化:编译时标记动态节点(patchFlag),diff 时只比较动态部分,跳过静态内容。
webpack 中 plugin 和 loader 的区别
答:Loader:文件转换器,将非 JS 文件(CSS/图片/TS 等)转换为 Webpack 能处理的模块,在
module.rules中配置,链式执行(从右到左)。Plugin:功能扩展器,通过钩子(hook)介入 Webpack 编译的各个阶段,可以做打包优化、资源管理、环境变量注入等。例:babel-loader(转 ES5)、HtmlWebpackPlugin(生成 HTML)、MiniCssExtractPlugin(提取 CSS)。
进程和线程的区别
答:进程:操作系统资源分配的最小单位,有独立的内存空间,进程间隔离。线程:CPU 调度的最小单位,同一进程的线程共享内存空间,切换开销小。一个进程包含至少一个线程。类比:进程 = 工厂,线程 = 工厂里的工人,共享工厂资源。
快手暑期前端一面 3.25
全是 AI 相关内容,八股没问。算法部分:
算法:
- 最长不重复子串
手撕代码:
function lengthOfLongestSubstring(s) {
const set = new Set();
let left = 0, maxLen = 0;
for (let right = 0; right < s.length; right++) {
while (set.has(s[right])) {
set.delete(s[left++]);
}
set.add(s[right]);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}- 两数之和
手撕代码:
function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (map.has(complement)) return [map.get(complement), i];
map.set(nums[i], i);
}
}北京某中小厂前端实习面经
4.路由的原理
答:前端路由核心是监听 URL 变化,匹配对应组件渲染,不刷新页面。两种模式:①Hash 模式:URL 带
#,通过hashchange事件监听;②History 模式:用pushState/replaceState修改 URL,popstate事件监听前进/后退。路由库维护一个路由表(path → component 映射),URL 变化时查表渲染对应组件。
7.JS中let和var的区别
答:①作用域:var 函数作用域,let 块级作用域;②变量提升:var 提升并初始化为 undefined,let 提升但不初始化(TDZ);③重复声明:var 可以重复声明,let 不行;④全局声明:var 在全局声明会成为 window 属性,let 不会。
8.组件通信方式
答:React 版:①
props(父→子);②回调函数 props(子→父,通过 props 传函数);③Context(跨层级);④状态管理(Redux/Zustand,全局共享);⑤useRef + forwardRef(父调子方法)。
9.typeof和instanceof的区别
答:
typeof:检测值的类型,返回字符串('string'/'number'/'object'/'function' 等),对 null 返回 'object'(bug),对数组返回 'object'。instanceof:检测对象是否是某构造函数的实例,沿原型链查找,对基本类型无效。
10.浏览器同源策略
答:浏览器安全机制,限制不同源(协议+域名+端口必须完全相同)的网页之间的交互。限制范围:①AJAX 请求跨域被阻止;②DOM 无法跨域访问(如 iframe);③Cookie/localStorage 不能跨域读取。解决方案:CORS(后端设响应头)、代理(Nginx/dev proxy)、JSONP(仅 GET)。
12.虚拟dom介绍一下
答:虚拟 DOM 是用 JS 对象描述真实 DOM 结构的编程概念。React 中 JSX 编译为
createElement()调用,返回 JS 对象(React 元素)。优势:①DOM 操作开销大,JS 对象操作快——通过 diff 算法比较新旧虚拟 DOM 差异,计算出最小更新操作,批量更新真实 DOM;②首次渲染无优势(甚至更慢),优势在更新阶段——只修改变化的节点;③跨平台抽象——同一虚拟 DOM 可以渲染到浏览器(ReactDOM)、原生(React Native)、Canvas 等不同平台。
13.针对项目做过哪些优化
答(结合项目):GenBI 项目:①Zod Schema 校验层(图表崩溃率→0);②usePolling 指数退避(减少 60% 请求);③react-window 虚拟列表(万行数据 <50ms);④路由级代码分割(首屏 JS -40%);⑤骨架屏(消除 CLS);⑥useMemo/useCallback 减少无效重渲染。
14.对项目监控有做过哪些东西
答:可以用 Performance API 监听 FP/FCP/LCP 等核心指标,
window.addEventListener('error')+window.addEventListener('unhandledrejection')捕获 JS 错误和 Promise 异常,上报到监控平台(如 Sentry)。项目中也可以用 Lighthouse CI 在 CI/CD 流程中自动跑性能评分。
16.用vite的时候有没有进行一些优化
答:①代码分割:Rollup 的
manualChunks配置,将大依赖(如 echarts、antd)单独分包,利用浏览器缓存;②gzip/brotli 压缩:vite-plugin-compression;③图片优化:vite-plugin-imagemin;④预构建优化:optimizeDeps.include预构建大依赖;⑤按需引入:配合 antd 的 tree-shaking,只打包使用的组件。
MiniMax 前端面经
这份面经已经自带了答案要点,保留原文。
快手电商前端面经(oc)
- 你项目中使用到了IntersectionObserver能讲一下为什么用这个替代监听滚动吗?
答:
scroll事件在主线程高频触发,需要手动计算元素位置(getBoundingClientRect),容易造成性能问题(触发重排)。IntersectionObserver是浏览器原生 API,异步在合成线程中观察元素是否进入视口,回调在空闲时触发,不阻塞主线程,性能更好。适合懒加载、无限滚动、曝光统计等场景。
- React的useEffect的第二个参数三种情况分别代表什么?
答:①不传(undefined):每次渲染后都执行;②空数组
[]:只在 mount 后执行一次(等价 componentDidMount);③有依赖[a, b]:a 或 b 变化时执行(依赖比较用Object.is)。清理函数在下一次 effect 执行前 + 组件卸载时调用。
- React.Memo和useMemo分别代表什么?
答:React.memo:高阶组件,包裹函数组件,当 props 没变时跳过重渲染(浅比较,可自定义比较函数)。useMemo:Hook,缓存计算结果,依赖不变就返回上次计算值。区别:memo 针对组件级渲染优化,useMemo 针对计算值缓存。还有 useCallback 是缓存函数引用。
美团前端暑期实习ai面面经
- 代理服务器机制、正向与反向代理区别及应用场景
答:正向代理:代理客户端,客户端知道代理存在,服务端不知道真实客户端。场景:翻墙、匿名访问、缓存加速。反向代理:代理服务端,客户端不知道真实服务器。场景:负载均衡、SSL 卸载、缓存静态资源、隐藏服务端。Nginx 就是常用的反向代理。
- 反向代理实现负载均衡的原理与实例
答:反向代理接收客户端请求后,按策略分发到后端多个服务器。策略:①轮询(默认,依次分发);②加权轮询(性能好的服务器分配更多请求);③IP hash(同一客户端 IP 始终访问同一服务器,保持 session);④least_conn(分配给当前连接数最少的服务器)。
- 反向代理处理静态资源缓存的工作方式与优势
答:Nginx 将前端打包后的静态文件(JS/CSS/图片)直接从磁盘返回,不转发给后端应用服务器。配合
expires/Cache-Control设置强缓存、gzip 压缩。优势:减轻后端压力、响应更快、可部署 CDN 加速。
- 实际开发中选择深拷贝的场景
答(结合项目):①Redux/状态管理中需要保证不可变数据——修改状态时深拷贝原对象再修改,确保引用变化触发重渲染;②表单编辑时需要保留原始数据用于"取消"回滚;③AI 返回的 ECharts option 需要做 sanitize 处理,深拷贝后修改避免污染原数据。
- Flex 子元素均匀分布且两端留间距的布局实现
答:
justify-content: space-evenly(均匀分布,包括两端间距相等);或justify-content: space-around(每个元素两侧间距相等,两端间距是中间的一半)+padding补齐。
- Flex 子元素间距相等且两端间距更小的布局调整
答:
justify-content: space-between(两端贴边,中间均匀)配合容器padding实现两端小间距;或用gap属性控制子元素间距 + 容器padding控制两端。
- 多人项目开发避免 CSS 命名冲突的方案
答:①CSS Modules:编译时自动加 hash 后缀,天然隔离(推荐);②BEM 命名规范:
.block__element--modifier结构化命名;③CSS-in-JS(styled-components/emotion):CSS 写在 JS 里,自动生成唯一类名;④Tailwind CSS:原子化 utility class,不写自定义类名;⑤Scoped CSS(Vue<style scoped>):编译时加属性选择器隔离。
- 前端处理 RAG 知识库引用失效与权限不足的交互降级方案
答(结合项目思路):与我项目中处理 AI 不确定性输出类似——核心是分级降级:①引用失效:标记为"来源已失效",提供"重新检索"按钮让用户触发数据重拉;②权限不足:展示友好提示"你没有查看此文档的权限"+ 申请权限入口;③网络异常:展示缓存的历史结果 + "数据可能不是最新"的提示。关键是让用户知道发生了什么、能做什么,而不是白屏。
28届腾讯前端ai面面经
3 在多人项目中,如何保证css的命名规范/正确性,请说出至少三种办法
答:①CSS Modules:编译时自动生成唯一类名,物理隔离;②BEM 命名规范:团队约定
.block__element--modifier格式,配合 stylelint 自动检查;③CSS-in-JS(styled-components):CSS 写在组件内,作用域天然隔离;④Tailwind CSS:原子化类名,不写自定义 CSS;⑤Vue scoped / Angular encapsulation:框架级样式隔离。
4 浏览器的缓存策略,以及如何选择不同的缓存策略
答:策略选择:①不常变的资源(JS/CSS/图片 + contenthash 文件名):强缓存
Cache-Control: max-age=31536000,文件内容变则 hash 变,自动失效;②HTML 入口文件:协商缓存Cache-Control: no-cache(每次请求验证是否过期);③API 接口:一般no-store或短时间缓存;④用户敏感数据:no-store(不缓存)。
5 react合成事件和原生事件的区别
答:合成事件(SyntheticEvent):React 自己实现的跨浏览器事件系统,将事件统一委托到 root 节点(React 17+ 是
#root,之前是document),通过事件冒泡统一处理。区别:①合成事件对象是 React 包装的,用e.nativeEvent获取原生事件;②合成事件通过事件池复用对象(React 17 取消了事件池);③阻止默认行为必须e.preventDefault(),不能return false;④所有事件统一在 root 处理,减少事件监听器数量。
AI coding美团前端开发二面面经
1.React的Fiber架构解决了什么问题?它的工作原理是什么?
答:解决两大瓶颈:①CPU 瓶颈:旧 Stack 架构递归遍历虚拟 DOM 不可中断,长任务阻塞主线程导致卡顿。Fiber 将渲染工作拆为可中断的小单元(每个 Fiber 节点),通过时间切片(5ms 一个切片)分帧执行,保证每帧有时间做渲染和处理用户输入。②I/O 瓶颈:旧架构任务无优先级。Fiber 引入 Scheduler 调度器,高优先级任务(用户输入)可以打断低优先级任务(数据渲染)。工作原理:Fiber 节点通过链表(child/sibling/return)连接,通过
performUnitOfWork深度优先遍历,shouldYield()判断时间切片是否用完,用完则让出主线程,通过 MessageChannel 在下一个宏任务继续。
2.在AI对话界面中,如何实现像ChatGPT那样的Markdown实时流式渲染?
答:①前端通过 SSE(EventSource)或 fetch + ReadableStream 接收流式数据;②用 state 累积接收到的文本片段;③使用 Markdown 渲染库(如 react-markdown)实时渲染当前累积的文本;④需要处理不完整的 Markdown(如未闭合的代码块)——可以在渲染前做简单的补全处理;⑤打字机效果可以用
requestAnimationFrame或setTimeout逐字追加。
3.谈谈你对PromptEngineering的理解,前端如何在代码中优化Prompt的质量?
答(结合项目):Prompt Engineering 是通过精心设计输入来引导 AI 输出期望结果。前端优化方式:①结构化 Prompt:明确角色、任务、格式要求、约束条件;②Few-shot 示例:在 prompt 中加入 1-2 个标准输出示例;③输出格式约束:要求 AI 返回 JSON 并指定 schema,我的项目中 system prompt 明确约束了 ECharts option 格式和分隔符;④前端校验反馈:Schema 校验失败时把错误信息拼到 prompt 让 AI 重新生成;⑤temperature 控制:要求确定性输出时设低值(0.2)。
4.你们项目中是如何处理AI接口的长连接超时和异常重试的?
答(结合项目):GenBI 的异步模式中,通过轮询获取 AI 生成状态。超时处理:①请求层设置 axios timeout;②轮询层有最大重试次数和最大轮询时间限制;③指数退避策略(3s → 4.5s → 6.75s → ... → 30s cap)自然降低请求频率。异常重试:封装通用 retry 逻辑,非幂等请求不重试,网络异常(5xx/timeout)最多重试 3 次,延迟递增。后端异步任务失败会记录
execMessage,前端展示具体失败原因。
5.什么是RAG(检索增强生成)?前端在RAG流程中可以参与哪些工作?
答:RAG = 先检索相关知识,再将知识作为上下文让 LLM 生成回答,解决 LLM 幻觉和知识时效性问题。流程:Query → 向量化 → 相似度检索 → 拼接 Prompt → LLM 生成。前端参与:①知识库管理界面(上传/编辑/删除文档);②检索结果展示(高亮相关片段、引用来源标注);③对话界面(流式渲染、Markdown 展示);④引用失效/权限不足的降级交互;⑤用户反馈(点赞/踩,用于优化检索质量)。
6.如何优化前端长列表的性能?如果列表项是AI生成的动态高度内容呢?
答:固定高度:react-window 的
FixedSizeList,O(1) 定位。动态高度:VariableSizeList,传入itemSize(index)函数,内部维护前缀和数组缓存偏移量,定位 O(log n)。问题是 AI 内容高度在渲染前未知——解决:①预估初始高度;②渲染后用ResizeObserver测量实际高度并更新缓存(resetAfterIndex);③极端场景用react-virtuoso(自动处理动态高度)。
7.谈谈前端安全,特别是AICoding场景下如何防范XSS攻击?
答(结合项目):AI Coding 场景的特殊风险:AI 可能生成含恶意代码的输出。我的项目中处理方式:①sanitize 过滤:在
JSON.stringify的 replacer 中检测含function/=>的字符串值并移除,过滤formatter/rich等 ECharts 危险字段(这些字段支持函数,可能执行任意 JS);②Zod Schema 白名单校验:只允许预定义的图表类型和字段;③CSP 策略:限制内联脚本和 eval 执行;④Error Boundary:即使注入成功也不会影响整个页面。
8.实现一个简单的AI Agent任务编排逻辑,前端如何处理多个Tool Call的串行或并行调用?
答:Agent 可能一次返回多个 tool call。处理策略:①并行:没有依赖关系的 tool call 用
Promise.all并行执行,提升效率;②串行:有依赖关系的(后一个 tool 的输入依赖前一个的输出)用for...of+await串行执行;③混合:用 DAG(有向无环图)描述依赖关系,拓扑排序后按层并行执行。前端实现:维护一个 task queue,每个 tool call 包装为 async 函数,用类似上方 Scheduler 的并发调度器控制并发数。