前言
大家好,我是木斯佳。
相信很多人都感受到了,在AI浪潮的席卷之下,前端领域的门槛在变高,纯粹的"增删改查"岗位正在肉眼可见地减少。曾经热闹非凡的面经分享,如今也沉寂了许多。但我们都知道,市场的潮水退去,留下的才是真正在踏实准备、努力沉淀的人。学习的需求,从未消失,只是变得更加务实和深入。
这个专栏的初衷很简单:拒绝过时的、流水线式的PDF引流贴,专注于收集和整理当下最新、最真实的前端面试资料。我会在每一份面经和八股文的基础上,尝试从面试官的角度去拆解问题背后的逻辑,而不仅仅是提供一份静态的背诵答案。无论你是校招还是社招,目标是中大厂还是新兴团队,只要是真实发生、有价值的面试经历,我都会在这个专栏里为你沉淀下来。专栏快速地址

温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。
面经原文内容
📍面试公司:快手电商
🕐面试时间:近期
💻面试岗位:日常实习前端一面
📝面试体验:偏技术深挖,考察代码能力和举一反三能力
❓面试问题:
- 数组类型怎么判断?有哪些判断类型的方法?typeof 有什么坑?
- ts,interface 和 type 的区别?interface 可以多类型继承吗?对象如何灵活的写 interface,key 和 value 都有类型要求?
- useMemo 和 useEffect 的执行时机?useEffect 为什么要放在页面渲染后执行而不是一起执行?useEffect 不同依赖情况的执行情况?为什么 hook 的调用不能写到条件语句?那 React 要设计成这种链表结构的 hook 呢?是设计缺陷吗?你觉得 React 设计的好吗?了解 solidjs 吗?
- 写一个自定义 hook usePrev() 闭包捕获旧值(用户没写出来)
- 项目八股延伸,markdown 渲染时 HTML 标签缺失怎么解决?
- sse 和 WebSocket 的区别?
- 项目开发怎么用 ai 的?
- mcp 和 Skills 的区别?
- 看我借鉴了 mcp 的思路以为我是懂哥,然后问 mcp 相关,我说只借鉴了他的 schema 思想。
- Rag 什么作用?项目的 Rag 是怎么设计的?
- 现在这个时代你觉得前端还有未来吗?怎么打算的?
反问
来源:牛客网 自在极意前端嘉豪
💡 木木有话说(刷前先看)
中规中矩,之前我们的集锦基本上已经覆盖到了,需要注意的是问题3,深度长问需要对相关概念有比较深的了解和认知
📝 快手电商前端一面·深度解析
🎯 面试整体画像
| 维度 | 特征 |
|---|---|
| 面试风格 | React原理深挖型 + TS高级型 + AI工程化型 |
| 难度评级 | ⭐⭐⭐(三星,Hooks原理追问极深) |
| 考察重心 | 类型判断、TS高级类型、React Hooks原理、MCP/Skills、RAG |
| 特殊之处 | 对React Hooks的设计哲学连续追问,考察源码理解深度 |
🔍 逐题深度解析
一、数组类型判断与typeof坑
判断数组的方法:
| 方法 | 示例 | 说明 |
|---|---|---|
Array.isArray() |
Array.isArray([]) |
最推荐 |
instanceof |
[] instanceof Array |
跨iframe问题 |
Object.prototype.toString |
Object.prototype.toString.call([]) === '[object Array]' |
最准确 |
constructor |
[].constructor === Array |
可能被重写 |
typeof的坑:
typeof null === 'object'(历史遗留bug)typeof [] === 'object'(数组也是对象)typeof new Number(1) === 'object'(包装对象返回object)
javascript
// 万能类型判断函数
function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
}
getType([]) // 'array'
getType(null) // 'null'
getType({}) // 'object'
二、TypeScript:interface vs type
| 维度 | interface |
type |
|---|---|---|
| 声明合并 | ✅ 同名自动合并 | ❌ 不可 |
| 扩展 | extends |
&(交叉类型) |
| 实现类 | implements |
❌ 不支持 |
| 适用类型 | 对象/函数/类 | 所有类型(联合/元组/原始类型) |
| 计算属性 | ❌ | ✅(`type Keys = 'a' |
interface多类型继承:
typescript
interface A { a: string }
interface B { b: number }
interface C extends A, B { c: boolean } // 可以多继承
对象灵活interface(索引签名):
typescript
// 方式1:索引签名
interface FlexibleObject {
[key: string]: string | number // key为string,value为string或number
}
// 方式2:Record工具类型
type FlexibleObject = Record<string, string | number>
// 方式3:key约束为联合类型
type Keys = 'name' | 'age' | 'email'
type User = Record<Keys, string | number>
三、React Hooks原理深挖(核心难点)
3.1 useMemo和useEffect的执行时机
| Hook | 执行时机 | 说明 |
|---|---|---|
useEffect |
浏览器绘制后(异步) | 不阻塞UI渲染 |
useLayoutEffect |
浏览器绘制前(同步) | 阻塞渲染,用于测量DOM |
useMemo |
渲染期间(同步) | 在组件函数体内部执行 |
3.2 useEffect为什么要放在渲染后执行
原因:
- 不阻塞UI渲染:如果useEffect在渲染前同步执行,耗时操作会阻塞页面显示
- 避免副作用影响渲染:副作用(如DOM操作、订阅)应该在UI稳定后再执行
- 保证一致性:渲染函数应该是纯函数,副作用应放在effect中
3.3 useEffect不同依赖的执行情况
| 依赖数组 | 执行时机 |
|---|---|
| 无(不传) | 每次渲染后都执行 |
[](空数组) |
仅组件挂载时执行一次 |
[a, b](有依赖) |
首次挂载 + a或b变化时执行 |
3.4 为什么Hook不能写在条件语句中
原因 :React用链表结构 存储Hooks,每个Hook按调用顺序对应链表中的一个节点。条件语句会破坏调用顺序,导致Hook与状态对应错乱。
javascript
// ❌ 错误:条件语句破坏调用顺序
function Component({ flag }) {
if (flag) {
const [state, setState] = useState(0) // flag为false时,这个Hook不存在
}
const [other, setOther] = useState(1) // 顺序错乱,other拿到state的值
}
// ✅ 正确:Hook始终在顶层调用
function Component({ flag }) {
const [state, setState] = useState(0)
const [other, setOther] = useState(1)
useEffect(() => {
if (flag) { /* 条件逻辑放在Hook内部 */ }
}, [flag])
}
3.5 为什么要设计成链表结构?是设计缺陷吗?
不是设计缺陷,而是权衡后的设计选择。
链表结构的优点:
- 动态添加Hook:支持自定义Hook,数量可变
- 无需编译器魔法:不依赖特殊的语法糖
- 性能可接受:每次渲染遍历链表,开销可忽略
为什么不用其他方案:
- 对象方案:需要为每个Hook生成唯一key(如useState('count')),增加心智负担
- 编译方案:需要像SolidJS那样依赖编译器,复杂度高
React设计评价:用户被问到"你觉得React设计得好吗"------这是开放题,建议客观回答:React的设计有取舍,链表Hook机制在开发体验和性能之间做了权衡;SolidJS的编译方案更激进,但生态不如React成熟。
四、自定义Hook:usePrev(闭包捕获旧值)
题目:实现一个Hook,保存上一次渲染的值。
javascript
function usePrev(value) {
const ref = useRef()
useEffect(() => {
ref.current = value
}, [value]) // 每次value变化后,将旧值存入ref
return ref.current // 注意:第一次返回undefined
}
// 使用示例
function Counter() {
const [count, setCount] = useState(0)
const prevCount = usePrev(count)
return <div>现在:{count},之前:{prevCount}</div>
}
闭包陷阱提醒:如果在useEffect之外直接访问ref.current,可能拿到的是旧值,因为ref的更新在effect中执行。
五、Markdown渲染时HTML标签缺失怎么解决
问题 :LLM流式返回时,Markdown中的HTML标签可能被截断(如<div>没有闭合)。
解决方案:
- 延迟渲染:设置缓冲区,等待标签完整后再渲染
- 增量解析:使用状态机追踪标签开启/关闭状态
- 兜底显示:渲染前检测标签是否完整,不完整时截断
- 使用DOMPurify:净化HTML,自动修复不完整标签
javascript
// 状态机追踪HTML标签
class HTMLStreamParser {
constructor() {
this.buffer = ''
this.inTag = false
}
append(chunk) {
this.buffer += chunk
// 检测标签是否完整(标签数成对)
const openTags = (this.buffer.match(/<[^/][^>]*>/g) || []).length
const closeTags = (this.buffer.match(/<\/[^>]+>/g) || []).length
if (openTags === closeTags) {
this.render(this.buffer) // 标签完整,安全渲染
}
// 否则继续等待
}
}
六、SSE和WebSocket的区别
| 维度 | SSE | WebSocket |
|---|---|---|
| 方向 | 单向(服务端→客户端) | 双向 |
| 协议 | HTTP | WS/WSS |
| 自动重连 | 内置 | 手动实现 |
| 二进制 | 需Base64编码 | 原生支持 |
| 实现复杂度 | 低 | 中 |
七、项目开发怎么用AI
回答思路:参考之前面经。
- 代码生成:Cursor/Copilot生成组件骨架、重复性代码
- 代码审查:让AI检查边界条件、潜在bug
- 调试辅助:粘贴错误信息,AI分析原因
- 文档生成:自动生成API文档、注释
- 单元测试:AI生成测试用例
八、MCP和Skills的区别
| 维度 | MCP | Skills |
|---|---|---|
| 定位 | 标准化交互协议 | 预定义能力单元 |
| 粒度 | 系统级(工具调用规范) | 任务级(代码审查、生成测试) |
| 实现 | 客户端-服务器架构 | Prompt + 工具描述 |
| 例子 | MCP Server连接数据库 | "生成单元测试Skill" |
关系:Skills可以基于MCP协议实现。
十、RAG的作用及项目设计
RAG作用:检索增强生成,让LLM基于外部知识库回答问题,解决幻觉问题。
项目RAG设计:
- 文档处理:上传PDF/Markdown → 解析 → 智能分块(500字符+50重叠)
- 向量化:调用Embedding API(如OpenAI text-embedding-3-small)
- 存储:向量存入Pinecone/Milvus,原文存入对象存储
- 检索:用户问题向量化 → 相似度检索 → 召回Top-K
- 生成:将召回内容注入Prompt → LLM生成 → 返回引用来源
📚 知识点速查表
| 知识点 | 核心要点 |
|---|---|
| 类型判断 | Array.isArray()、typeof坑(null/数组)、Object.prototype.toString |
| TS interface vs type | 接口可合并/扩展,类型可联合/元组;索引签名[key:string]:T |
| useEffect执行时机 | 浏览器绘制后,不阻塞渲染 |
| Hook不能条件调用 | 链表结构依赖调用顺序 |
| usePrev实现 | useRef + useEffect,闭包存储旧值 |
| Markdown截断 | 缓冲区 + 状态机 + 兜底渲染 |
| SSE vs WebSocket | 单向/双向、自动重连、二进制支持 |
| MCP vs Skills | 协议 vs 能力单元 |
| RAG | 检索增强生成,解决幻觉,召回+注入Prompt |
📌 最后一句:
快手电商这场一面,是一场"React原理深水区"的面试。面试官不满足于"useEffect在渲染后执行"这种表面答案,而是追问"为什么是渲染后""为什么Hook不能写在条件里""为什么要设计成链表结构"------这些问题直指React的核心设计哲学。用户反馈"代码能力区简直是酱味大鸡",但能面对这样的深度追问,本身就是一次宝贵的技术洗礼。框架API可以速成,但对设计原理的理解,需要真正沉下心读源码、看RFC、思考取舍。 这恰恰是大厂面试中最能拉开差距的地方。