在这里插入图片描述


一面(技术基础)

1. 请简述一下事件循环机制,并说明微任务和宏任务在其中的执行时机。

JavaScript 是单线程的,事件循环是它处理异步任务的核心机制。
浏览器中有调用栈、任务队列和微任务队列。整体流程是:

  1. 执行全局同步代码(属于一个宏任务)。
  2. 当这个宏任务执行完后,会清空所有微任务
  3. 之后浏览器可能会执行渲染。
  4. 再从宏任务队列中取出下一个宏任务执行,重复以上过程。
  • 宏任务:script(整体代码)、setTimeoutsetInterval、I/O、UI 渲染等。
  • 微任务Promise.then/catch/finallyMutationObserverqueueMicrotask

执行时机
一个宏任务完成后,会立即执行该宏任务产生的所有微任务,然后再进入下一个宏任务。微任务总是插队在下一个宏任务之前执行,所以微任务的优先级比宏任务高。


2. 请手写一个简单的防抖函数,并说明在 AI 对话场景中如何应用。

function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

AI 对话场景应用

  • 输入联想:用户在输入框连续输入时,不需要每次按键都向后端请求建议,可以等用户停止输入一段时间(如300ms)后再请求联想接口,减少无效请求。
  • 流式响应后的自动滚动:AI 流式输出内容时,页面需要自动滚动到底部,但频繁滚动可能抖动,用防抖让渲染平稳后再滚动到底,提升体验。

3. 什么是 Promise,它解决了什么问题?

Promise 是 JavaScript 异步编程的一种解决方案。它代表一个尚未完成但将来会完成的异步操作,有三种状态:pending、fulfilled、rejected,状态一旦改变就不可逆。

主要解决的问题

  • 回调地狱:之前的异步依赖回调嵌套,多层嵌套让代码难以维护。Promise 通过链式 .then() 调用实现扁平化。
  • 错误处理统一:可以通过 .catch() 统一捕获前面所有链路的错误。
  • 信任问题:Promise 保证只会被决议一次,回调一定会被异步调用,避免了多重回调、调用过早/过晚等问题。
  • 组合能力:提供 Promise.allPromise.race 等静态方法,方便并行控制和竞态处理。

4. 请解释一下什么是流式输出(Streaming),前端如何处理这种数据?

流式输出指服务端持续生成数据,并以分块(chunk)的形式逐步发送给客户端,而不是等全部内容生成完毕一次性返回。这在 AI 大模型对话中非常常见,可以实现打字机式的响应体验。

前端处理方式

  • 使用 Server-Sent Events (SSE):通过 EventSource API 监听服务端推送的文本事件,逐段更新界面。
  • 使用 Fetch API 读取流:fetch(url).then(res => res.body.getReader()),循环读取 Uint8Array 块,用 TextDecoder 解码,按行或按标记分割后更新 UI。
  • 也可使用 WebSocket,接收分片消息。

关键点:需要处理不完整的数据块,维护缓冲区,拼接完整后再渲染(尤其是对 Markdown 这种结构化文本)。


5. 简单谈谈你对 CSS 盒模型的理解。

CSS 盒模型描述了元素在页面中生成的矩形盒子,由内到外四层:

  • 内容 (content):真实内容区域。
  • 内边距 (padding):内容与边框之间的空白。
  • 边框 (border):包围 padding 和内容的边界。
  • 外边距 (margin):与其他元素之间的间距。

两种模式

  • 标准盒模型 box-sizing: content-box:width/height 仅指内容区域,盒子总宽 = width + padding + border。
  • 替代盒模型 box-sizing: border-box:width/height 包含 content + padding + border,更符合布局直觉,现代项目中通常全局设置 border-box

理解盒模型是精确布局、避免尺寸计算错误的基础。


6. 解释一下什么是闭包,它在前端开发中有什么作用?

闭包是指函数能够记住并访问其定义时的词法作用域,即使该函数在其他地方被调用。
本质:内部函数保留对外部函数变量的引用,导致外部函数作用域无法被垃圾回收。

在前端开发中的作用

  • 数据私有化:模拟私有变量,模块模式,避免全局污染。
  • 函数工厂:创建预配置的函数,例如 bind 部分参数。
  • 保持状态:在事件处理、定时器、异步回调中保留循环变量或局部状态(解决经典的循环索引问题)。
  • 实现高阶函数:防抖、节流、柯里化都依赖闭包保存计时器或累积参数。
    注意:滥用闭包可能导致内存泄漏,需适时释放。

二面(深入原理与项目)

1. 在 AI 项目中,你们是如何处理大模型返回的 Markdown 渲染问题的?

我们通常采用以下方案:

  • 解析库:使用 markedremark + rehype 将 Markdown 转 HTML,配合 highlight.js / prism.js 做代码高亮。
  • 安全处理:利用 DOMPurify 净化输出,防止 XSS。
  • 流式容错:大模型流式返回时 Markdown 可能不完整(代码块未闭合等),我们实现状态机缓冲,等检测到完整块级结构(如代码块结束符 ` ````)再进行渲染,或对不完整部分先用纯文本展示。
  • 增量渲染:维护 Markdown 源字符串不断追加,触发重新解析,同时用 React.memo 或虚拟 DOM 减少重绘;复杂场景引入增量解析器,只解析新部分。
  • 自定义组件:将解析后的 AST 映射为自定义 React 组件(如 CodeBlock 带复制按钮、表格响应式、数学公式用 KaTeX),提升交互与视觉。

2. 聊聊 React 的 Fiber 架构,它为什么能提升性能?

Fiber 是 React 16 重构的协调引擎。之前的 Stack Reconciler 采用递归遍历更新,过程不能中断,长时间占用主线程导致卡顿。

Fiber 的核心改进:

  • 可中断的异步协调:将渲染任务拆成多个小的 Fiber 节点(工作单元),通过链表结构可随时暂停和恢复。
  • 时间切片:利用浏览器空闲时间(requestIdleCallback 或 Scheduler 包)执行工作单元,让出主线程给更高优先级的任务(如用户输入、动画)。
  • 优先级调度:不同更新分配不同优先级(如用户交互高于数据获取),高优先级任务可打断低优先级。
  • 双缓冲:在内存中构建新的 Fiber 树,完成后一次性提交到 DOM,减少中间状态闪烁。

通过这些机制,Fiber 使 React 能够实现并发渲染,保证复杂应用的响应性和流畅度。


3. 什么是 RAG(检索增强生成),前端在其中扮演什么角色?

RAG(Retrieval-Augmented Generation)是将信息检索与 LLM 结合,先从外部知识库检索相关文档,再将文档作为上下文增强模型的生成,减少幻觉,得到更准确、即时的回答。

前端角色

  • 提供知识入口:上传文档、配置知识库的界面。
  • 展示检索证据:在 AI 回答中高亮展示引用的原文片段、来源链接,提升可信度。
  • 上下文组装:前端可能将用户问题 + 本地缓存文档片段拼接,再提交给大模型,或与后端约定格式。
  • 交互反馈:支持用户对检索结果进行“赞/踩”或引用调整,帮助优化检索。
  • 对话管理:结合对话历史,可能前端也需对上下文进行裁剪,确保检索片段和对话都能放进模型上下文窗口。

4. 说说前端工程化中,如何进行性能监控?

主要包括以下方面:

  • 核心 Web 指标:使用 web-vitals 库或 PerformanceObserver 采集 LCP、FID/INP、CLS。
  • 自定义性能:通过 performance.timingperformance.getEntriesByType('navigation') 收集页面加载时间、资源耗时、API 响应时长。
  • 错误监控:捕获 window.onerrorunhandledrejection,资源加载错误,上报堆栈和元数据。
  • 接口监控:包装 fetch / XMLHttpRequest,记录请求的耗时、状态码、响应大小,统计成功率。
  • 上报策略:使用 navigator.sendBeacon 或图片打点,在页面卸载时确保数据发送;采样率控制、数据压缩。
  • 工程化集成:通过构建插件注入监控代码,自动上传 sourcemap 以解析错误堆栈,配合告警平台(Sentry 等)。

目标是建立可量化的性能基线和实时告警,驱动优化决策。


5. 谈谈你对前端模块化的理解,CommonJS 和 ES Modules 有什么区别?

模块化是将代码拆分为独立、可复用的模块,避免全局变量污染和依赖混乱。
历史方案有 IIFE、CommonJS、AMD、CMD、UMD,最终标准化为 ES Modules。

CommonJS 与 ES Modules 主要区别

  • 语法:CJS 用 require/module.exports;ESM 用 import/export
  • 加载时机:CJS 运行时动态加载;ESM 编译时静态分析,构建阶段即可确定依赖关系,利于 tree shaking。
  • 输出方式:CJS 输出值的拷贝,基本类型被缓存;ESM 输出值的只读引用,模块内部变化会反映到外部。
  • this 指向:CJS 模块顶层 this 指向当前模块;ESM 顶层 thisundefined
  • 循环依赖:CJS 可能得到不完整副本;ESM 由于是动态引用,能更好地处理循环依赖。

现代项目普遍以 ESM 为标准,借助打包工具兼容。


6. 什么是跨域?你们项目中是如何解决跨域问题的?

跨域是浏览器的同源策略限制:协议、域名、端口任一不同,就不能读取另一源的响应,目的是防止恶意网站窃取数据。

解决方式

  • CORS:服务端设置 Access-Control-Allow-Origin 等响应头,支持预检请求,是最正规的方案。
  • 代理:开发环境通过 webpack-dev-servervite 的 proxy 将 API 请求转发到同域;生产环境用 Nginx 反向代理,后端 BFF 层转发。
  • JSONP:利用 <script> 标签不受同源限制,仅支持 GET,已较少用。
  • WebSocket:不受同源策略约束,可跨域通信。
  • postMessage:用于 iframe 或窗口间的安全跨域通信。

在我们 AI 项目中,调用外部模型 API 主要通过 BFF 代理,既解决跨域又能隐藏密钥,同时可以增加鉴权和流量控制。


7. 解释一下原型链,为什么说 JS 是基于原型的语言?

每个 JS 对象都有一个内部 [[Prototype]](可通过 __proto__Object.getPrototypeOf 访问),指向它的原型对象。原型对象也有自己的原型,层层向上直到 null,形成原型链。
当访问对象属性时,如果自身没有,就会沿着原型链向上查找。

为什么是基于原型的语言?
JS 中没有传统的类继承,而是通过对象直接继承自另一个对象来共享属性和方法。
构造函数创建实例时,实例的 [[Prototype]] 指向构造函数的 prototype,这就是原型继承。
ES6 的 class 只是语法糖,本质还是基于原型的委托机制。这种动态、可随时修改原型的关系使 JS 非常灵活,称为基于原型的语言。


8. 在 AI 聊天应用中,如何处理长对话的上下文管理?

核心挑战是模型上下文窗口有限(token 限制),需在保持连贯性和控制成本间平衡。

前端策略

  • 裁剪策略:保留最新 N 轮对话,或计算总 token 数,超限时从最早的消息开始移除(保留系统提示词)。
  • 摘要压缩:对早期对话生成摘要,替代历史消息,减少 token 消耗。
  • 智能保留:标记重要消息(如用户明确要求记住的信息)不被裁剪。
  • 用户控制:提供“清空上下文”按钮,允许用户主动重置。
  • 本地缓存:前端保留完整历史,仅向后端发送裁剪后的上下文,本地展示可折叠早期消息以优化性能。
  • 显示提示:当上下文接近窗口上限时给出提示,避免丢失重要信息。

实际中我们多采用滑动窗口 + 手动清空组合,并考虑在后续版本加入自动摘要。


三面(架构与综合能力)

1. 如果让你设计一个 AI 智能体(Agent)的前端工作台,你会考虑哪些核心模块?

核心模块包括:

  • 对话与交互面板:多轮对话,流式渲染,支持用户输入、文件/图片上传,展示代理思考过程(如“推理中”、“调用工具”等状态)。
  • 工具与插件管理:可视化配置代理可调用的 API、数据库、代码解释器等,测试工具调用结果。
  • 知识库与记忆管理:上传文档、管理向量库,配置长期记忆,查看检索命中记录。
  • 任务规划与工作流编排:拖拽式编排多步骤任务,展示代理拆解的计划(DAG 图),可执行、暂停、重试子任务。
  • 调试与可观测性:请求/响应日志,token 消耗,工具调用链追踪,错误告警,成本统计。
  • 权限与安全:密钥管理,用户权限控制,沙箱环境。
  • 仪表盘:性能指标、使用量、成功率等监控面板。

技术考量:采用微前端或插件架构保证可扩展性;通过 SSE 或 WebSocket 保持实时通信;实现流式渲染引擎以处理过程输出。


2. 聊聊你在项目中遇到的最大挑战,你是如何解决的?

(此处以 AI 聊天场景为例)最大挑战是 Markdown 流式渲染中的格式撕裂和性能问题。模型输出逐字到达,频繁重新解析整段 Markdown 并渲染,不仅造成闪烁,还可能因代码块未闭合导致布局错乱。

解决过程

  1. 调研发现现有解析库难以处理不完整 Markdown。
  2. 我们设计了一个增量解析层:基于有限状态机跟踪当前块类型(普通文本、代码块、表格等),等待块结束符才调用完整解析。不完整块先用纯文本安全渲染。
  3. 使用 React 的虚拟列表和 React.memo 减少长列表重绘,仅在尾部增量更新时局部渲染。
  4. 结合 requestAnimationFrame 批量更新,避免频繁 DOM 操作。
    最终测试稳定,用户体验显著提升。也让我深刻理解了流式处理与编译原理前端的结合。

3. 你如何看待 AI 对前端开发的影响?你平时如何使用 AI 工具?

影响

  • 大幅提升效率:自动化生成代码、单元测试、样式调整,让开发者更聚焦在架构与交互设计上。
  • 降低门槛:非专业开发者也能通过自然语言构建界面。
  • 工作重心转移:前端工程师需要更懂 AI 集成、流式处理、大模型限制、安全等;更关注产品思维和对 AI 产出的审查把控。
  • 不会完全替代:AI 生成的代码仍需要人类审查、优化性能、保证可维护性,深度业务逻辑需要人工决策。

平时使用

  • 用 GitHub Copilot / ChatGPT 快速补全代码、生成样板,减少重复劳动。
  • 解释陌生代码或技术文档,快速学习。
  • 分析错误日志,提出可能原因和修复建议。
  • 进行技术方案对比,但会自己验证真伪。
  • 通过对话快速产出原型设计思路。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐