快手AI平台前端开发面试题
一、完整面试题清单
一面(技术基础)
-
介绍一下 SSE (Server-Sent Events) 和 WebSocket 的区别? -
谈谈 CSS 实现对话气泡的几种方式? -
Promise 的状态有哪些,如何实现一个简单的超时控制? -
说说 React Hooks 为什么不能写在条件判断里? -
如何处理前端长文本的截断与展开? -
手写题:实现一个简单的 EventEmitter? -
算法:判断括号匹配(用于 Prompt 模板校验) -
浏览器垃圾回收机制了解吗? -
什么是跨域,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 配置、服务端中转转发三类,可根据场景选择。核心要点:
-
跨域定义:同源策略要求两个地址的协议、域名、端口完全一致,任一不同即跨域,是浏览器的安全机制,仅限制浏览器端的请求,服务端不受此限制 -
解决方案: -
开发环境代理:前端通过 Vite/Webpack 的 devServer.proxy 配置,将接口请求代理转发到 AI 服务端,解决开发阶段跨域 -
服务端 CORS 配置:AI 服务端配置跨域资源共享,设置 Access-Control-Allow-Origin等响应头,允许指定域名的前端请求,是生产环境的主流方案 -
服务端中转转发:前端请求自己的后端服务,由后端服务作为中间层去请求 AI 接口,再将结果返回给前端,彻底规避跨域,同时可以保护 API Key 不泄露 -
其他方案:JSONP(仅支持 GET 请求,适配性差,不推荐)
夜雨聆风