乐于分享
好东西不私藏

快手AI平台前端开发面试题

快手AI平台前端开发面试题

一、完整面试题清单

一面(技术基础)

  1. 介绍一下 SSE (Server-Sent Events) 和 WebSocket 的区别?
  2. 谈谈 CSS 实现对话气泡的几种方式?
  3. Promise 的状态有哪些,如何实现一个简单的超时控制?
  4. 说说 React Hooks 为什么不能写在条件判断里?
  5. 如何处理前端长文本的截断与展开?
  6. 手写题:实现一个简单的 EventEmitter?
  7. 算法:判断括号匹配(用于 Prompt 模板校验)
  8. 浏览器垃圾回收机制了解吗?
  9. 什么是跨域,AI 接口调用遇到跨域怎么解决?

二、解答

一面(技术基础)

1. 介绍一下 SSE (Server-Sent Events) 和 WebSocket 的区别?

总结:SSE 是基于 HTTP 协议的服务端到客户端单向推送方案,自带断线重连,适配 AI 对话这类服务端持续推送的场景;WebSocket 是独立于 HTTP 的全双工 TCP 协议,支持双向实时通信,更适合客户端与服务端频繁交互的场景。核心要点

  • 通信方向:SSE 是单向通信(仅服务端→客户端);WebSocket 是双向全双工通信,两端可随时互发数据
  • 协议基础:SSE 基于标准 HTTP 协议,复用现有基础设施;WebSocket 需通过 HTTP 握手后升级为独立 TCP 协议
  • 断线重连:SSE 内置自动重连机制;WebSocket 需手动实现重连逻辑
  • 数据支持:SSE 默认仅支持 UTF-8 文本,可通过 Base64 转码二进制;WebSocket 原生支持文本和二进制数据
  • 浏览器限制:SSE 受同域名并发连接数限制(Chrome 上限6个);WebSocket 无此限制

2. 谈谈 CSS 实现对话气泡的几种方式?

总结:对话气泡的核心是实现主体容器+尖角效果,主流方案以伪元素为基础,纯色场景用边框法,带边框场景用旋转法,复杂异形用 clip-path 裁剪,渐变色场景用遮罩法,兼顾兼容性与效果还原。核心要点

  • 边框三角法:给宽高为0的伪元素设置透明边框构造三角形尖角,兼容性最好,适配纯色气泡
  • 旋转方块法:用伪元素生成小正方形,旋转45度后定位到气泡边缘,适配带边框的气泡
  • clip-path 裁剪法:直接裁剪出气泡主体+尖角的完整形状,代码简洁,适配复杂异形,低版本浏览器兼容性一般
  • 遮罩/渐变法:使用 mask-image 或背景渐变实现尖角,完美适配渐变色背景,保证尖角与主体颜色一致

3. Promise 的状态有哪些,如何实现一个简单的超时控制?

总结:Promise 共有3种不可逆的状态,状态一旦从 pending 变更就无法修改;超时控制核心是通过 Promise.race() 让业务请求和定时器 Promise 竞争,达到超时时间直接 reject,可配合 AbortController 取消原请求。核心要点

  • Promise 状态:pending(进行中)、fulfilled(已成功)、rejected(已失败),仅支持 pending→fulfilled 或 pending→rejected 两种流转,状态变更后不可逆
  • 超时核心实现:使用 Promise.race() 同时传入业务请求 Promise 和超时定时器 Promise,定时器在指定时间后执行 reject,以先完成的 Promise 结果为准
  • 进阶优化:超时触发后,通过 AbortController 取消原网络请求,避免无效资源占用
  • 异常处理:必须通过 catch 捕获超时异常,避免程序崩溃

4. 说说 React Hooks 为什么不能写在条件判断里?

总结:React 内部通过链表结构,按 Hooks 的调用顺序存储和匹配对应状态,条件判断会导致每次组件渲染时 Hooks 调用顺序不一致,引发状态匹配错位、渲染异常等不可预期的问题。核心要点

  • 存储机制:每个组件对应的 Fiber 节点上,有一个 memoizedState 链表,Hooks 的状态和副作用会按调用顺序存储在该链表中
  • 匹配规则:React 完全依赖 Hooks 的物理调用顺序关联对应状态,每次渲染都会按顺序遍历链表匹配状态
  • 异常后果:条件判断、循环语句会导致 Hooks 调用顺序在不同渲染轮次中不一致,引发链表偏移,后续 Hooks 会取到错误的状态,出现渲染错乱等bug
  • 官方约束:eslint-plugin-react-hooks 插件会强制校验该规则,开发阶段给出报错提示

5. 如何处理前端长文本的截断与展开?

总结:纯展示类的长文本截断优先用 CSS 方案,开发成本低、性能好;需要支持展开收起、自定义截断逻辑的复杂场景,用 JS 动态计算实现,同时做好性能优化。核心要点

  • 单行截断:使用 CSS 实现,核心属性 white-space: nowrap + overflow: hidden + text-overflow: ellipsis,兼容性最好
  • 多行截断:使用 CSS 的 -webkit-line-clamp 属性,配合 display: -webkit-box 限制显示行数,适配大部分现代浏览器
  • JS 动态截断:获取文本容器与内容的高度,对比计算出可显示的最大字符数,截取文本后拼接省略号,同时实现展开/收起切换逻辑,适配所有自定义场景
  • 性能优化:通过防抖处理窗口 resize 事件,避免频繁的高度计算和重渲染;提前缓存文本内容,减少重复操作

6. 手写题:实现一个简单的 EventEmitter?

总结:EventEmitter 是发布订阅模式的经典实现,核心是用对象存储事件名和对应的回调函数数组,实现事件绑定、触发、解绑、一次性执行四个核心方法。核心实现代码

classEventEmitter{
constructor() {
// 存储事件与回调数组的映射
this.events = Object.create(null);
  }

// 绑定事件回调
  on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
    }
this.events[eventName].push(callback);
  }

// 触发事件,传递参数
  emit(eventName, ...args) {
const callbacks = this.events[eventName];
if (!callbacks) return;
// 遍历执行所有回调
    callbacks.forEach(cb => cb.apply(this, args));
  }

// 解绑指定回调
  off(eventName, callback) {
const callbacks = this.events[eventName];
if (!callbacks) return;
// 过滤掉要移除的回调
this.events[eventName] = callbacks.filter(cb => cb !== callback);
  }

// 绑定一次性事件,执行后自动解绑
  once(eventName, callback) {
const onceCallback = (...args) => {
      callback.apply(this, args);
this.off(eventName, onceCallback);
    };
this.on(eventName, onceCallback);
  }
}

核心要点

  • 用对象存储事件映射,key 为事件名,value 为回调函数数组
  • on 方法:初始化事件数组,推入回调函数
  • emit 方法:获取对应回调数组,遍历执行并传递参数
  • off 方法:过滤移除指定的回调函数
  • once 方法:封装回调,执行后自动调用 off 解绑

7. 算法:判断括号匹配(用于 Prompt 模板校验)

总结:这是栈结构的经典应用题,核心思路是遇到左括号入栈,遇到右括号则弹出栈顶的左括号进行匹配,中途匹配失败或遍历结束后栈不为空均为不合法,可直接用于 Prompt 模板的符号合法性校验。核心实现代码

functionisValidBrackets(str{
// 建立右括号到左括号的映射
const bracketMap = {
')''(',
']''[',
'}''{'
  };
const stack = [];

for (let char of str) {
// 如果是左括号,入栈
if (Object.values(bracketMap).includes(char)) {
      stack.push(char);
    } 
// 如果是右括号,校验匹配
elseif (bracketMap[char]) {
// 栈空 或 栈顶不匹配,直接返回false
if (!stack.length || stack.pop() !== bracketMap[char]) {
returnfalse;
      }
    }
// 非括号字符,直接跳过
  }

// 遍历结束,栈必须为空才合法
return stack.length === 0;
}

核心要点

  • 用数组模拟栈结构,遵循先进后出的原则
  • 建立右括号到对应左括号的映射,快速匹配校验
  • 左括号入栈,右括号弹出栈顶匹配,不匹配直接返回 false
  • 边界处理:覆盖空字符串、全左括号、全右括号、嵌套括号等场景

8. 浏览器垃圾回收机制了解吗?

总结:浏览器(V8 引擎)的垃圾回收核心是标记清除算法,同时采用分代回收策略,将内存分为新生代和老生代,针对不同生命周期的对象使用不同的回收算法,自动清理内存中不可达的对象,减少内存泄漏。核心要点

  • 核心算法:标记清除法。分为标记和清除两个阶段,标记阶段从根对象(window、全局变量等)出发,遍历标记所有可达的对象;清除阶段回收所有未被标记的不可达对象,解决了循环引用的问题
  • 分代回收策略:V8 引擎将堆内存分为两个区域
    • 新生代:存放生命周期短的对象,使用 Scavenge 算法,将内存分为两半,每次只使用一半,回收时将存活对象复制到另一半,清空原区域,执行速度快
    • 老生代:存放生命周期长、占用内存大的对象,使用标记-整理算法,标记存活对象后,将存活对象向内存一端移动,清理边界外的内存,减少内存碎片
  • 淘汰算法:引用计数法,早期浏览器使用,通过记录对象被引用的次数判断是否回收,存在循环引用无法回收的缺陷,现已基本淘汰

9. 什么是跨域,AI 接口调用遇到跨域怎么解决?

总结:跨域是浏览器的同源策略限制,当请求的地址和当前页面的协议、域名、端口任一不一致时,就会产生跨域,阻止非同源请求的响应;AI 接口跨域的核心解决方案分为前端开发环境代理、服务端 CORS 配置、服务端中转转发三类,可根据场景选择。核心要点

  • 跨域定义:同源策略要求两个地址的协议、域名、端口完全一致,任一不同即跨域,是浏览器的安全机制,仅限制浏览器端的请求,服务端不受此限制
  • 解决方案:
    1. 开发环境代理:前端通过 Vite/Webpack 的 devServer.proxy 配置,将接口请求代理转发到 AI 服务端,解决开发阶段跨域
    2. 服务端 CORS 配置:AI 服务端配置跨域资源共享,设置 Access-Control-Allow-Origin 等响应头,允许指定域名的前端请求,是生产环境的主流方案
    3. 服务端中转转发:前端请求自己的后端服务,由后端服务作为中间层去请求 AI 接口,再将结果返回给前端,彻底规避跨域,同时可以保护 API Key 不泄露
    4. 其他方案:JSONP(仅支持 GET 请求,适配性差,不推荐)