虚拟盒子前端实习面经:八股合格但网络链路翻车
- 轮次
- 一面+二面
- 结果
- 未通过
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 自我介绍
- 用过哪些框架?
- Vue 组件通信有哪些方式?
- 闭包是什么?
- 闭包的使用场景有哪些?
- computed 和 watch 的区别?
- 讲一下实习项目:业务、你的职责、难点分别是什么?
- TypeScript 里 any 和 unknown 有什么区别?
- Promise 讲一下
- 两个互不依赖的接口怎么同时请求?
- Vue3 了解吗?和 Vue2 有什么区别?
- Vue3 的响应式原理是什么?
- Vite 和 Webpack 有什么区别?
- 用过哪些 AI 工具?
- AI 多次解决不了的时候,你怎么分析?
- 深拷贝和浅拷贝
- 深拷贝还有哪些实现方法?
- Token 自动刷新是怎么做的?
- 浏览器缓存
- SSE 是怎么做的?
- 网络中断时页面会怎样?
- (第二位面试官)再自我介绍一遍
- 心跳保活用什么通信方式?
- 站内聊天是网页、桌面还是 APP?
- WebSocket 的用户 ID 从哪里获取?
- html2canvas + jsPDF 的导出链路和样式是怎么处理的?
- React、Next.js、桌面端做过吗?
- 什么是 RESTful?
- HTTPS 链路是怎么跟服务器连接的?
- TCP 的响应机制是什么?
- AI 的长记忆、短记忆怎么抽离和存储?
《参考解析》
1. any 与 unknown。any 等于关掉类型检查:赋值给任何类型、任意属性访问、任意调用都放行,代码退回 JS;unknown 是类型安全的「未知」——任何值都能赋给 unknown,但用之前必须收窄(typeof、instanceof、自定义类型守卫或 as 断言),不能直接属性访问、直接调用,也不能直接赋给具体类型。实践上,接第三方数据、JSON.parse 的返回值、catch (e) 里的 e 都应该用 unknown,再用类型守卫收窄;any 只应出现在迁移期的临时位置,并配 noImplicitAny 和 lint 规则约束。追问常延伸到 never:never 表示「不可能有值」,用于穷尽性检查,比如 switch 的 default 分支里写 const _exhaustive: never = value。
2. computed 与 watch。computed 是派生状态:基于响应式依赖算出新值,有缓存(依赖不变就不重算)、必须同步返回值,适合在模板里直接渲染;watch 是副作用:依赖变化时执行一段逻辑,不返回值、可以异步,适合发请求、操作 DOM、写 localStorage。选型口诀是「能用 computed 表达的别用 watch」,因为 watch 里再赋值给另一个 ref 会引出多源同步和循环更新。watchEffect 自动收集用到的依赖并立即执行一次,适合依赖不固定的场景;watch 默认惰性,能拿到新旧值(对象类型要注意新旧值可能是同一个引用),比较对象内部要 deep: true。Vue3 的 computed 是懒求值加 effect 的 dirty 标记,配合 Proxy 收集到的细粒度依赖,比 Vue2 基于 defineProperty 的实现更容易做到按需更新。
3. Token 无感刷新。核心是「双 token(access + refresh)+ 单飞(single flight)」。access 过期(接口返回 401 且错误码表示已过期)时,不能让每个并发请求各自去刷新,否则会刷新出多份新 token、还可能互相作废。做法是维护一个全局 refreshPromise:第一个失败的请求发起刷新并把它存下来,其余请求 await 同一个 promise;刷新成功后把这些请求重放一遍(先把请求配置里的旧 header 换掉,并限制重放次数避免死循环);刷新失败则清空登录态并跳登录。这就是「并发锁 + 失败队列」的含义。另外要注意 refresh token 自身也有过期时间、也要防并发;服务端最好支持「刷新即作废旧 token」并处理旧 token 重放;测试时要专门构造「同一时刻 5 个接口同时 401」的场景,这是最容易漏的用例。
4. 浏览器缓存。强缓存由 Cache-Control: max-age=31536000, immutable 或 Expires 控制,命中就不发请求。Expires 是绝对时间且依赖客户端时钟,和服务端对不齐就会出偏差,所以现代实现以 Cache-Control 优先、Expires 只作老客户端兜底。协商缓存是 Last-Modified/If-Modified-Since 或 ETag/If-None-Match,命中返回 304 且不带 body;ETag 比 Last-Modified 精确(后者是秒级粒度,内容没变但时间变了会误判),但分布式部署要注意 ETag 生成算法一致。实践口径:带内容 hash 的静态资源用一年 max-age 加 immutable;HTML 入口用 no-cache(要协商)保证发版立即生效——no-cache 不是不缓存,no-store 才是完全不存;还要知道地址栏回车、F5、Ctrl+F5 三种操作分别会带上什么缓存头。
5. SSE 与断网处理。原生 EventSource 的限制很多:只能发 GET、不能自定义请求头(所以带不了 Authorization)、不能带请求体、重连由浏览器托管、只能传文本。要「带鉴权 + 自定义 header + 可中断」,就用 fetch 加 response.body.getReader() 手写解析:按 \n\n 切事件块,逐行解析 data:/event:/id:,用 TextDecoder({stream: true}) 处理跨 chunk 的半截 UTF-8 字符——这个坑最容易踩,中文被切断会变乱码;取消用 AbortController。断网时的表现是 reader.read() 挂起或抛错,要按状态码给不同的 UI 反馈(401 跳登录、5xx 提示重试、网络错误走自动重连),重连时带 Last-Event-ID 或业务游标做断点续传,并且保留已收到的内容而不是清空重来;服务端要定期发注释行心跳(: ping),否则 Nginx 的 proxy_read_timeout 和 CDN 会把空闲连接掐掉。
6. RESTful 与 HTTPS 链路。RESTful 是一套架构风格:用 URI 标识资源、用 HTTP 方法表达动作、用状态码表达结果——GET /users/1 取资源、POST /users 创建、PUT/PATCH 全量或部分更新、DELETE 删除;资源名用名词复数、从属关系用层级表达(/users/1/orders)、过滤分页排序走 query。它不是「URL 里带占位符」,核心是语义由方法和状态码承担,而不是靠 /getUserById 这类动词路径;还要能说清 PUT 幂等而 POST 不幂等、GET 不应有副作用。HTTPS 链路:TCP 三次握手之后先做 TLS 握手——ClientHello(版本、密码套件、随机数)→ ServerHello + 证书链 → 客户端验证证书(签发者是否受信、是否过期、域名是否匹配、是否被吊销)→ 用密钥交换(TLS 1.3 是 ECDHE,1.2 常见 RSA/ECDHE)协商出会话密钥 → Finished 校验握手完整性,之后才发 HTTP 请求。要点是:证书只负责证明「这个公钥属于这个域名」,防窃听和防篡改靠的是对称加密加 AEAD;TLS 1.3 把握手压到 1-RTT、复用时可 0-RTT,并统一用 (EC)DHE 获得前向安全;有代理或 CDN 时是「浏览器到 CDN」和「CDN 到源站」两段独立 TLS,排障要先分清是哪一段的证书。至于「TCP 的响应机制」,如果问的不是握手挥手,想听的是确认应答、超时重传与快速重传、滑动窗口、拥塞控制这一整套——也就是「TCP 怎么知道对方收到了、丢了怎么补」。