JavaScript 面试题

共 407 道 JavaScript 面试题。答案默认折叠,便于先自行作答。 1. requestAnimationFrame 与 requestIdleCallback 在渲染优化中的执行时机差异?谁优先触发? 难度:3 · 类型:QA 题目要点 requestAnimationFrame 在浏览器即将进行下一帧渲染之前执行,用于动画与视觉更新,优先级高于渲染阶段后的任务。requestIdleCallback 只会在当前帧所有渲染任务完成且仍有剩余时间时触发,属于低优先级空闲调度机制。因此在同一帧中,rAF 必然先于 rIC 执行。rAF 用于保证帧同步,rIC 用于利用空闲时间执行非关键任务。 参考答案 这个问题本质是理解 浏览器一帧的调度模型。 不搞清楚一帧里发生了什么,很容易回答成“谁快谁慢”这种表层结论。 核心结论先给出: 在有下一帧渲染需求的情况下,requestAnimationFrame 一定优先于 requestIdleCallback 执行。 requestIdleCallback 只会在当前帧还有“空闲时间”时才会触发。 下面从浏览器帧模型拆解。 一、浏览器一帧的基本流程 以 60fps 为例,一帧约 16.6ms。 一次完整渲染循环大致是: 执行宏任务(例如 setTimeout) 清空微任务队列 触发 requestAnimationFrame 回调 进行样式计算(Style) 布局计算(Layout) 绘制(Paint) 合成(Composite) 如果还有时间 → 执行 requestIdleCallback 需要明确一点: rAF 是“下一帧渲染前”的回调 rIC 是“当前帧剩余时间”的回调 二、requestAnimationFrame 的执行时机 requestAnimationFrame 的设计目标是: 在浏览器即将进行下一次重绘之前执行回调。 特点: 每帧最多执行一次 与刷新率同步 在样式计算之前触发 如果标签页不可见,会暂停 执行时机可以理解为: ...

leetcode 面试题

共 38 道 leetcode 面试题。答案默认折叠,便于先自行作答。 1. 最大公共前缀 难度:2 · 类型:QA 编写一个函数,接收一个字符串数组作为输入,找出这些字符串的最大公共前缀。如果没有公共前缀,则返回空字符串。 输入输出要求: 输入:一个字符串数组。 输出:一个字符串,表示最大公共前缀。 示例: const input = ["flower", "flow", "flight"]; const result = longestCommonPrefix(input); console.log(result); // 输出:'fl' 题目要点 最大公共前缀问题的核心是逐步缩小候选前缀;横向扫描通过不断用后续字符串修剪前缀,实现简单且高效;在最坏情况下时间复杂度为 O(n·m),空间复杂度为 O(1),非常适合在实际工程和面试中使用。 参考答案 这个问题本质是在多个字符串之间求一个公共的、连续的前缀子串,并且要求尽可能长。关键不在于字符串操作技巧,而在于如何逐步收敛搜索空间。 一、整体思路说明(横向扫描) 横向扫描的核心思想是: 先假设第一个字符串是公共前缀,然后不断用后续字符串去“修剪”它。 执行过程可以概括为: 取第一个字符串作为初始前缀 从第二个字符串开始,逐个比较 如果当前字符串不以该前缀开头,就不断缩短前缀 一旦前缀缩短为空,说明不存在公共前缀,可以提前结束 这个过程的好处是逻辑直观,且在工程中可读性和可维护性都很好。 二、示例代码实现(横向扫描) function longestCommonPrefix(strs) { if (!strs || strs.length === 0) return ""; let prefix = strs[0]; for (let i = 1; i < strs.length; i++) { while (!strs[i].startsWith(prefix)) { prefix = prefix.slice(0, -1); if (prefix === "") return ""; } } return prefix; } 示例验证 const input = ["flower", "flow", "flight"]; console.log(longestCommonPrefix(input)); // "fl" 三、时间与空间复杂度分析 时间复杂度: 最坏情况下为 O(n * m) ...

Node.js 面试题

共 40 道 Node.js 面试题。答案默认折叠,便于先自行作答。 1. NestJs、Nust.js、Next.js 这几个框架有什么区别 难度:2 · 类型:QA 题目要点 NestJS 是 Node.js 的企业级服务端框架,强调模块化、依赖注入和 TypeScript,适合构建 API 和微服务; Nuxt.js 是 Vue 生态的全栈框架,在 Vue 基础上提供 SSR、SSG、文件路由等能力; Next.js 是 React 生态的全栈框架,除了 SSR、SSG 外,还支持 React Server Components、Server Actions 等现代特性。 三者最大的区别在于定位不同:NestJS 负责后端服务,Nuxt.js 和 Next.js 负责全栈 Web 应用开发,分别服务于 Vue 和 React 技术栈。 参考答案 这几个框架名字非常相似,但定位完全不同。简单来说: NestJS:Node.js 服务端开发框架。 Nuxt.js:Vue 生态的全栈应用框架。 Next.js:React 生态的全栈应用框架。 三者分别解决的是不同层面的问题,并不存在直接竞争关系。 先来看 NestJS。 NestJS 是基于 Node.js 构建的服务端框架,底层默认使用 Express,也可以切换到 Fastify。它借鉴了很多 Java Spring 的设计思想,例如依赖注入(DI)、模块化、装饰器以及 IoC 容器,因此对于大型团队开发来说,代码组织会更加规范。 ...

React.js 面试题

共 148 道 React.js 面试题。答案默认折叠,便于先自行作答。 1. 说说 Hooks 的依赖数组原理 难度:3 · 类型:QA 题目要点 Hooks 的依赖数组本质是一次浅层引用比较机制。React 在 commit 阶段通过 Object.is 逐项比较新旧依赖数组,决定是否重新执行 effect,并在变化时先执行 cleanup。依赖数组不会监听变量变化,也不负责触发 render,只用于声明副作用与状态之间的关系。依赖缺失会导致闭包捕获旧值,依赖不稳定会导致 effect 频繁执行,因此依赖必须完整且保持引用稳定。 参考答案 这个问题的核心,不是“依赖数组怎么写”,而是:React 是如何判断副作用是否需要重新执行的。 依赖数组的存在,本质是为了让 React 在两次 render 之间进行一次“浅层比较(shallow compare)”,从而决定是否跳过本次 effect 的执行。 一、从 useEffect 的执行时机说起 useEffect(() => { // effect }, [a, b]); React 在每次 render 结束后,会把 effect 收集到 Fiber 节点的 effect 链表中。 当进入 commit 阶段时,React 会: 取出当前 Fiber 上一次保存的依赖数组 与本次 render 生成的新依赖数组进行比较 决定是否标记该 effect 需要执行 如果依赖变化,则: ...

Typescript 面试题

共 61 道 Typescript 面试题。答案默认折叠,便于先自行作答。 1. 如何管理和优化 tsconfig? 难度:3 · 类型:QA 题目要点 tsconfig 应被视为工程规范而非临时配置;通过分层与继承降低维护成本;类型严格性需要渐进式收紧而非一次性激进开启;合理控制编译范围以优化性能;在复杂工程中利用 tsconfig 约束依赖边界,使类型系统服务于长期可维护性。 参考答案 tsconfig 的管理与优化,本质上是在类型安全、开发体验与工程效率之间做长期平衡。它并不是一次性配置文件,而是随着项目规模、团队协作方式和构建体系不断演进的工程资产。 首先需要明确 tsconfig 在工程中的角色定位。它既是 TypeScript 编译器的输入约束,也是 IDE 类型分析与提示能力的基础配置。如果把 tsconfig 仅当作“让项目能跑起来的必要文件”,往往会在项目中后期暴露出类型失控、编译缓慢或不同环境行为不一致的问题。因此,治理 tsconfig 的第一步,是将其视为工程规范的一部分,而不是单纯的工具配置。 在实际管理中,通常会通过“分层”的方式来降低复杂度。将通用且稳定的配置抽离为基础 tsconfig,由不同运行环境或子项目通过 extends 继承,再在局部覆盖差异化选项。这种方式可以避免在多个 tsconfig 中重复维护同一组 compilerOptions,也能清晰表达哪些约束是全局共识,哪些是特定场景的取舍。例如,类型严格性、模块解析策略往往属于全局约束,而是否生成声明文件、是否开启 sourceMap 则更偏向构建阶段需求。 在优化层面,类型严格性的管理尤为关键。一次性开启所有严格选项,在历史项目中通常不可行,反而会导致大量噪音,削弱团队对类型系统的信任度。更可持续的做法,是以 strict 为目标方向,但通过逐步引入单项严格规则,让类型质量随时间提升,而不是通过一次激进配置制造阻力。tsconfig 在这里承担的是“收紧边界”的角色,而不是制造阻断。 编译性能同样是 tsconfig 优化中不可忽视的一环。随着项目体量增大,合理控制 include、exclude 范围,避免将无关文件纳入类型分析,是最直接也最有效的手段。同时,明确区分“类型检查”与“代码转译”的职责,可以避免在开发阶段承担不必要的编译成本。在一些工程中,会通过不同 tsconfig 分别服务于 IDE 类型检查与构建流程,从而兼顾体验与效率。 在 Monorepo 或组件库场景下,tsconfig 还承担着依赖边界约束的职责。通过 project references 明确包之间的依赖关系,不仅可以加速增量编译,也能在类型层面阻止不合法的跨包引用。这类配置一旦稳定下来,往往比 lint 规则更可靠,因为它直接作用于编译阶段。 长期来看,tsconfig 的优化不是“调参数”,而是通过持续审视哪些约束是必要的、哪些已经不再适用,让类型系统始终贴合真实的工程状态。当 tsconfig 能够稳定表达团队的工程共识时,它的价值才真正体现出来。 2. Boolean 和 boolean 有什么区别? 难度:3 · 类型:QA ...

Vue.js 面试题

共 123 道 Vue.js 面试题。答案默认折叠,便于先自行作答。 1. 为何setup()中直接解构props会丢失响应性? 难度:2 · 类型:QA 题目要点 setup 中直接解构 props 会丢失响应性,是因为解构会把属性值拷贝为普通变量,打断了与 props Proxy 的引用关系,导致 Vue 无法进行依赖收集。正确做法是直接使用 props.xxx,或通过 toRefs / toRef 将属性转换为保持响应性的 ref。 参考答案 在 Vue 3 中,setup() 里直接解构 props 会丢失响应性,根本原因在于:响应式是依赖引用关系实现的,而解构会打断这种引用关系。 下面从原理、示例和正确做法三个层面说明。 一、根本原因:解构会“拷贝值”,不再是响应式引用 在 setup(props) 中: props 本身是一个 浅只读的响应式对象(Proxy) Vue 的响应式系统是 基于 getter / setter(Proxy 拦截)+ 依赖收集 实现的 解构会把属性的当前值“取出来”,赋值给一个普通变量 一旦解构: const { title } = props; 此时: title 是一个 普通变量 它不再通过 props.title 访问 Vue 无法拦截 title 的读取 依赖收集链路断裂 → 响应性丢失 二、对比示例:为什么一个会更新,一个不会 ❌ 错误示例(丢失响应性) export default { props: { title: String }, setup(props) { const { title } = props; return { title }; } }; 父组件更新 title 时: ...

前端安全 面试题

共 35 道 前端安全 面试题。答案默认折叠,便于先自行作答。 1. 如果发现JWT Token泄露,如何快速响应? 难度:3 · 类型:QA 题目要点 定位:确认泄露的是单个用户 Token 还是全局密钥。 拉黑:立即将泄露的 Token jti 加入 Redis 禁用名单。 阻断:如果是密钥泄露,立即更新环境变量中的 JWT_SECRET。 通知:提醒受影响用户,并建议其修改密码(以防攻击者通过其他渠道获取了账号控制权)。 审计:检查该 Token 泄露期间产生的异常操作日志,进行回滚或补偿。 参考答案 由于 JWT 是无状态的(服务器默认不存储 Token,无法像 Session 那样轻易在服务端作废),快速响应的核心思路在于阻断该 Token 的有效性或强制变更鉴权环境。 以下是针对不同场景的快速响应方案: 1. 服务端拦截:引入“黑名单”机制(最快响应) 这是最直接的补救措施。虽然 JWT 旨在去中心化校验,但在紧急情况下,必须引入中心化检查。 操作:将泄露的 Token 唯一标识(通常是 jti 载荷)或整个 Token 字符串存入 Redis 等极速缓存中,并设置过期时间(等于该 Token 的剩余有效期)。 逻辑更新:修改后端的鉴权中间件,在校验 JWT 签名通过后,额外多一步检查:“该 Token 是否在 Redis 黑名单中?”。如果在,则拒绝请求。 优点:秒级生效,精准拦截。 2. 变更签名密钥(最彻底但代价大) 如果怀疑是大规模泄露,或者加密私钥(Secret Key)本身已暴露: 操作:立即在服务端更换用于签署 JWT 的 Secret Key。 后果:由于密钥变了,之前所有基于旧密钥生成的 Token(包括合法用户的)都会瞬间失效。 适用场景:密钥泄露或遭受系统性攻击。虽然这会导致全服用户强制下线(需重新登录),但在极端安全风险下是必要的“熔断”。 3. 强制用户重新登录:变更 User Secret 如果你的 JWT 签名逻辑中引入了用户特有的变量(例如:HMAC256(payload, base_secret + user_password_hash)): ...

场景题 面试题

共 84 道 场景题 面试题。答案默认折叠,便于先自行作答。 1. 现在大模型已经能高效生成界面代码,有人认为前端会被AI颠覆,也有人认为这只是工具革新。你更倾向哪种观点?你认为前端未来3-5年真正的机会点或转型方向是什么? 难度:3 · 类型:QA 题目要点 更倾向于“工具革新”而非“彻底颠覆”;大模型替代的是低层实现工作,而前端正在向工程化、系统设计以及 AI 应用方向演进,未来机会集中在高阶工程能力与“前端 + AI”结合的领域。 参考答案 更倾向于“工具革新”的判断,但需要补充一个前提:被替代的不是前端岗位,而是低抽象层的前端工作。大模型把“写代码”这一步极大压缩,但没有消解前端在系统中的职责。 从本质上看,前端的价值不在于把设计稿转成代码,而在于如何在浏览器这个受限运行时中构建复杂系统并保证体验、性能与稳定性。大模型擅长生成局部正确的代码,但在跨模块一致性、状态复杂性、长期可维护性等方面仍然依赖工程设计能力。 一、为什么更接近“工具革新” 大模型改变的是生产方式,而不是问题本身: 代码生成 → 自动化:组件、页面、样板代码的产出成本趋近于零 问题复杂度 → 上移:从“怎么写”转为“怎么设计、怎么约束、怎么演进” 人机协作 → 常态化:开发流程变成“设计 + 约束 + 校验 + 修正” 这和当年从 jQuery 到框架、再到工程化工具的演进类似,本质是抽象层不断上移。 二、未来 3–5 年的核心变化 可以用一句话概括:前端从“实现层”走向“系统层 + AI 协同层” 1. 前端工程能力会被放大,而不是削弱 大模型生成的代码,如果没有约束,很容易出现: 状态混乱(状态源不清晰) 性能问题(重复渲染、无效请求) 架构失控(组件边界混乱) 因此需要更强的工程能力去“约束 AI”: 设计组件规范、状态模型、数据流 统一代码风格与约束(lint / schema / contract) 构建可持续演进的架构 本质上:人负责“系统正确性”,AI 负责“实现效率” 2. “前端 + AI”的应用能力成为新分水岭 未来不是“用 AI 写代码”,而是“用前端做 AI 产品”: ...

小程序 面试题

共 15 道 小程序 面试题。答案默认折叠,便于先自行作答。 1. 小程序可以做哪些性能优化? 难度:2 · 类型:QA 题目要点 小程序性能优化围绕逻辑层与视图层分离架构展开,重点是减少 setData 频率与数据体积,控制列表节点数量,优化首包体积与启动链路,并通过分包加载、WXS 与懒加载等手段降低渲染压力。核心原则是减少跨线程通信与避免无意义重渲染,从架构层面解决性能瓶颈。 参考答案 小程序的性能优化,不能只从“前端渲染”角度看。它的运行模型和 Web 不同,存在 逻辑层(JSCore)与视图层(WebView)分离 的架构特征,核心瓶颈往往出在: 跨线程通信成本 setData 传输体积 渲染节点数量 包体与启动链路 优化必须围绕这些机制展开。 一、理解运行架构是前提 以 微信小程序 为例: 逻辑层:JS 执行环境 视图层:WebView 渲染 两层通过 JSON 序列化通信 每一次 setData: 数据序列化 线程间传输 视图层 diff 真实节点更新 所以小程序性能优化的第一原则是: 减少跨线程通信的数据量与次数。 二、控制 setData 的粒度与频率 1. 避免大对象全量更新 错误方式: this.setData({ form: newFormObject }) 如果 form 很大,每次都会整体传输。 正确方式: this.setData({ "form.username": value }) 使用路径更新,最小化数据传输。 2. 合并多次 setData 多次调用: this.setData({ a: 1 }) this.setData({ b: 2 }) 会触发多次通信。 ...

工具 面试题

共 40 道 工具 面试题。答案默认折叠,便于先自行作答。 1. 你了解 Git 的 Worktree 吗? 难度:3 · 类型:QA 题目要点 Git Worktree 允许多个工作目录共享同一个仓库对象库,从而同时检出多个分支,避免频繁 checkout 带来的上下文切换问题。其核心依赖 Git 的对象数据库与工作区分离设计,相比 clone 具有更低的磁盘与网络成本,适用于并行开发、hotfix、版本对比和 CI 场景,本质是同一仓库的多工作视图机制。 参考答案 Git Worktree 是 Git 提供的一种 在同一个仓库中同时检出多个工作目录(working directory) 的机制,本质目的是解决“一个仓库只能对应一个当前分支”的限制。 在传统 Git 使用方式下,一个仓库目录只能 checkout 一个分支。如果正在开发 feature 分支,此时需要紧急修复线上 bug,就必须经历: stash 当前修改 切换分支 修复问题 再切回原分支恢复现场 这个过程不仅低效,而且容易产生冲突或遗漏。Worktree 的出现,就是为了避免这种上下文切换成本。 一、Worktree 的核心原理 Git 仓库实际上由两部分组成: .git:对象数据库(commit、tree、blob、refs) working directory:当前检出的文件 Worktree 允许 多个 working directory 共享同一个 .git 对象库。 也就是说: commit history 只有一份 objects 只有一份 但可以有多个独立的代码目录,每个目录对应不同分支 Git 内部会在 .git/worktrees/ 下维护额外工作区的元信息,每个 worktree 都拥有自己的 HEAD、index 和 checkout 状态。 ...