前端安全 面试题

共 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 状态。 ...

工程化 面试题

共 87 道 工程化 面试题。答案默认折叠,便于先自行作答。 1. webpack 中的 Loader ,链式调用顺序会影响编译结果吗? 难度:2 · 类型:QA 题目要点 Webpack Loader 是一个按流水线执行的源码转换系统,Loader 的链式顺序直接决定源码被处理的阶段;配置顺序为从左到右,但执行顺序为从右到左,每个 Loader 都处理前一个 Loader 的输出,因此顺序错误会导致语义阶段错乱甚至编译失败;通常应遵循“靠近源码的 Loader 放右侧,靠近运行时的 Loader 放左侧”的原则,同时 Loader 还存在 pitch 与 normal 两阶段执行机制,使顺序对最终编译结果具有决定性影响。 参考答案 会,而且这是 Webpack Loader 机制中非常关键且经常被误解的一点: Loader 的链式调用顺序不仅会影响编译结果,而且很多语法是否能正确工作,完全取决于 Loader 的执行顺序。 理解这个问题的关键在于:Loader 本质是一个“源码转换流水线(transform pipeline)”。 一、Loader 本质:源码的逐步转换 在 Webpack 中 hookup 的 Loader,并不是同时运行的,它们会对模块源码进行一层一层的转换: 原始源码 → loaderA → loaderB → loaderC → 最终 JS 模块 每个 Loader: 接收上一个 Loader 的输出 返回新的源码字符串(或 AST 转换结果) 交给下一个 Loader 因此: ...

性能优化 面试题

共 52 道 性能优化 面试题。答案默认折叠,便于先自行作答。 1. 响应式开发中,如何避免窗口大小监听导致的重排抖动? 难度:2 · 类型:QA 题目要点 响应式开发中 resize 导致抖动的本质原因是窗口变化会高频触发事件,从而反复触发布局计算。常见优化方式包括对 resize 事件进行节流或防抖以降低执行频率,避免在回调中混合 DOM 读写以减少强制同步布局,利用 requestAnimationFrame 控制更新时机,以及使用 ResizeObserver、媒体查询或容器查询等浏览器能力,让布局响应更多地交给浏览器完成,从而降低重排和重绘的成本。 参考答案 在响应式开发中,如果直接监听 resize 事件并在回调中执行布局计算或 DOM 操作,很容易引发频繁的 重排(reflow)和重绘(repaint)。原因在于浏览器在拖动窗口尺寸时会持续触发 resize 事件,如果每次触发都进行样式读取和 DOM 更新,就会造成布局计算不断被打断,从而出现明显的抖动或性能下降。 实际工程中通常会从 事件触发频率控制、布局计算方式优化、浏览器能力利用 三个层面进行处理。 首先是对 resize 事件进行频率控制。浏览器在拖拽窗口时可能每秒触发几十到上百次 resize,如果每次都重新计算布局,主线程压力会非常大。常见做法是通过 节流(throttle)或防抖(debounce) 将计算频率降低。例如在节流策略下,每隔一段时间才执行一次布局更新,从而避免在连续拖动过程中频繁触发布局计算。对于布局实时性要求较低的场景,防抖也比较适合,即在窗口停止变化后再执行计算。 其次是避免在 resize 回调中混合 DOM 读写操作。浏览器在读取布局信息(例如 offsetWidth、getBoundingClientRect)时,如果之前存在未提交的样式修改,会强制触发布局计算,这被称为 强制同步布局(Forced Reflow)。因此更好的方式是将读取和写入操作进行分离,例如先批量读取尺寸,再统一修改样式,或者借助 requestAnimationFrame 将 DOM 更新安排到浏览器下一帧执行,避免频繁打断渲染流程。 另外一个更现代的方案是尽量减少对 window.resize 的依赖,而是使用 元素级尺寸监听机制。浏览器已经提供了 ResizeObserver API,可以直接监听某个容器尺寸变化。当布局响应依赖的是容器宽度而不是窗口宽度时,这种方式更加精确,同时也减少无关 resize 触发带来的性能损耗。 在 CSS 层面也可以减少 JavaScript 的参与。例如使用 媒体查询(media query) 或 容器查询(container query) 来处理布局变化,让浏览器在样式计算阶段直接完成响应式适配。CSS 驱动的响应式布局通常比 JavaScript 监听 resize 更高效,因为浏览器可以对样式计算进行统一调度。 ...

算法 面试题

共 22 道 算法 面试题。答案默认折叠,便于先自行作答。 1. 不重复最大子串 难度:2 · 类型:QA 给定一个字符串,请实现一个函数来找到其中的不重复最大子串。例如,对于字符串"abcabcbb",不重复最大子串是"abc",长度为3。 请写出实现该功能的代码,并说明其时间复杂度。 考虑到性能优化,你认为还有哪些改进空间?请提出优化思路并实现优化后的代码。 题目要点 滑动窗口 是解决最长不重复子串问题的核心思想。 Set 方法简单直观,但每遇到重复字符可能多次移动左指针。 Map 优化通过记录字符索引,直接跳过重复区域,减少不必要操作。 时间复杂度 O(n),空间复杂度 O(Σ)。 参考答案 一、滑动窗口实现 function lengthOfLongestSubstring(s) { let 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]); left++; } set.add(s[right]); maxLen = Math.max(maxLen, right - left + 1); } return maxLen; } // 测试 console.log(lengthOfLongestSubstring("abcabcbb")); // 输出 3 思路 使用 滑动窗口 [left, right] 遍历字符串。 用 Set 存储当前窗口内字符。 当遇到重复字符时,移动左指针,直到窗口内无重复字符。 每次窗口扩大时更新最大长度。 时间复杂度 每个字符 最多进出窗口一次 → O(n) 空间复杂度:O(min(n, Σ)),Σ 是字符集大小。 二、性能优化 上面方法每遇到重复字符,需要 逐个删除左边字符。可以进一步优化为 直接跳过重复字符的索引,使用 Map 存储字符上次出现的索引。 ...

编程题 面试题

共 136 道 编程题 面试题。答案默认折叠,便于先自行作答。 1. 实现发布订阅模式 难度:2 · 类型:QA 题目要点 发布订阅模式通过事件中心解耦消息发送者与接收者,核心实现是维护事件名与回调函数列表之间的映射关系。订阅阶段注册回调函数,发布阶段遍历执行所有监听函数,取消订阅则从列表中移除对应回调。工程实践中通常会扩展一次性订阅、事件隔离等能力,但在大型应用中需要避免滥用事件总线,以免造成事件链复杂和内存管理问题。 参考答案 发布订阅模式(Publish–Subscribe Pattern)是一种典型的 消息通信模式。其核心思想是将消息的发送者(Publisher)与接收者(Subscriber)进行解耦,消息不会直接发送给某个具体对象,而是发布到一个事件或主题(Topic)上,由所有订阅该事件的监听者统一接收。 在前端应用中,这种模式常用于 组件通信、事件总线、状态变化通知、插件系统等场景。例如一个模块只负责发布事件,而其他模块只需要订阅事件即可响应变化,双方不需要知道彼此的存在。 实现发布订阅模式通常需要三个核心能力: 注册订阅(subscribe / on) 发布事件(publish / emit) 取消订阅(unsubscribe / off) 本质上可以通过维护一个 事件名 → 回调函数列表 的映射结构来实现。 一个简单实现如下: class EventEmitter { constructor() { this.events = Object.create(null); } on(eventName, handler) { if (!this.events[eventName]) { this.events[eventName] = []; } this.events[eventName].push(handler); } emit(eventName, ...args) { const handlers = this.events[eventName]; if (!handlers) return; handlers.forEach(fn => fn(...args)); } off(eventName, handler) { const handlers = this.events[eventName]; if (!handlers) return; this.events[eventName] = handlers.filter(fn => fn !== handler); } } 使用方式如下: ...

计算机基础 面试题

共 19 道 计算机基础 面试题。答案默认折叠,便于先自行作答。 1. 进程、线程、协程分别是什么概念? 难度:1 · 类型:QA 题目要点 进程 是独立的资源分配单位,每个进程有自己的内存和资源。 线程 是进程中的执行单元,线程之间共享进程的资源,但每个线程有自己的执行栈。 协程 是一种用户级的轻量级线程,通过程序控制的方式在任务之间进行切换,适合需要高效并发的场景。 参考答案 进程、线程和协程是计算机程序设计中不同层次的执行单元,各自有不同的概念和特点。以下是它们的详细解释: 1. 进程(Process) 概念:进程是操作系统分配资源的基本单位,是正在执行的程序的实例。每个进程都有自己的地址空间、内存、文件描述符等资源。 特点: 独立性:进程是相互独立的,互不干扰。一个进程的崩溃不会直接影响到其他进程。 资源分配:每个进程有独立的内存空间和系统资源。 开销大:由于需要独立的资源和内存,进程之间的切换(上下文切换)开销相对较大。 应用:常用于需要高隔离性和独立性的场景,如多进程服务器、操作系统服务等。 2. 线程(Thread) 概念:线程是进程中的执行单元,是程序执行的最小单位。一个进程可以包含多个线程,这些线程共享进程的资源(如内存)。 特点: 共享资源:线程之间共享进程的内存和资源,这使得线程间通信更加高效,但也带来了同步和竞争的问题。 开销小:线程的创建和销毁比进程要快,线程之间的切换也比进程切换更高效。 协作:线程之间可以进行协作,适合进行多任务处理。 应用:适用于需要并发执行的场景,如多线程应用程序、并行计算等。 3. 协程(Coroutine) 概念:协程是一种轻量级的线程,允许在执行过程中挂起和恢复,支持非抢占式的任务切换。协程可以在单线程中并发执行多个任务,但它们之间的切换由程序控制而不是操作系统。 特点: 协作式切换:协程通过显式的挂起和恢复操作进行切换,不需要操作系统的调度。 轻量级:协程的创建和切换开销非常小,通常比线程更高效。 适用场景:适用于需要大量并发操作但不需要多线程资源的场景,如异步编程、事件驱动编程等。 应用:广泛用于异步编程、游戏开发、网络编程等领域。许多现代编程语言(如 Python 的 asyncio、JavaScript 的 async/await)都支持协程。 2. 解释性语言和编译型语言有什么区别? 难度:2 · 类型:QA 题目要点 解释性语言:逐行解释执行,适合快速开发和跨平台,但执行速度较慢。 编译型语言:预先编译成机器码,执行速度较快,适合性能要求高的应用,但开发周期较长。 现代编程语言和环境往往结合这两种方式,以兼顾开发效率和执行性能。 参考答案 解释性语言和编译型语言是两种不同的编程语言执行方式,它们在代码执行、编译过程、执行效率等方面有显著差异。以下是它们的主要区别: 1. 解释性语言(Interpreted Languages) 定义:解释性语言的代码在运行时由解释器逐行解释和执行。解释器将源代码逐步翻译成机器码或中间代码,然后立即执行。 执行方式: 代码在每次执行时都会被解释器逐行翻译。 不需要预先编译成机器码。 优点: 开发和测试灵活性:支持即时运行和调试,便于快速开发和修改。 跨平台性:代码可以在不同平台上运行,只要有相应的解释器。 缺点: ...

计算机网络 面试题

共 104 道 计算机网络 面试题。答案默认折叠,便于先自行作答。 1. 说说流式输出的原理及其应用场景 难度:3 · 类型:QA 题目要点 流式输出的本质是将结果按时间拆分为连续数据流,实现边生成、边传输、边消费;它通过降低首字节时间和提升反馈及时性来改善用户体验;常见于大模型推理、长耗时任务、实时数据和媒体传输等场景;但同时也带来更高的工程复杂度,需要在体验收益与系统成本之间进行权衡。 参考答案 流式输出并不是一种新的计算模型,而是一种结果传递与消费方式的改变:从“一次性生成、一次性返回”,变为“边生成、边传输、边消费”。理解它的关键,不在于某个具体 API,而在于数据生产方、传输层和消费方三者如何协同工作。 一、流式输出的基本原理 1. 传统非流式模式 在非流式模式下,系统的执行流程是严格串行的: 服务端完整计算结果 将结果一次性写入响应 客户端在接收完全部数据后再进行处理或渲染 这种模式的特点是实现简单,但首字节时间(TTFB)和用户感知延迟较高,尤其当计算过程本身很慢时,用户在很长一段时间内得不到任何反馈。 2. 流式输出模式 流式输出的核心变化在于:结果不再作为一个整体返回,而是被拆分为多个连续的数据块。 典型流程是: 服务端在计算过程中,阶段性地产生部分结果 每当有新数据可用,就立刻写入响应流并 flush 客户端持续读取数据流,并逐步消费、渲染或处理 在传输层,这通常依赖于长连接 + 分块传输,例如 HTTP chunked encoding、Server-Sent Events 或 WebSocket。 3. 从系统视角看流式输出 从系统设计角度,流式输出具备以下特征: 数据是按时间顺序增量产生的 消费方不需要等待生产方完全结束 生产与消费之间形成一种弱同步关系 这使得系统整体从“请求-响应”模型,转变为一种更接近“发布-订阅”的交互方式。 二、前端视角下的实现机制 在前端,流式输出通常体现在如何读取并渲染增量数据。 以 HTTP 流为例: 浏览器通过 Fetch API 获取一个 ReadableStream 通过 getReader() 持续读取字节块 将字节解码为文本或结构化数据 逐步更新 UI,而不是等全部完成 这种方式要求前端具备更细粒度的状态管理能力,例如处理中间态、取消、错误恢复等问题。 三、典型应用场景 1. 大模型推理与对话系统 这是当前最典型的应用场景之一。 模型在生成文本时是逐 token 产生的,流式输出可以让用户几乎立即看到内容开始出现,显著降低等待焦虑,同时也便于中途打断和重试。 ...