Adobe 前端面试题全解:从 front-end-interview-handbook 看 JS 算法、UI 组件与系统设计考点
本文基于 front-end-interview-handbook 仓库中的 Adobe 前端面试指南,系统梳理 Adobe 前端面试(FE loop)中反复出现的 JavaScript 编码题、UI 编码题与系统设计题,并结合仓库中配套的 React Hooks 指南、UI 组件题与系统设计 RADIO 框架章节,为每类题目给出可直接演练的代码实现与备考策略。读完后你可以掌握 Adobe 面试的轮次结构、高频考点的完整解法,以及社区内推者(insider)总结的避坑要点。
Adobe 前端面试的整体流程
根据仓库文档中收录的 2025 年真实面经,Adobe 的前端面试循环(FE loop)通常是 5 轮,结构大致为:
| 轮次 | 内容 | 说明 |
|---|---|---|
| Pre-onsite | 快节奏技术问答 + 调试 | 覆盖团队看重的技术栈,现场调试一个应用并讨论性能改进 |
| Architecture round | 架构 + 编码 | 曾出现最后 10 分钟追问递归 API 调用的"陷阱题" |
| Behavioral / tech discussion | 行为面 | 不写代码 |
| Coding + chat | 算法 + 自定义 React hook | 曾出现基于 setInterval 的自定义 hook 题 |
| Manager chat | 经理面 | — |
更早期(2023 年 10 月)的面经则描述为:先完成一道 HackerRank 题目并快速 Q&A,随后进入 5 轮循环,每轮约 30 分钟技术 + 10 分钟行为面 + 5 分钟 Q&A,技术题包括 LC medium 的"数岛屿"(nested 0/1 数组中的岛屿总数)、LC easy 的日期格式转换(Oct 1 23 转 10-01-23)、用 JS 或 React 构建 Accordion,以及手写 reduce polyfill。
从面经看,Adobe 的整体难度被评价为"比沉迷 LeetCode 的公司更简单一些,但 React 细节和 REST API 是明确的扣分点"。因此备考重点应放在:React 机制(hooks 生命周期与陷阱)、异步代码的边界情况、以及 UI 组件的可访问性。
JavaScript 编码题
1. 递归抓取所有子孙链接(getAllLinks 异步 BFS)
这是 Adobe"语言与 Web 基础"轮的高频题:给定一个 API getAllLinks(url),它返回某页面上所有链接;要求写一个函数返回 url 的所有子孙链接(子 URL 的子 URL,以此类推)。在此基础上面试官会追问以下边界情况:
- 页面之间存在循环依赖(A 链接到 B,B 又链接回 A)时怎么办;
getAllLinks返回的是 promise 时如何处理;- 异步代码如何写得更高效(并行 vs 串行);
- 时间复杂度是多少。
一个能覆盖上述追问的标准解法是异步 BFS + visited 集合去重:
// getAllLinks(url): Promise<string[]> —— 返回该页面上的一层直接链接
async function getAllDescendantLinks(startUrl) {
const visited = new Set([startUrl]); // 去重:既防止无限递归,也避免重复请求
const descendants = [];
const queue = [startUrl];
while (queue.length > 0) {
const batch = queue.splice(0, queue.length);
// 同一层级的请求并行发出,而不是逐个 await,这是"异步效率"追问的得分点
const results = await Promise.all(
batch.map((url) => getAllLinks(url))
);
for (const links of results) {
for (const link of links) {
if (!visited.has(link)) {
visited.add(link); // 标记已访问,天然解决循环依赖
descendants.push(link);
queue.push(link); // 加入下一层继续遍历
}
}
}
}
return descendants;
}
讲解要点(面试中应主动覆盖):
- 循环依赖:
visited集合在入队时就标记,保证同一 URL 只会被请求一次,图上有环也不会死循环; - 异步效率:同一层的请求用
Promise.all并行发起,把总耗时从 O(层数 × 单次请求) 的串行等待压缩到接近 O(层数 × 单次请求) 的并行等待; - 复杂度:时间 O(V + E),V 为去重后的 URL 数、E 为链接总数;空间 O(V)。
仓库中一位 2025 年 6 月的面经特别提醒了一个实操细节:该轮测试环境未预置测试用例,是面试官现场输入测试用例,你需要手工跑(handrun)代码来验证正确性——所以本地推演能力比依赖测试框架更重要。
2. 手写 Array.prototype.reduce
reduce polyfill 是前端面试的常青树题,Adobe 曾在 UI/算法轮直接考到。仓库的 JavaScript 章节 也将其列为第一周基础周的核心练习(与 map、filter polyfill 并列)。
if (!Array.prototype.reduce) {
Array.prototype.reduce = function (callback, initialValue) {
const arr = Object(this); // 支持类数组
const len = arr.length;
let i = 0;
let accumulator;
if (arguments.length > 1) {
// 显式提供了初始值
accumulator = initialValue;
} else {
// 未提供初始值:用第一个元素作为初始累加器
while (i < len && !(i in arr)) i++;
if (i >= len) throw new TypeError('Reduce of empty array with no initial value');
accumulator = arr[i++];
}
for (; i < len; i++) {
if (i in arr) {
accumulator = callback(accumulator, arr[i], i, arr);
}
}
return accumulator;
};
}
考察点与 算法章节中列出的预期一致:reduce 遍历是 O(n)。面试时应主动提到三个规范细节:callback(accumulator, currentItem, index, array) 的四参数签名、无初始值时空数组要抛 TypeError、以及稀疏数组(in 判断)的处理。
3. 找出数组中的"领导者"(Leaders in an array)
题目:找出数组中所有严格大于其右侧所有元素的元素。例如 [9, 32, 8, 7, 1, 12] 的领导者是 [32, 12, 9](或按出现顺序 [9, 32, 12],以题目要求为准)。
标准做法是从右向左一次遍历维护"当前最大值":
function findLeaders(arr) {
const leaders = [];
let maxFromRight = -Infinity;
for (let i = arr.length - 1; i >= 0; i--) {
if (arr[i] > maxFromRight) {
leaders.unshift(arr[i]); // 或 push 后再 reverse
maxFromRight = arr[i];
}
}
return leaders;
}
- 时间 O(n)、空间 O(n)(存结果),只需一次遍历——面试官常追问"能否 O(1) 额外空间",答案是倒序输出时即可;
- 这是典型的"贪心 + 一次扫描"题,与仓库算法章节推荐的备考方向(树、递归、BFS/DFS 优先于困难 DP)一致:前端面试官很少出 hard 级动态规划,这类 medium 级数组题是主流。
4. 基于 setInterval 的自定义 React Hook(useRef vs useState 陷阱)
2025 年 9 月的面经记录了 Adobe 第 4 轮的真实场景:面试官要求实现一个"使用 setInterval 的自定义 hook"。候选人代码"功能上能用",但定时器无法正确清除,且在编辑器中随机触发了两次——根因是把回调存进了 useState,而正确做法是用 useRef。
仓库的 React Interview Playbook hooks 章节 中恰好列出了对应的两条规则:当某个值不影响渲染时用 useRef 而不是 useState,以及 setInterval 必须返回清理函数,否则内存泄漏。下面把这个知识点落成可作答的代码:
import { useEffect, useRef } from 'react';
// 正确版本:回调存 useRef,避免每次渲染生成新函数触发 effect 重建定时器
function useInterval(callback, delay) {
const savedCallback = useRef(callback);
// 每次渲染都同步最新回调,但同步本身不会触发 effect 重跑
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay === null) return; // delay 为 null 时暂停
const id = setInterval(() => {
savedCallback.current(); // 始终调用最新闭包
}, delay);
return () => clearInterval(id); // 关键:清理,防止定时器残留/重复触发
}, [delay]);
}
面试讲解的三个得分点:
- 为什么
useState会出 bug:把回调存入useState后,任何setState都可能引发重渲染,导致 effect 依赖变化、定时器被重建,出现"随机跑两次"的现象;useRef.current是可变引用,赋值不触发渲染,定时器生命周期稳定; - cleanup 缺失的后果:仓库 hooks 章节给出的反例正是
setInterval无 cleanup 的内存泄漏场景,Adobe 面试官实际就是在这里给了"interval wouldn't clear properly"的反馈; - React 知识缺口提示:面经总结指出 Adobe 会基于这类问题判定候选人"React 上有 gaps",备考时建议系统过一遍 hooks 的生命周期(mount/update/unmount 时 effect 与 cleanup 的时机)。
UI 编码题
1. 用 Vanilla JS 或 React 实现 Accordion
Accordion 在 Adobe 的 UI 轮中出现过(2023 年 10 月面经原话:"build an accordion using JS or react")。仓库的 User Interface 章节 将 Accordion 列为 UI 编码题第一梯队,并给出进阶路线:先做基础渲染与展开/收起,再补 ARIA 角色/状态,最后实现符合 ARIA 规范的键盘支持(Home/End/Arrow Up/Down 在 header 间移动焦点)。"基础版可能足以通过面试,但把可访问性做对会拿到额外加分,展示资深水平。"
最小可运行版本(React):
import { useState } from 'react';
function Accordion({ items }) {
const [openIndex, setOpenIndex] = useState(null);
return (
<div>
{items.map((item, i) => (
<div key={item.title}>
<button
aria-expanded={openIndex === i}
aria-controls={`panel-${i}`}
onClick={() => setOpenIndex(openIndex === i ? null : i)}
>
{item.title}
</button>
{/* 用 hidden 而非 display:none 的样式 hack,配合 aria-controls 建立关联 */}
<div id={`panel-${i}`} hidden={openIndex !== i}>
{item.content}
</div>
</div>
))}
</div>
);
}
vanilla JS 版本的核心是把状态从 useState 换成 classList.toggle('open') + CSS 过渡,逻辑同构。答完后主动追问自己:"如果要支持多项同时展开 / 键盘导航 / 屏幕阅读器,改哪里?"——这正是仓库文档指出的加分项。
2. 把顶部 Header 设计稿实现为 React 组件
面经中的原题:"Given a top-header design of a document, implement it as a React component"。这是一道设计稿还原 + 组件化题,考察点集中在:
- 布局:flex 对齐(logo 左、导航中、操作区右),
gap与flex: 1的使用; - 语义化与可访问性:
<header>、<nav>、aria-label; - 组件拆分:
Header、Nav、NavList的职责边界,props 设计(links: {label, href}[]); - 响应式:窄屏下导航收起为 menu button(可顺手引出 Accordion 思路,与上一题形成呼应)。
仓库 UI 章节把这类"从设计稿到组件"的任务归入 Components 分类(与 Accordion、Tabs、Star Rating、Image Carousel 同类),建议在面试中边写边说布局决策,展示 tradeoff 意识。
3. Flood Fill:带形状轮廓的 m × n 网格填充
这是 2025 年 6 月面经中 hiring manager 轮的原题(当时只要求伪代码,但值得准备完整实现):
有一个 m × n 网格,其中包含若干形状的轮廓线。若点击某个形状内部,只 flood fill 该形状;若点击形状外部,则填充整个网格中除形状内部以外的区域。
思路是把网格看成图,对点击点做 BFS/DFS,只允许在同值(未填充)格子之间扩散,轮廓线(例如标记为 1 的边框)是天然屏障:
// grid: m x n 二维数组,0 = 空白可填充,1 = 轮廓线/障碍
function floodFill(grid, startX, startY) {
const m = grid.length, n = grid[0].length;
const target = grid[startY][startX];
if (target === 1) return; // 点在线条上:不做处理(边界条件)
const queue = [[startX, startY]];
grid[startY][startX] = 2; // 2 = 已填充;先标记再入队,避免重复入队
while (queue.length > 0) {
const [x, y] = queue.shift();
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
const nx = x + dx, ny = y + dy;
// 越界、轮廓线、已填充,都不可扩散——形状边界由此被"挡住"
if (nx < 0 || nx >= n || ny < 0 || ny >= m || grid[ny][nx] !== target) continue;
grid[ny][nx] = 2;
queue.push([nx, ny]);
}
}
}
"点形状内只填该形状、点形状外填整块背景"这一行为不需要任何特殊判断:BFS 遇到轮廓线自然停止,连通域被轮廓分割成不同区域,所以点击位置决定了填充哪个连通域。讲解时说明复杂度 O(mn) 时间、O(mn) 空间(最坏整网格入队),并与 Adobe 2023 年面过的"数岛屿"题(同样是网格 BFS/DFS)关联起来——两道题本质都是网格图上的连通域遍历,一套模板通吃。
系统设计题:非前端方向的 LRU 与 OOD
2025 年 6 月的面经明确指出 Adobe 的系统设计轮是 non-frontend system design,被考了两道:设计 LRU cache,以及为国际象棋(chess)游戏设计类与接口(OOD)。仓库的 系统设计框架章节 也提示 RADIO 框架"不仅适用于前端系统设计,后端系统设计同样可用",正好覆盖这一场景。
1. LRU Cache(JS 版参考实现)
LRU 的规范行为:get/put 均须 O(1),容量满时淘汰最久未使用的键。JS 中最简洁的实现利用 Map 保持插入序的语义:
class LRUCache {
constructor(capacity) {
this.cap = capacity;
this.map = new Map(); // key -> value;迭代序即"从旧到新"的访问序
}
get(key) {
if (!this.map.has(key)) return -1;
const val = this.map.get(key);
// 删除后重新 set,把该键移到"最新"位置
this.map.delete(key);
this.map.set(key, val);
return val;
}
put(key, value) {
if (this.map.has(key)) this.map.delete(key);
else if (this.map.size >= this.cap) {
const oldest = this.map.keys().next().value; // 队首即最久未使用
this.map.delete(oldest);
}
this.map.set(key, value);
}
}
面试中可补充两点深度:(1) 严格语言中 Map 队头删除仍算 O(1),但底层通常是"哈希表 + 双向链表",主动画出链表结构说明 get 时"摘到尾部"的操作能展示数据结构功底;(2) 追问真实场景:HTTP 响应缓存、React 的 memo 与 LRU 思想的关系(仓库 系统设计 cheatsheet 中的评估维度——架构拆解、技术深度、tradeoff 阐述——对这类题同样适用)。
2. 为 Chess 设计类与接口(OOD)
标准拆解思路(面试中边说边画 UML):
- 实体:
Chessboard(8×8 状态)、Piece(基类)及其子类King/Queen/Rook/Bishop/Knight/Pawn、Player(白/黑)、Move(起点、终点、是否吃子)、Game(回合状态、历史记录、胜负判定); - 接口:
Piece.canMove(from, to, board)与Piece.generateMoves(board)多态化——每种棋子的合法走法封装在自己身上,Game.makeMove(move)统一校验 + 更新棋盘 + 切换回合; - 关键 tradeoff:走法校验放
Piece还是Game?前者符合开闭原则(新增棋子类型不改Game),后者便于统一处理将军/应将等跨棋子规则——能主动说出这个权衡正是评估轴里"Exploration and tradeoffs"想看到的。
来自 GreatFrontEnd 社区的原始面经
以下四段为仓库文档收录的社区内推者原文经验(已按仓库记录的时间倒序整理),是本文所有考点判断的一手依据:
2025 年 9 月 25 日(Firefly 团队 FE loop,5 轮):
5 轮总计:1. Pre-onsite——快节奏问答覆盖他们看重的技术栈,外加调试一个应用并讨论性能改进。2. Architecture 轮。最后 10 分钟面试官问了一个"陷阱题":编码一个递归 API 调用。我过去 3 年一直在用 SSR GraphQL,卡在
setTimeout中如何处理返回值(不能用副作用变量),没写完(他说我完成了 95%)。3. 行为/技术讨论,不写代码。4. 聊天 + 一个需要setInterval的自定义 React hook。面试官认为代码功能上能用,但 interval 无法正确清除、在编辑器里随机跑了两遍——React 的修复办法正是对 setInterval 用useRef而不是useState。5. 经理聊。反馈:所有人都喜欢我,但基于上述两个问题,他们说我在 React 和 REST API 上有缺口。总体比沉迷 LeetCode 的公司简单——把 React 的怪癖准备到位应该就没问题。
2025 年 6 月 19 日:
参加了 Adobe 系统设计,是非前端系统设计。被要求设计 LRU cache、为国际象棋游戏设计类和接口。还有 hiring manager 轮,被要求实现 flood fill 算法:一个 m*n 网格上有若干形状的轮廓,点在形状内部则填充该形状,点在形状外部则填充整个网格(形状除外)。不过只要求写伪代码。
2025 年 6 月 16 日:
参加了 Adobe 面试。在语言与 Web 基础轮,被问到"数组中的领导者"这道题。另一个问题就是
getAllLinks(url)的子孙链接题,边界情况层层加码:页面间循环依赖怎么办?getAllLinks返回 promise 怎么办?异步代码怎么更高效?时间复杂度?要注意的一点是,我不得不手工跑代码,因为测试用例没有预置,测试用例是面试官现场敲出来的而不是粘贴的。
2023 年 10 月 26 日(FE mid-level):
上周刚面完 Adobe FE mid-level。流程是 HackerRank + 与 HM 快速 Q&A,之后 5 轮,每轮 30 分钟技术 + 10 分钟行为 + 5 分钟 Q&A。技术题包括:LC medium——在 0 和 1 的嵌套数组中数岛屿总数;LC easy——把
Oct 1 23转成10-01-23;UI——用 JS 或 React 做 Accordion;手写.reducepolyfill。难度偏简单,但当时只有一个 headcount,所以没拿到 offer。
备考策略小结
- 题型定位:Adobe 偏"solid fundamentals"而非 LeetCode hard——数组/网格一次遍历题(leaders、岛屿、flood fill)、polyfill(
reduce)、设计稿还原(Accordion、Header)构成主体; - React 机制是明确的评分项:自定义 hook、
setInterval清理、useRefvsuseState的取舍(参见 React hooks 章节),面经两次把失败原因直接归到 React 细节上; - 系统设计按通用(非前端)准备:LRU、OOD 这类经典题,可借用仓库 RADIO 框架(framework 章节)结构化作答;
- 练习方式适配 Adobe 风格:至少一次"无测试用例、纯手工推演"的模拟——在纸上/白板上按面试官口述的输入逐步推演输出,这是文档面经中被单独强调的实战细节;
- 异步代码要习惯追问链:以
getAllLinks为例,"循环 → promise → 并行化 → 复杂度"的追问顺序就是 Adobe 的出题习惯,回答时应逐层主动覆盖,而不是等面试官追问。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00