LZWcc's Blog

Back

第一次真实技术面,这几个问题我几乎都没答出来。逐个补上理解,作为下一次面试前的复习笔记。

1. 如何排查一个网站加载过慢?#

答题骨架

一句话: 先定位慢在哪一段,再针对性优化,最后用指标验证。

  1. 确认现象:首屏白屏、资源加载慢,还是交互卡顿;先稳定复现
  2. 看 Network:瀑布图和 Timing,拆成 DNS、连接/TLS、TTFB、下载,判断是网络、后端还是资源问题
  3. 看 Performance:请求都结束了还慢,就找主线程长任务、布局和绘制
  4. 验证:用 LCP、INP、CLS 对比优化前后

可能追问: TTFB 高一定是后端慢吗?LCP、INP、CLS 分别是什么,阈值多少?

先分清楚慢在哪里#

“加载慢”可能是打开后一直白屏、图片迟迟不出现,也可能是页面已经出现,但点击按钮没反应。不同现象对应不同检查方向。

我会先确认:哪个页面、什么操作、首次访问还是再次访问、所有用户还是特定地区或设备。记录一次可复现的过程,再比较正常和异常情况。

先理解一次 HTTPS 请求的过程#

排查之前,先知道请求经历了哪些阶段。下面以通过新建 TCP 连接访问 HTTPS 网站为例,把 DNS、TLS、TTFB 等术语串起来;Initiator 则用于追踪请求由谁触发。

从域名到响应 一次 HTTPS 请求,经历了什么? 新建 TCP 连接示例 · DNS / TLS / TTFB
  1. DNS找到服务器 这个域名对应哪个 IP?

    浏览器先解析域名,获得服务器的 IP 地址。已有 DNS 缓存时,这一步可能很快,或不需要再次查询。

    Network → TimingDNS Lookup

  2. TCP建立连接 先与服务器建立通信连接。

    浏览器与服务器通过 TCP 握手建立连接,为后续可靠、有序地传输数据做准备。网络距离、丢包等会影响连接耗时。

    Network → TimingInitial connection(包含连接建立等耗时)

  3. TLS建立加密通信 确认对方身份,协商如何加密。

    TLS 是 Transport Layer Security(传输层安全协议)。握手时,浏览器验证服务器证书,双方协商加密参数和密钥,保护后续的 HTTP 通信。HTTPS 就是通过 TLS 保护 HTTP。

    Network → TimingSSL(DevTools 中常用的名称)

  4. HTTP发送请求 告诉服务器:我需要这个网页或数据。

    连接准备好后,浏览器发送 HTTP 请求,包括请求方法、路径、请求头,以及可能存在的请求体。

    Network → TimingRequest sent

  5. TTFB等待首字节 请求发出后,多久开始收到回复?

    TTFB 是 Time To First Byte(首字节时间)。这里特指 DevTools 的 Waiting 阶段:等待响应的第一个字节,其中包含网络往返和服务器准备响应的时间。因此,这一段很长不能直接证明数据库慢。收到第一个字节也不代表网页已经下载完。

    Network → TimingWaiting (TTFB)

  6. Download下载响应 继续接收响应的剩余内容。

    开始收到回复后,浏览器继续接收 HTML、接口数据或其他资源的响应内容。耗时与内容大小、带宽,以及浏览器接收和读取数据的情况有关。下载完成也不等于页面已经渲染完成。

    Network → TimingContent Download

范围:HTTP/1.1 或 HTTP/2 over TCP 的简化流程,省略排队、代理等阶段。连接复用、DNS 缓存和资源缓存可能省去部分步骤;HTTP/3 使用 QUIC,不能直接套用这里的 TCP 流程。

口径:页面加载指标中的 TTFB 通常还包含重定向、DNS 和连接建立等前置时间,不能直接等同于这里的 Waiting 阶段。DevTools 的 Initial connection 与 SSL 展示也可能存在包含关系,不应机械相加。

查看 Chrome Network Timing 说明 ↗

按请求和渲染链路排查#

打开 DevTools 的 Network,重新加载页面,查看瀑布图和请求的 Timing。首次访问可以禁用缓存测试,但也要保留正常缓存下的对照。

看到的现象先检查什么后续方向
DNS、连接、TLS 阶段长域名解析、网络延迟、连接复用从不同网络复测,检查连接和 CDN
Waiting / TTFB 长HTML 或 API 等待首字节时间结合服务端日志查排队、业务处理、数据库和上游服务
Content Download 长响应大小、带宽、压缩和缓存减少传输内容,优化图片、压缩和缓存
请求很多,而且串行发生请求之间的依赖和 Initiator消除不必要的瀑布请求,独立任务可并行
请求结束了,页面仍迟迟不显示主线程执行、样式计算、布局、绘制在 Performance 中定位长任务和渲染阻塞

TTFB 高不能直接判定为 SQL 慢,它还受网络往返和服务器其他处理影响。浏览器证据只能帮助缩小范围,具体后端原因需要服务端证据。Network Timing 说明 ↗

如果主要资源已经下载完,就录制 Performance,看时间花在 JavaScript、布局还是绘制上。例如:同步计算阻塞主线程、大量 DOM 同时渲染、反复读取布局后写样式,都可能让用户觉得页面卡住。

用指标验证效果#

展开了解 LCP、INP、CLS:内容出来、点击响应、页面乱跳

这三个是 Core Web Vitals(核心网页指标),分别衡量加载、交互响应和视觉稳定性。

指标用户的感受含义与例子良好阈值
LCP(Largest Contentful Paint,最大内容绘制)主要内容多久出来?从开始加载,到视口内最大的合格图片或文本块绘制出来。例如首屏主图终于显示。≤ 2.5 秒
INP(Interaction to Next Paint,交互到下一次绘制)点了之后多久有反馈?观察一次访问中的点击、触摸和键盘交互,以接近最慢的交互延迟代表整体响应性。例如点击菜单后多久展开。≤ 200 毫秒
CLS(Cumulative Layout Shift,累计布局偏移)页面会不会突然乱跳?衡量意外布局偏移,取最严重的一段偏移窗口的累计分数。例如上方图片加载后,把正要点击的按钮挤走。≤ 0.1(无单位)

容易混淆的地方: LCP 不等于所有资源下载完;INP 看的是下一次绘制的反馈,不是等待接口业务全部完成;CLS 也不只观察加载阶段,用户操作引起的预期位移通常不计入。

LCP 慢时结合 Network 和 Performance 查关键资源与渲染;INP 差时重点看主线程长任务;CLS 高时检查图片是否预留尺寸、内容是否突然插入等。这些阈值用于真实用户数据时,要按移动端、桌面端分别看第 75 百分位,不能凭一次本地测试判断整体达标。指标定义与阈值 ↗

修复后用相同设备、网络和操作复测。实验室测试适合复现问题,线上真实用户数据能判断问题是否普遍存在。

2. Vue 3 相对于 Vue 2 有哪些变化?#

答题骨架

一句话: 主要变化在响应式原理、代码组织方式和渲染性能三方面。

  1. 响应式:Object.defineProperty → Proxy,能检测新增、删除属性和数组下标,支持 Map、Set
  2. 代码组织:在 Options API 之外新增 Composition API,用 composable 替代 mixins,TS 类型推断更好
  3. 渲染:编译器标记静态和动态内容,减少运行时比较(见第 4 题)
  4. 其他:Fragment 多根节点、Teleport;迁移时注意 v-model 等破坏性变化

可能追问: Proxy 比 defineProperty 好在哪?mixins 有什么问题?(见下方两个折叠块)Vue 3 必须用 Composition API 吗?

维度Vue 2Vue 3
对象响应式主要通过 Object.defineProperty 劫持属性主要通过 Proxy 代理对象
变更检测新增、删除对象属性,以及数组索引赋值、修改长度存在限制对代理对象的这些操作可以被追踪,也支持 Map、Set
逻辑组织常用 Options API(选项式 API),复用常见 mixins可使用 Composition API(组合式 API),把相关逻辑组织成 composable
TypeScript能使用,但复杂选项和 mixins 的推断较困难Composition API 的变量、函数更利于类型推断
模板与渲染已有静态优化和虚拟 DOM编译器给运行时更多提示,减少无用比较
组件能力通常单个根节点支持 Fragment 多根节点、Teleport 等

Vue 3 的 ref 通过 .value 的 getter/setter 跟踪访问,不能笼统地说所有响应式都只用 Proxy。Vue 2 响应式说明 ↗、Vue 3 响应式原理 ↗

展开了解对象响应式原理:defineProperty 拦截属性,Proxy 拦截对象

两者思路相同:读取时收集依赖(谁用了这个数据),写入时通知依赖更新。区别在于拦截的对象不同。

Vue 2:初始化时递归遍历 data,把每个已有属性改写成 getter/setter。

// 简化示意,非源码
function defineReactive(obj, key, val) {
  observe(val) // 值是对象就继续递归,初始化时一次性转完
  Object.defineProperty(obj, key, {
    get() {
      dep.depend() // 收集依赖
      return val
    },
    set(newVal) {
      val = newVal
      dep.notify() // 通知更新
    }
  })
}
js

因为只拦截”已经存在的某个属性”,所以新增、删除属性检测不到。

Vue 3:用 Proxy 代理整个对象,所有读、写、删除操作都会经过它。

操作/维度Vue 2Vue 3
新增属性 obj.newKey = 1检测不到,用 this.$set能检测
删除属性 delete obj.key检测不到,用 this.$delete能检测
数组下标赋值 arr[0] = x、改 length检测不到,用 $set 或 splice能检测
push、splice 等 7 个变更方法能检测(Vue 改写了数组原型方法)能检测
Map、Set不支持支持
嵌套对象初始化时全部递归转换访问时才转换,初始化成本更低
兼容性支持 IE9+不支持 IE(Proxy 无法 polyfill)

面试一句话: Vue 2 用 defineProperty 在初始化时递归劫持每个已有属性,所以新增、删除属性和数组下标赋值检测不到,要用 $set;Vue 3 用 Proxy 代理整个对象,这些操作都能拦截,还支持 Map、Set,嵌套对象按需懒代理。代价是不支持 IE,基本类型仍需 ref。

两个容易答错的点#

  • Vue 3 仍然支持 Options API,不要求全部改成 Composition API。
  • Composition API 不是 Vue 3 独占,Vue 2.7 也内置了它。比较版本时要说明具体版本,而不是只比较写法。

Composition API 的重点是逻辑组织与复用。例如,把搜索词、请求、加载状态和取消请求放在一个 useSearch() 中,比把同一功能分散在多个选项里更容易维护,但它不会自动让业务代码变快。Composition API FAQ ↗

展开了解 mixins(混入)与 composable(组合式函数):为什么 Vue 3 推荐后者

Vue 2 中,多个组件共用一段逻辑,最常用 mixins:把 data、methods、生命周期等选项写成一个对象,组件引入后 Vue 会把它合并进组件。

mixins 的三个问题:

  1. 来源不清:this.x 来自哪个 mixin,要逐个文件找。
  2. 命名冲突:多个 mixin 或组件定义了同名属性,会按合并规则静默覆盖。
  3. 隐式依赖:mixin 可能依赖组件”应该有”的某个属性,但这种约定没有写在任何地方。

Vue 3 用 composable 替代:它就是一个普通函数,显式调用、显式返回。

面试一句话: mixins 靠合并选项复用逻辑,存在来源不清、命名冲突、隐式依赖的问题;composable 是普通函数,显式调用、显式返回,解决了这些问题。Composables ↗

迁移细节参考:Vue 3 Breaking Changes ↗。

3. Vue 3 的响应式有哪些限制或容易踩的坑?#

答题骨架

一句话: Vue 3 的响应式依赖“通过代理读写”,绕开代理就追踪不到。

  1. 解构:基本类型属性解构成普通变量后不再更新 → 用 toRefs
  2. 替换变量:给变量重新赋值一个新的 reactive,原有依赖收不到通知 → 需要整体替换时用 ref
  3. 原始对象:直接修改原始对象不触发更新 → 统一使用代理对象
  4. DOM 异步更新:改完状态立即读 DOM 还是旧的 → await nextTick()

可能追问: ref 和 reactive 怎么选?解构 defineProps() 会丢失响应式吗?

Vue 3 只有通过代理对象(或 ref.value)读写才会被追踪,原理见第 2 题的折叠块。下面前三个坑本质上都是读写绕开了代理;后两个则和更新调度、编译处理有关。

解构基本类型属性后,普通变量不会继续跟随更新#

import { reactive, toRefs } from 'vue'

const state = reactive({ count: 0 })
const { count } = state

state.count++
console.log(count) // 0:这只是解构时得到的数字

const { count: countRef } = toRefs(state)
state.count++
console.log(countRef.value) // 2:仍然通过 ref 关联 state.count
js

不是 state 失去响应式,而是普通数字变量不再通过代理读取属性。解构出的值如果是一个响应式对象,对该对象的嵌套修改仍可以被追踪;但父属性被替换后,旧变量不会自动指向新对象。

替换局部变量,不等于通知原来的依赖#

import { reactive, ref, watchEffect } from 'vue'

let state = reactive({ count: 0 })
watchEffect(() => console.log(state.count))

state = reactive({ count: 1 }) // 普通变量赋值,本身不会触发上面的 effect

// 需要整体替换时,用 ref 作为稳定的响应式入口
const form = ref({ name: '' })
form.value = { name: 'LZWcc' }
js

reactive() 适用于对象、数组、集合,不能直接把一个数字变成响应式。基本类型或需要整体替换的状态可以用 ref()。响应式基础与限制 ↗

代理对象和原始对象不是同一个引用#

import { reactive } from 'vue'

const raw = { count: 0 }
const state = reactive(raw)

console.log(state === raw) // false
raw.count++ // 绕过代理,不会由这次写入触发响应式更新
state.count++ // 通过代理修改
js

raw.count++ 确实修改了底层数据,只是没有触发通知;以后发生其他更新时可能读到这个新值,因此更容易造成误解。业务里应尽量统一使用代理,避免混用原始对象和代理对象。响应式深入 ↗

状态更新不等于 DOM 已更新#

修改响应式状态后立即读取 DOM,可能还是旧结果。需要读取更新后的 DOM 时使用 await nextTick(),而不是凭感觉加一个 setTimeout()。这是批量更新的调度机制,不是响应式失效。

注意 Vue 3.5 的 props 解构例外#

在 Vue 3.5+ 的同一个 <script setup> 中,编译器会对从 defineProps() 解构出的变量访问进行处理,使其保持响应性。这个能力不适用于任意 reactive() 解构。

<script setup>
import { watch } from 'vue'

const { title } = defineProps(['title'])

// 传入 getter,watch 才能追踪读取;不是传入当前字符串值
watch(() => title, (value) => console.log(value))
</script>
vue

所以,“Vue 3 解构一定丢失响应式”说得过于绝对,要区分对象、值类型、编译上下文与版本。Reactive Props Destructure ↗

4. Vue 3 框架自身做了哪些性能优化?#

答题骨架

一句话: 核心是编译器和运行时配合:编译时分析模板,运行时只处理会变的部分。

  1. 静态提升/缓存:不变的节点只创建一次,更新时复用
  2. Patch Flags:标记节点哪部分是动态的(文本、class 等),只比较这部分
  3. Block Tree:直接收集动态子节点,跳过静态结构的逐层遍历
  4. tree-shaking:没用到的 API 打包时去掉,减小体积

可能追问: 批量异步更新是 Vue 3 新增的吗?

这道题先讲框架原理。第五题再讲我写项目时可以采取的措施。

编译阶段识别哪些内容会变#

<template>
  <section>
    <h2>面试复习</h2>
    <p>{{ count }}</p>
  </section>
</template>
vue

当 count 更新时,标题并没有变化。Vue 编译器可以分析模板,把这些信息留给运行时,减少重复工作。

机制解决什么问题
静态内容缓存/静态提升复用不变的节点或表达式,避免每次更新都重新创建和比较;具体生成形式随版本变化
Patch Flags标记节点的动态部分,例如只需更新文本或 class,避免逐项全面检查
Block Tree / 动态子节点收集在稳定结构内直接追踪动态节点,减少逐层遍历静态结构

重点是 Vue 3 进一步利用编译信息,缩小运行时 diff 需要处理的范围。Vue 渲染机制 ↗

构建体积与更新调度#

支持 tree-shaking 的 API 配合构建工具,可以移除未使用的功能,减少下载和执行的 JavaScript。

Vue 会批量处理组件更新,同一轮同步代码连续修改状态,不会每次都立刻更新 DOM。注意这在 Vue 2 中就已存在,不算 Vue 3 新增。Vue 性能指南 ↗、Vue 2 更新队列 ↗

5. 开发 Vue 3 项目时有哪些性能优化手段?#

答题骨架

一句话: 先测量,区分加载慢还是更新慢,再对症优化并复测。

  1. 加载:路由懒加载、异步组件、精简依赖、图片尺寸与懒加载、按需考虑 SSR/SSG
  2. 更新:大列表分页或虚拟列表;传稳定的 props 减少子组件更新;computed 缓存;大型数据用 shallowRef
  3. 高频操作:输入防抖、取消过期请求
  4. 验证:优化后复测,说明指标变化和代价

可能追问: 首屏图片能懒加载吗?v-memo 有什么风险?

先测量瓶颈,再分别考虑加载性能和更新性能。

加载阶段:少下载、少阻塞#

  • 路由按需加载,把暂时不用的页面代码拆出去;大型非首屏组件按需异步加载。
  • 检查打包产物,减少重复或过大的依赖,确认按需导入是否有效。
  • 图片使用适当尺寸和格式,非首屏图片可延迟加载;首屏关键图片不应盲目懒加载。
  • 根据业务考虑 SSG 或 SSR,并衡量服务端成本与客户端 hydration 成本。
// Vue Router 的路由懒加载
const routes = [
  {
    path: '/interview',
    component: () => import('./pages/Interview.vue')
  }
]
js

这些措施影响不同阶段,应结合请求与渲染关键路径判断优先级。Vue 性能指南 ↗、理解关键渲染路径 ↗

更新阶段:减少不必要的工作#

场景可采取的措施需要理解的边界
大列表分页或虚拟列表虚拟列表主要减少同时存在的 DOM,不会自动减少服务器返回的数据
父状态变化导致大量子组件更新传递稳定、必要的 props避免每次都创建语义相同的新对象
重复派生计算使用 computed缓存依赖结果,不代表首次计算没有成本
大型不可变数据考虑 shallowRef嵌套修改不会自动通知,需要替换根值或显式触发
高频输入与请求防抖、取消过期请求防抖降低触发频率,取消或结果校验处理响应乱序

v-once 适用于确定不再更新的内容,v-memo 可以按依赖跳过子树更新,但依赖遗漏会导致显示陈旧。先确认有收益,再使用这些优化。Vue 更新性能优化 ↗

例如列表选中态,把“是否选中”算好后传给行组件:

<Row
  v-for="item in items"
  :key="item.id"
  :item="item"
  :active="item.id === activeId"
/>
vue

在 item 引用稳定等条件下,切换 activeId 时,大多数行的 props 不变,有助于跳过其更新。key 要表达稳定身份,不要用随机值强迫重建。

6. 计算机网络的五层是什么?#

答题骨架

一句话: 从上到下是应用层、传输层、网络层、数据链路层、物理层。

  1. 应用层:应用之间的消息格式,如 HTTP、DNS
  2. 传输层:进程之间通信,用端口区分应用,如 TCP、UDP
  3. 网络层:IP 寻址和跨网络路由
  4. 数据链路层:相邻节点之间传输帧,如以太网、Wi-Fi
  5. 物理层:把比特变成电、光、无线信号

加分: 用一次 HTTP 请求串起来:发送时逐层封装,接收时逐层拆开。

可能追问: 和 OSI 七层、TCP/IP 四层有什么区别?经过路由器时帧会变吗?

从上往下记:应用、传输、网络、数据链路、物理。传输层在部分教材中也叫运输层。

层次主要职责例子常见数据单位
应用层定义应用之间交换消息的规则HTTP、DNS、SMTP报文
传输层端到端的进程通信,使用端口区分应用TCP、UDPTCP 报文段、UDP 数据报
网络层IP 寻址和跨网络路由IPv4、IPv6、ICMP分组/IP 数据报
数据链路层相邻节点之间传输帧、进行差错检测等以太网、Wi-Fi 的链路部分帧
物理层在介质上传输比特,规定信号等特性电信号、光信号、无线信号比特

用一次 HTTP 请求串起来#

以 HTTP/1.1 over TCP 为例,先省略加密细节:应用生成 HTTP 请求,传输层通过 TCP 提供可靠、有序的字节流,网络层用 IP 寻址并经路由器转发,链路层在每一跳封装成帧,物理层把比特变成信号发送。

接收端逐层处理,最终把请求交给服务器应用。经过路由器时,链路帧会按下一跳重新封装,不是一个以太网帧原封不动穿过整个互联网。

不要混淆三种模型#

  • 五层模型是常见的教学划分。
  • OSI 是七层,多出会话层、表示层等划分。
  • TCP/IP 通常按四层描述,把物理与链路相关内容归入链路层。

RFC 1122 描述了互联网协议族的分层结构,可用来对照 TCP/IP 模型,不能把它直接称为“五层标准”。RFC 1122 ↗

7. MySQL 有哪些存储引擎?#

答题骨架

一句话: 常见的是 InnoDB、MyISAM、MEMORY,一般业务用默认的 InnoDB。

  1. InnoDB:默认引擎;支持事务、行级锁、MVCC、崩溃恢复、外键
  2. MyISAM:不支持事务和外键,表级锁,老项目可能遇到
  3. MEMORY:数据在内存中,重启后丢失,表级锁
  4. 补充:存储引擎是表级别的选择;还有 CSV、ARCHIVE 等专用引擎,用 SHOW ENGINES 查看

可能追问: 为什么一般业务选 InnoDB?行级锁一定只锁一行吗?

存储引擎负责表数据的存储、索引和访问等工作。它是表级别的选择,同一个数据库可以有使用不同引擎的表。

引擎需要记住的特点
InnoDB默认引擎;支持事务、崩溃恢复、外键,使用行级锁和 MVCC 支持并发
MyISAM不支持事务和外键约束,采用表级锁;老项目可能遇到
MEMORY数据放在内存中,重启后行数据丢失;不支持事务,采用表级锁
CSV以 CSV 文本形式存储数据,适合特定交换场景,不支持索引
ARCHIVE面向压缩归档场景,能力有专门限制
BLACKHOLE接收写入但不保存数据,可用于特定复制或测试场景
NDB用于 MySQL NDB Cluster,不代表普通 MySQL 实例默认可用

实际可用引擎应在当前实例检查:MySQL 8.4 存储引擎文档 ↗。

-- 查看当前实例支持的引擎
SHOW ENGINES;

-- 查看已有表的引擎
SHOW CREATE TABLE your_table;
sql

为什么一般业务选择 InnoDB?#

订单、库存这类数据通常需要事务的一致性保障;InnoDB 的事务、崩溃恢复和并发控制能力适合这类需求。MVCC 让一致性读可以读取合适的数据版本,但不表示所有读操作都不加锁,例如锁定读和普通快照读不同。

“支持行锁”也不意味着任何语句都只锁一行。实际锁定范围受索引、查询条件和隔离级别影响,不能只看 SQL 表面写了一个条件。InnoDB 简介 ↗

面试复盘(一):网站性能、Vue 3、计算机网络与 MySQL
https://lzwcc.xyz/blog/interview-review-01/
Author LZWcc
Published at October 2, 2026