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

温馨提示:市面上的面经鱼龙混杂,甄别真伪、把握时效,是我们对抗内卷最有效的武器。
面经原文内容(一面)
📍面试公司:腾讯
🕐面试时间:2月10日
💻面试岗位:前端暑期提前批(一面)
⏱️预计时长:60分钟,实际51分钟
❓面试问题:
开场
- 对于部门的背景和这次面试有什么想问的吗
项目与跨端
-
我看到你现在是在百度实习,可以讲讲这个业务吗
-
跨端做的是IOS还是安卓
-
你对整个架构有什么理解吗,比如离线包这一块,比如离线包解决什么样的问题,离线包是怎么下发和更新的
-
如果说不用离线包,在app上打开一个http地址,这个页面会有什么问题
-
离线包除了快还有什么优势
React原理
-
React18新特性有了解吗
-
在没有这些特性之前,react是怎么调度的,有了之后是怎么调度的
-
fiber本身是为了解决什么样的问题
-
如果没有fiber,diff的时候会怎么样,会不会有一些性能瓶颈
TypeScript
-
平时ts用的多吗
-
讲讲泛型,如果现在要通过泛型传入一个string进去,希望推导出来的结果也是string,怎么做
React Hooks
-
平时用vue还是react多一点
-
讲一下react中常用的hooks
-
useLayoutEffect和useEffect有什么区别
-
useEffect中可以返回一个函数,这个函数什么时候会执行
性能优化
-
平时有没有对项目做一些性能优化
-
除了资源相关的优化,还能在哪些方面做优化
-
在浏览器中从输入URL到页面加载完毕的过程是什么(引出可优化的内容)
网络
-
有了解过http2.0吗,可以讲一下他的一些特性吗
-
二进制帧如果丢失,重传机制是怎么样的
-
TCP和UDP的区别
-
TCP是怎么去实现可靠传输的
手撕
- 深拷贝(后续又延伸问了处理除了对象和数组类型之外的数据的思路)
反问
- 反问及建议
来源:牛客网 喜喜玺玺
📝 腾讯前端暑期提前批一面·深度解析
🎯 面试整体画像
| 维度 | 特征 |
|---|---|
| 公司定位 | 腾讯 - 互联网巨头 |
| 面试风格 | 深度原理型 + 项目延伸型 + 网络基础型 |
| 难度评级 | ⭐⭐⭐⭐(四星半,从项目到原理层层深入) |
| 考察重心 | 跨端架构、React原理、TS泛型、性能优化、网络协议、手写能力 |
木木有话说:这场面试比较有价值,我分成了上下两篇去讲,今天的热帖前10只有一个是前端的。虽然环境不行,但是坚持总有回报,感谢原UP的分享,也恭喜他成功获得offer。
🔍 逐题深度解析
一、跨端架构与离线包
问题:你对整个架构有什么理解吗,比如离线包这一块?
javascript
// 1. 离线包是什么?
// 离线包是将H5页面资源(HTML/CSS/JS/图片)打包下载到本地,下次打开时直接从本地加载
// 2. 离线包解决的问题
// - 网络依赖:弱网/无网环境下可访问
// - 加载速度:本地加载,跳过网络请求
// - 用户体验:秒开率提升,白屏时间减少
// 3. 离线包下发和更新流程
// 3.1 下发
// - 发布系统生成离线包(zip包)
// - 上传到CDN
// - App启动时检查更新
// 3.2 更新机制
// - 增量更新:只下载变更的文件
// - 全量更新:版本差异大时全量下载
// - 灰度发布:逐步放量,监控异常
// 3.3 离线包结构
offline-package/
├── manifest.json // 版本信息、文件列表
├── index.html // 入口文件
├── static/
│ ├── css/
│ ├── js/
│ └── images/
问题:不用离线包,在app上打开http地址会有什么问题?
javascript
// 1. 网络问题
// - 弱网环境加载慢或失败
// - 首次打开需要下载所有资源
// 2. 性能问题
// - DNS解析耗时
// - TCP连接耗时
// - TLS握手耗时
// - 资源串行下载
// 3. 用户体验问题
// - 白屏时间长
// - 加载进度不可控
// - 断网时完全不可用
// 4. 数据统计
// 对比数据:
// - 离线包:平均加载时间 200ms
// - HTTP在线:平均加载时间 1.2s
// - 离线包首屏加载速度提升 83%
问题:离线包除了快还有什么优势?
javascript
// 1. 离线可用
// 地铁、电梯等弱网场景仍可正常使用
// 2. 节省流量
// - 只下载一次,后续使用本地资源
// - 增量更新只下载变更部分
// 3. 版本控制
// - 统一版本管理,避免缓存混乱
// - 灰度发布,逐步放量
// 4. 安全可控
// - 离线包可签名验证
// - 避免中间人攻击
// 5. 性能优化空间
// - 可预加载下一个页面
// - 可预渲染页面
// 6. 数据监控
// - 可统计离线包使用率
// - 可监控加载成功率
二、React18新特性与调度
问题:React18新特性有了解吗?
javascript
// 1. 并发渲染(Concurrent Rendering)
// - 渲染可中断,根据优先级调度
// - 高优先级任务优先执行
// 2. 自动批处理(Automatic Batching)
// - 在Promise、setTimeout中也能批量更新
setTimeout(() => {
setCount(c => c + 1)
setFlag(f => !f) // React18中会合并,只触发一次渲染
}, 0)
// 3. 新Hooks
// - useTransition:标记非紧急更新
const [isPending, startTransition] = useTransition()
startTransition(() => {
setState(newState) // 低优先级更新
})
// - useDeferredValue:延迟更新
const deferredValue = useDeferredValue(value)
// - useId:生成唯一ID
const id = useId()
// 4. Suspense改进
// - 支持服务端渲染
// - 支持数据获取
// 5. 新Root API
// React 18
const root = createRoot(container)
root.render(<App />)
// React 18之前
ReactDOM.render(<App />, container)
问题:在没有这些特性之前,react是怎么调度的,有了之后是怎么调度的?
javascript
// React 18之前:同步不可中断渲染
// - 递归遍历虚拟DOM,一旦开始无法中断
// - 如果组件树很大,会阻塞主线程
// - 用户输入、动画会出现卡顿
// React 18之后:并发可中断渲染
// 1. 时间切片
// 将渲染工作拆分成小单元(5ms),空闲时执行
requestIdleCallback(workLoop)
// 2. 优先级调度
const priorities = {
Immediate: 1, // 用户输入
UserBlocking: 2, // 点击、滚动
Normal: 3, // 网络请求
Low: 4, // 数据预加载
Idle: 5 // 日志上报
}
// 3. 双缓冲
// current树(当前显示) + workInProgress树(内存构建)
// 构建完成后一次性切换
// 4. 调度流程
function scheduleUpdate(priority) {
if (priority === Immediate) {
// 立即执行
performSyncWork()
} else {
// 放入任务队列,等待调度
taskQueue.push({ update, priority })
requestIdleCallback(processTaskQueue)
}
}
问题:fiber本身是为了解决什么样的问题?
javascript
// 1. React 15的问题
// - 递归更新:一旦开始无法中断
// - 主线程长时间占用:页面卡顿
// - 无法优先级调度:高优先级任务被阻塞
// 2. Fiber的解决方案
// 2.1 可中断
// Fiber将更新拆分成小单元,每执行一个单元检查是否有更高优先级任务
// 2.2 优先级调度
// 不同更新分配不同优先级
// 高优先级任务可打断低优先级
// 2.3 任务复用
// 更新过程中可以复用已完成的工作
// 2.4 错误边界
// 可以捕获子组件树的错误
// 3. Fiber节点结构
{
tag: 1, // 组件类型
type: 'div', // 元素类型
key: null, // 唯一标识
stateNode: DOM节点, // 真实DOM
return: Fiber父节点, // 父节点
child: Fiber子节点, // 第一个子节点
sibling: Fiber兄弟节点, // 下一个兄弟节点
pendingProps: {}, // 新props
memoizedProps: {}, // 当前props
memoizedState: {}, // 当前state
effectTag: 'UPDATE', // 操作类型
lanes: 1, // 优先级
alternate: null // 指向workInProgress树
}
问题:如果没有fiber,diff的时候会怎么样?
javascript
// 没有Fiber的情况下:
// 1. 递归diff,无法中断
function diff(oldNode, newNode) {
// 比较节点
if (oldNode.type !== newNode.type) {
replaceNode(oldNode, newNode)
return
}
// 递归子节点
for (let i = 0; i < oldNode.children.length; i++) {
diff(oldNode.children[i], newNode.children[i])
}
}
// 2. 性能瓶颈
// - 如果组件树有1000个节点,一次更新需要执行1000次diff
// - 中间无法让给用户输入,页面卡顿
// - 帧率可能掉到10fps以下
// 3. 具体问题
// - 长列表更新:全部重新计算
// - 动画卡顿:渲染任务抢占主线程
// - 输入延迟:用户输入得不到响应
// 4. 有Fiber后的改善
// - 增量渲染:分片执行,每片约5ms
// - 优先级调度:用户输入优先处理
// - 帧率提升:稳定在60fps
三、TypeScript泛型
问题:讲讲泛型,如果现在要通过泛型传入一个string进去,希望推导出来的结果也是string,怎么做?
typescript
// 1. 基础泛型
function identity<T>(arg: T): T {
return arg
}
const result = identity<string>('hello') // result类型是string
// 2. 类型推导
const result2 = identity('hello') // 自动推导为string
// 3. 约束泛型
function loggingIdentity<T extends { length: number }>(arg: T): T {
console.log(arg.length) // 可以访问length属性
return arg
}
// 4. 多个泛型参数
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 }
}
// 5. 泛型工具类型
// 传入string返回string的类型工具
type Identity<T> = T extends string ? string : T
type Result = Identity<string> // string
type Result2 = Identity<number> // number
// 6. 泛型约束实现传入string返回string
type SameType<T> = T extends string ? string : T
function sameType<T>(arg: T): SameType<T> {
return arg as any
}
const a = sameType('hello') // string
const b = sameType(123) // number
// 7. 更精确的写法
type EnsureString<T> = T extends string ? T : never
function ensureString<T>(arg: T & EnsureString<T>): T {
return arg
}
const c = ensureString('hello') // 只能传string
// ensureString(123) // 报错
四、React Hooks深入
问题:讲一下react中常用的hooks
javascript
// 1. useState - 状态管理
const [count, setCount] = useState(0)
// 2. useEffect - 副作用
useEffect(() => {
document.title = `点击了${count}次`
return () => { /* 清理 */ }
}, [count])
// 3. useContext - 上下文
const theme = useContext(ThemeContext)
// 4. useReducer - 复杂状态
const [state, dispatch] = useReducer(reducer, initialState)
// 5. useCallback - 缓存函数
const handleClick = useCallback(() => {
doSomething(count)
}, [count])
// 6. useMemo - 缓存计算结果
const expensiveValue = useMemo(() => {
return computeExpensive(count)
}, [count])
// 7. useRef - 引用
const inputRef = useRef(null)
// 8. useLayoutEffect - 同步执行
useLayoutEffect(() => {
// DOM更新后同步执行
}, [])
// 9. useImperativeHandle - 暴露子组件方法
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus()
}))
// 10. useDebugValue - 调试
useDebugValue(value)
问题:useLayoutEffect和useEffect有什么区别?
javascript
// 1. 执行时机
// useEffect:异步执行,在浏览器绘制之后
// useLayoutEffect:同步执行,在DOM更新后、浏览器绘制前
// 2. 执行顺序
function Component() {
useEffect(() => {
console.log('useEffect')
}, [])
useLayoutEffect(() => {
console.log('useLayoutEffect')
}, [])
return <div>Component</div>
}
// 输出顺序:useLayoutEffect → useEffect
// 3. 适用场景
// useLayoutEffect:需要同步测量DOM
useLayoutEffect(() => {
const height = ref.current.offsetHeight // 立即测量
setHeight(height)
}, [])
// useEffect:大多数副作用场景
useEffect(() => {
fetchData() // 异步请求
}, [])
// 4. 性能考虑
// useLayoutEffect会阻塞浏览器绘制,尽量少用
// useEffect不会阻塞绘制,性能更好
问题:useEffect中可以返回一个函数,这个函数什么时候会执行?
javascript
// 1. 组件卸载时
useEffect(() => {
console.log('组件挂载')
return () => {
console.log('组件卸载') // 组件销毁时执行
}
}, [])
// 2. 依赖更新前(清理上一次的副作用)
useEffect(() => {
console.log('count变化:', count)
return () => {
console.log('清理上一次的count副作用') // 每次count更新前执行
}
}, [count])
// 3. 每次重新渲染后
useEffect(() => {
console.log('每次渲染后执行')
return () => {
console.log('下次渲染前执行') // 每次重新渲染前都会执行
}
}) // 无依赖数组
// 4. 典型应用:清除定时器
useEffect(() => {
const timer = setInterval(() => {
console.log('tick')
}, 1000)
return () => {
clearInterval(timer) // 组件卸载时清除定时器
}
}, [])
// 5. 取消订阅
useEffect(() => {
const subscription = eventEmitter.subscribe(handleEvent)
return () => {
subscription.unsubscribe() // 组件卸载时取消订阅
}
}, [])
五、性能优化
问题:除了资源相关的优化,还能在哪些方面做优化?
javascript
// 1. 渲染优化
// 1.1 React.memo
const MemoizedComponent = React.memo(Component)
// 1.2 useMemo避免重复计算
const expensiveValue = useMemo(() => compute(a, b), [a, b])
// 1.3 useCallback避免函数重新创建
const handleClick = useCallback(() => doSomething(), [deps])
// 1.4 虚拟列表
import { FixedSizeList } from 'react-window'
<List
height={500}
itemCount={10000}
itemSize={35}
>
{({ index, style }) => <div style={style}>Row {index}</div>}
</List>
// 2. 代码优化
// 2.1 避免内联函数
// ❌ 不好
<button onClick={() => handleClick(id)} />
// ✅ 好
const handleClick = useCallback(() => doSomething(id), [id])
<button onClick={handleClick} />
// 2.2 避免重复计算
// ❌ 不好
const visibleList = list.filter(item => item.visible) // 每次渲染都计算
// ✅ 好
const visibleList = useMemo(() =>
list.filter(item => item.visible), [list]
)
// 3. 网络优化
// 3.1 请求合并
// 将多个请求合并成一个
// 3.2 数据缓存
const cache = new Map()
async function fetchData(key) {
if (cache.has(key)) return cache.get(key)
const data = await api.get(key)
cache.set(key, data)
return data
}
// 4. 用户体验优化
// 4.1 骨架屏
<div className="skeleton" />
// 4.2 渐进式加载
// 先显示低质量内容,再替换为高质量
// 4.3 预加载
<link rel="preload" href="next-page.js" as="script">
问题:从输入URL到页面加载完毕的过程,有哪些可优化的点?
javascript
// 1. DNS解析优化
// - DNS预解析
<link rel="dns-prefetch" href="//api.example.com">
// 2. TCP连接优化
// - 预连接
<link rel="preconnect" href="https://api.example.com">
// 3. 请求发送优化
// - 减少请求数量(合并、雪碧图)
// - 减少请求体积(压缩、Gzip)
// - CDN加速
// 4. 服务器响应优化
// - 缓存策略
// - 数据压缩
// - CDN缓存
// 5. 浏览器解析优化
// - 关键CSS内联
// - 异步加载JS
// - 懒加载
// 6. 渲染优化
// - 避免重排重绘
// - 骨架屏
// - 虚拟列表
六、网络协议
问题:有了解过http2.0吗,可以讲一下他的一些特性吗?
javascript
// HTTP/2.0核心特性
// 1. 二进制分帧
// 将消息分割为更小的帧,二进制传输
// 请求1: [frame1][frame2]
// 请求2: [frame1][frame2][frame3]
// 2. 多路复用
// 一个TCP连接同时发送多个请求
// 避免HTTP/1.1的队头阻塞
// 3. 头部压缩
// 使用HPACK压缩头部,减少体积
// 第一次请求:{ method: 'GET', path: '/', user-agent: 'Chrome' }
// 第二次请求:只发送差异部分
// 4. 服务器推送
// 服务器主动推送资源
push('/', {
'style.css': styles,
'script.js': script
})
// 5. 请求优先级
// 可以设置请求优先级,关键资源优先
// 6. 性能提升对比
// HTTP/1.1: 6个连接,串行请求
// HTTP/2.0: 1个连接,并行请求
// 加载时间减少30-50%
问题:二进制帧如果丢失,重传机制是怎么样的?
javascript
// 1. HTTP/2.0基于TCP,重传机制由TCP保证
// 2. TCP重传机制
// 2.1 超时重传
// 发送数据后启动定时器,超时未收到ACK则重传
// 2.2 快速重传
// 收到3个重复ACK,立即重传
// 2.3 选择性确认(SACK)
// 告知发送方哪些数据已收到,只重传丢失部分
// 3. HTTP/2.0层面的处理
// 3.1 流控
// 每个流有独立的流量控制
// 3.2 优先级
// 丢失的帧如果是高优先级,会优先重传
// 3.3 错误处理
// 如果某个流持续丢包,可能关闭该流
问题:TCP和UDP的区别?
| 维度 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠传输(确认重传) | 不可靠,可能丢包 |
| 顺序 | 保证数据顺序 | 不保证顺序 |
| 流量控制 | 有(滑动窗口) | 无 |
| 拥塞控制 | 有 | 无 |
| 头部大小 | 20字节 | 8字节 |
| 速度 | 慢 | 快 |
| 适用场景 | HTTP、FTP、邮件 | 直播、DNS、游戏 |
问题:TCP是怎么去实现可靠传输的?
javascript
// 1. 确认应答(ACK)
// 发送方发送数据后,等待接收方确认
// 未收到确认则重传
// 2. 超时重传
// 发送数据时启动定时器,超时未收到ACK则重传
// 3. 序列号
// 每个字节都有序列号,接收方按序组装
// 4. 校验和
// 检测数据是否损坏
// 5. 流量控制(滑动窗口)
// 接收方告知发送方自己的接收能力
// 发送方根据窗口大小调整发送速度
// 6. 拥塞控制
// - 慢启动
// - 拥塞避免
// - 快速重传
// - 快速恢复
七、手撕:深拷贝
问题:深拷贝(处理对象、数组及其他类型)
javascript
// 1. 基础版(只处理对象和数组)
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item))
}
const cloned = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key])
}
}
return cloned
}
// 2. 完整版(处理循环引用、各种类型)
function deepClone(obj, hash = new WeakMap()) {
// 处理null和基本类型
if (obj === null || typeof obj !== 'object') return obj
// 处理循环引用
if (hash.has(obj)) return hash.get(obj)
// 处理Date
if (obj instanceof Date) {
return new Date(obj)
}
// 处理RegExp
if (obj instanceof RegExp) {
return new RegExp(obj)
}
// 处理Map
if (obj instanceof Map) {
const map = new Map()
hash.set(obj, map)
obj.forEach((value, key) => {
map.set(key, deepClone(value, hash))
})
return map
}
// 处理Set
if (obj instanceof Set) {
const set = new Set()
hash.set(obj, set)
obj.forEach(value => {
set.add(deepClone(value, hash))
})
return set
}
// 处理Array
if (Array.isArray(obj)) {
const arr = []
hash.set(obj, arr)
obj.forEach((item, index) => {
arr[index] = deepClone(item, hash)
})
return arr
}
// 处理Object
const cloned = Object.create(Object.getPrototypeOf(obj))
hash.set(obj, cloned)
// 处理Symbol属性
const symbols = Object.getOwnPropertySymbols(obj)
symbols.forEach(sym => {
cloned[sym] = deepClone(obj[sym], hash)
})
// 处理普通属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key], hash)
}
}
return cloned
}
// 3. JSON方法(有局限)
const clone = JSON.parse(JSON.stringify(obj))
// 局限:不能处理函数、undefined、Symbol、循环引用
// 4. 处理其他类型
function cloneOtherTypes(obj) {
switch (Object.prototype.toString.call(obj)) {
case '[object Date]':
return new Date(obj)
case '[object RegExp]':
return new RegExp(obj)
case '[object Map]':
return new Map(obj)
case '[object Set]':
return new Set(obj)
default:
return null
}
}
📚 知识点速查表
| 知识点 | 核心要点 |
|---|---|
| 离线包 | 本地资源、快、离线可用、增量更新 |
| React18 | 并发渲染、自动批处理、新Hooks |
| Fiber | 可中断、优先级调度、双缓冲 |
| TS泛型 | 类型参数、约束、工具类型 |
| Hooks | 常用Hooks、执行时机、清理函数 |
| 性能优化 | 渲染优化、代码优化、网络优化 |
| HTTP/2 | 二进制分帧、多路复用、头部压缩 |
| TCP可靠 | ACK、重传、序列号、滑动窗口 |
| 深拷贝 | 循环引用、各种类型、WeakMap |
📌 最后一句:
腾讯这场一面,从跨端架构到React原理,从TS泛型到网络协议,再到手写深拷贝,覆盖了一个前端工程师需要的核心能力。面试官问的每个问题都在考察原理深度和工程实践的结合。能答好这些,说明你已经有了冲击大厂的实力。
