文章目录


一、Vue 2 (Options API) vs Vue 3 (Composition API) 深度对比

1. 核心定义

  • Options API (Vue 2):通过定义 data, methods, computed, watch 等选项来组织逻辑。
  • Composition API (Vue 3):通过 setup 函数,将相同逻辑功能的变量与函数组合在一起。

2. 优缺点详表

维度 Options API (选项式) Composition API (组合式)
优点 上手简单:结构固定,新手友好。
规范统一:代码看起来很整齐。
逻辑聚合:相同功能的代码可以写在一起。
复用性极强:通过 Hooks (Composables) 轻松复用。
TS 支持完美:原生支持类型推导。
Tree-shaking:按需引入,打包体积更小。
缺点 逻辑碎片化:大型组件中,一个功能的代码散落在各处。
复用困境:Mixins 容易导致命名冲突和来源不明。
TS 适配差:难以进行类型推导。
学习成本:需要理解 ref, reactive, toRefs 等底层概念。
自由度过高:如果缺乏规范,代码可能写得比 Options API 还乱。

3. 为什么 Vue 3 最终选择了 Composition API?

A. 解决“逻辑碎片化”问题

在 Options API 中,处理“搜索”功能的代码可能在 data 里有变量,在 methods 里有方法,在 mounted 里有初始化。当组件变得庞大时,开发者需要频繁上下滚动。
Composition API 允许你把这部分逻辑提取到一个独立的函数中,实现“高内聚、低耦合”。

B. 彻底取代 Mixins

Vue 2 时代逻辑复用主要靠 Mixins,但它有三大痛点:

  1. 命名冲突:Mixins 的属性会被合并到组件中。如果两个 Mixin 甚至组件本身定义了同名的 data 或 methods,后引入的会覆盖前面的。
    • 痛点:当你引入多个 Mixin 时,你必须小心翼翼地检查每一个变量名,防止被覆盖。
  2. 来源不明:这是 Mixins 最致命的问题。在模板中使用一个变量时,如果它不是定义在当前组件里,你很难一眼看出它来自哪个 Mixin。
    • 痛点:当一个组件引入了 5 个 Mixin 时,调试代码就像在走迷宫,你需要在 5 个不同的文件中搜索某个函数的定义。
  3. 缺乏灵活性与高耦合:Mixins 很难接收参数来改变其内部逻辑。它们通常是“全量引入”,你无法按需取用。
    • Composition API 通过函数导入导出的方式,让数据来源变得清晰透明。

C. 拥抱 TypeScript

Options API 的 this 上下文在 TypeScript 中很难进行类型推导。而 Composition API 基于普通的变量和函数,天然适配 TS,极大地提升了大型项目的维护性。

D. 为什么组合式 API 能取代它?

组合式 API 通过函数式的思维解决了上述问题:

  1. 显式引入:变量和函数是通过解构赋值获取的,来源一目了然。

  2. 重命名机制:由于是解构赋值,你可以随意重命名变量,彻底告别命名冲突。

  3. 参数传递:你可以像调用普通函数一样传参,逻辑更加灵活。


4. 形象类比

  • Options API 就像是整理房间的抽屉:袜子放袜子抽屉,内衣放内衣抽屉。虽然整齐,但如果你要穿一套衣服,你得打开好几个抽屉。
  • Composition API 就像是搭配好的套装:把上衣、裤子、袜子配好放在一起。你要穿哪套,直接整套拿走就行。

💡 面试金句:
“Composition API 并不是要取代 Options API,它实际上是为解决复杂业务逻辑的组织与复用而生的。它让 Vue 具备了处理大型、长期维护项目的能力,同时也让 Vue 与 TypeScript 的结合达到了工业级的水准。”


二、Vue2 与 Vue3 生命周期对比及父子组件执行顺序

1. 生命周期钩子对比表

Vue3 引入了 Composition API,生命周期钩子改为了以 on 开头的函数,并移除了 beforeDestroydestroyed

阶段 Vue2 (Options API) Vue3 (Composition API) 作用及常见用途
创建 beforeCreate - (用 setup 代替) 实例初始化之后。
created - (用 setup 代替) 实例创建完成,可访问数据。适合初始化非 DOM 数据
挂载 beforeMount onBeforeMount DOM 挂载前。
mounted onMounted DOM 挂载完成。适合操作 DOM、发起网络请求
更新 beforeUpdate onBeforeUpdate 数据更新,DOM 重新渲染前。
updated onUpdated DOM 更新完成。避免在此修改状态(易死循环)
卸载 beforeDestroy onBeforeUnmount 实例销毁前。适合清除定时器、解绑事件
destroyed onUnmounted 实例销毁后。
缓存 activated onActivated <keep-alive> 缓存的组件激活时调用。
deactivated onDeactivated <keep-alive> 缓存的组件停用时调用。

2. 核心钩子能做些什么?

  • created / setup: 尽早发起网络请求(API Call),初始化不需要操作 DOM 的变量。
  • mounted: 涉及 DOM 操作的插件初始化(如:ECharts、Swiper、Map)。
  • beforeUnmount: 非常关键!必须在这里销毁定时器 (setInterval)、取消全局事件监听 (window.addEventListener),否则会导致内存泄漏
  • activated: 在被缓存的页面中,如果你希望用户每次切回该页面都刷新数据,请在此执行。

3. 父子组件生命周期执行顺序

Vue 的核心逻辑是:父组件先开始,子组件先结束;父组件先挂载,子组件先完成。

A. 初次挂载过程

  1. beforeCreate / setup
  2. created
  3. beforeMount
  4. beforeCreate / setup
  5. created
  6. beforeMount
  7. mounted (子组件先挂载完成)
  8. mounted (父组件最后挂载完成)

B. Keep-Alive 激活与停用

当父子组件都被 <keep-alive> 包裹时:
首次进入

  1. 父 beforeMount

  2. 子 beforeMount

  3. 子 mounted

  4. 子 activated

  5. 父 mounted

  6. 父 activated

离开组件 (切换到其他页面)

  1. 子 deactivated

  2. 父 deactivated

注意:即使是离开,也是父级先感知到失活。

再次进入 (命中缓存)

  1. 父 activated

  2. 子 activated

注意:命中缓存时,逻辑通常是从父到子依次激活。

C. 数据更新过程

  1. beforeUpdate
  2. beforeUpdate
  3. updated
  4. updated

D. 销毁过程

  1. beforeUnmount
  2. beforeUnmount
  3. unmounted
  4. unmounted

总结规律:父组件总是最先触发“开始”动作,但最后触发“完成”动作。子组件则包裹在父组件的生命周期之内执行。

三、 Vue 响应式原理深度剖析:Computed & Watch (Vue 2 vs Vue 3)

在 Vue 的响应式系统中,computedwatch 是开发者最常用的两个工具。从 Vue 2 到 Vue 3,虽然用法上保持了延续性,但底层实现从 Object.defineProperty 进化到了 Proxy


1. Vue 2:基于 Watcher 的派发更新

在 Vue 2 中,响应式的核心是 Dep 类和 Watcher 类。所有的响应式行为都由不同类型的 Watcher 驱动。

A Vue 2 中的三种 Watcher

Vue 2 内部统一使用 Watcher 类,通过不同的参数标识来区分功能:

类型 描述
Render Watcher 每个组件对应一个。当数据变化时,触发组件的 re-renderpatch
Computed Watcher 为计算属性创建。具有 Lazy(惰性) 特性,只有在取值且依赖变化时才重新计算。
User Watcher 开发者手动写的 watch。支持 deepimmediate 等配置。

B Computed 的底层原理(Vue 2)

  • 缓存机制:每个 Computed 属性对应一个 Watcher,内部有一个属性 dirty
  • 初次访问dirtytrue,执行求值函数,计算完成后将 dirty 置为 false
  • 依赖收集:计算属性执行时,会访问响应式数据,从而让 Computed Watcher 订阅该数据的 Dep
  • 依赖触发:当数据改变时,通知 Computed Watcher,它并不立即重新计算,而是将 dirty 设为 true。此时,它会通知 Render Watcher 重新渲染。渲染时再次读取 Computed,发现 dirtytrue,从而获取新值。

2. Vue 3:基于 Effect 的响应式系统

Vue 3 彻底重构了响应式,不再使用 Watcher 类,而是引入了更轻量、更强大的 Effect(副作用函数) 概念。

A Vue 3 中的三种 Effect

Vue 3 的底层核心是 ReactiveEffect 类,所有的响应式行为都是一个 Effect。

类型 描述
Component Effect 负责组件渲染。Vue 3 的渲染函数包裹在 effect 中。
Computed Effect computed() 返回的是一个只读的响应式 ref。内部封装了一个 effect
Watch Effect 开发者使用的 watchwatchEffect

B Vue 3 底层源码剖析

(1)Computed 实现步骤
  1. 创建 Effect:调用 computed(getter) 时,内部会创建一个 ReactiveEffect 实例。
  2. 调度器(Scheduler):这是关键。Computed 的 Effect 拥有一个定制的调度器。当依赖变动时,不执行 getter,而是将 _dirty 设为 true,并触发 triggerRefValue(this)
  3. 取值逻辑
    • 访问 .value 时,触发 trackRefValue(收集依赖)。
    • 如果 _dirtytrue,执行 effect.run() 重新求值并关闭 _dirty
(2)Watch 实现步骤
  1. 标准化 Source:将输入的监视对象(ref, reactive, function)转化为一个 getter 函数。
  2. 定义 Job:创建一个调度任务 job,内部执行用户的 callback
  3. 创建 Effect:实例化 ReactiveEffect,并将 job 作为调度器。
  4. 执行
    • 如果是 immediate: true,立即执行。
    • 正常情况下,当依赖数据变化,调度器 job 被触发,执行回调。

3. Vue 2 vs Vue 3 深度对比

A 响应式劫持手段

  • Vue 2: 使用 Object.defineProperty。缺点是无法监听对象属性的添加/删除,以及数组索引的直接修改(需要特殊 Hack)。
  • Vue 3: 使用 ES6 Proxy。可以完美拦截对象的所有操作(deletehas 等)以及数组的所有变化,且性能更优(按需递归)。

B 依赖收集模型

  • Vue 2 (Dep-Watcher)
    D e p → [ W a t c h e r 1 , W a t c h e r 2 ] Dep \rightarrow [Watcher1, Watcher2] Dep[Watcher1,Watcher2]
    每个属性对应一个 Dep 实例,里面存储着订阅它的 Watcher
  • Vue 3 (WeakMap-Map-Set)
    使用全局统一的 targetMap 存储结构:
    WeakMap { target -> Map { key -> Set [effects] } }
    这种结构不需要在对象上挂载 Dep 实例,更加解耦。

4. 总结:如何选择 computed 和 watch?

维度 Computed (计算属性) Watch (侦听器)
核心目的 派生出新值,简化模板逻辑。 执行副作用操作(API调用、DOM修改)。
缓存 有缓存。只有依赖变了才会重算。 无缓存。变化即执行。
异步 不支持异步操作。 支持异步操作。
底层实现 惰性 Effect / Dirty 标记位。 立即/调度 Effect。

💡 一句话建议:

如果一个值需要经过其他数据计算得出,用 computed;如果你需要在某个数据变化时去“干一件事”(如发请求、存本地存储),用 watch


Vue 2 和 Vue 3 虽然在底层实现上从“Watcher 模式”转向了“Effect 模式”,但其 依赖收集(Track)与派发更新(Trigger) 的核心思想是一脉相承的。

四、模板编译过程

从 Vue 2 到 Vue 3,模板编译的底层逻辑发生了巨大变化。简单来说,模板编译(Compile) 的过程就是将 HTML 字符串转换成一个返回虚拟 DOM(VNode)的 JavaScript 函数。

在 Vue 中,将 template 编译为 render 函数是提升运行时性能的关键。虽然 Vue 2 和 Vue 3 的基本思路一致(解析 -> 转化 -> 生成),但在实现细节和编译优化上,Vue 3 有了质的飞跃。


1. Vue 2 的编译过程:经典的“三步走”

Vue 2 的编译器(vue-template-compiler)逻辑相对线性:

A. 解析阶段 (Parse)

  • 利用正则匹配将模板字符串转换成 抽象语法树 (AST)
  • 识别指令(v-if, v-for)、属性、事件绑定。
  • 生成一个描述层级的 JavaScript 对象。

B. 优化阶段 (Optimize)

这是 Vue 2 特有的步骤。编译器会 深度遍历 AST,检测出其中的 静态节点 (Static Root)

  • 目的:如果一个节点及其子节点永远不会改变(如纯文本),Vue 会将其打上静态标记
  • 好处:在后续补丁(Patch)过程中,直接跳过这些静态树,减少对比开销。

C. 生成阶段 (Generate)

将 AST 转换成一段形式如 with(this){ return _c('div', { attrs: {"id": "app"} }, [...]) }渲染函数字符串,最后通过 new Function() 转化为真正的函数。


2. Vue 3 的编译过程:工业级的重构

Vue 3 的编译器(@vue/compiler-dom)变得模块化且异常强大,过程分为:

A. 解析 (Parse)

生成 AST。与 Vue 2 不同,Vue 3 的解析器更接近原生 HTML 解析逻辑,容错性更好。

B. 转换 (Transform)

这是 Vue 3 的核心。它不再只是简单地标记静态节点,而是进行 “预分析”

  • 静态提升 (Static Hoisting):将静态节点提升到渲染函数之外,只创建一次,重复使用。
  • 属性压缩:合并重复的类名、样式。

C. 生成 (Codegen)

生成高度优化的代码。Vue 3 舍弃了 with 语句,生成的代码更易于 JS 引擎优化(JIT 友好)。


3. Vue 3 相比 Vue 2 的重大优化

Vue 3 的编译优化被统称为 “编译时驱动的运行时优化”

A. PatchFlag (静态标记)

在 Vue 2 中,即使只有一个动态绑定,diff 算法也会全量对比整个组件树。
Vue 3 在生成的 render 函数中加入了 PatchFlag

  • createVNode 时,会给动态节点打上一个位掩码数字(例如 1 代表文本动态,2 代表 class 动态)。
  • 结果:diff 算法变成“靶向更新”,只对比有标记的属性,性能提升数倍。

B. HoistStatic (静态提升)

  • Vue 2:每次渲染都会重新创建静态节点的虚拟 DOM。
  • Vue 3:将不含变量的节点提升到 render 函数外部声明。
  • 结果:内存占用降低,多次渲染时直接复用同一个对象。

C. CacheHandlers (事件监听器缓存)

  • Vue 2:每次渲染都为元素重新绑定一个新的内联函数。
  • Vue 3:会自动缓存事件处理函数。
  • 结果:避免了因函数引用变化导致的子组件不必要更新。

D. Block Tree (块级树)

Vue 3 引入了 Block 的概念。在 v-ifv-for 之外的区域被视为一个稳定的“块”。

  • 结果:diff 算法从“深度遍历整个虚拟 DOM 树”进化为“遍历一个平坦的动态节点数组”,完全跳过了静态结构。

4. Vue 2 与 Vue 3 静态优化对比表

虽然 Vue 2 开启了静态优化的先河,但它存在明显的局限性,这也是 Vue 3 改进的重点:

优化维度 Vue 2 的做法 Vue 3 的做法
颗粒度 “全家桶”式:标记逻辑以整个节点或子树为单位。要么整个子树是静态的(直接跳过),要么就是完全动态的(全量对比)。 “靶向”式:利用 PatchFlag,即使节点是动态的,也能精准标记具体哪个属性(如:仅文本、仅类名或仅样式)是动态的,实现属性级别的更新。
内存占用 每次组件重新渲染时,虽然 Patch 阶段会跳过对比,但依然会重复创建静态节点的虚拟 DOM 对象,造成不必要的内存开销。 静态提升 (Hoisting):将静态节点及其属性定义移出渲染函数之外。全局只在初始化时创建一次对象,后续渲染直接复用引用,内存更加精简。
动态节点里的静态部分 无法处理:如果一个 div 内部包含一个动态的 span,那么整个 div 及其子节点都无法被标记为静态,必须参与全量 Diff。 Block Tree (块级树):引入“块”的概念。动态节点会被扁平化收集到 dynamicChildren 数组中。Diff 时只遍历该数组,完全忽略中间夹杂的静态结构。

总结对比

特性 Vue 2 Vue 3
Diff 方式 全量 Diff (递归整个树) 靶向 Diff (基于 PatchFlag)
静态内容 仅在补丁阶段跳过 静态提升,直接不参与重新创建
事件绑定 每次渲染重新生成 事件缓存 (Cache Handlers)
代码体积 较难进行 Tree-shaking 模块化设计,支持 Tree-shaking

Vue 3 的核心哲学是能在编译时确定的事情,绝不留到运行时去做。 这使得它在处理大型复杂应用时,性能表现远超 Vue 2。

五、 Vue3 核心升级深度解析

Vue3 并不是简单的版本升级,而是从底层架构到开发模式的一次全面重构。可以从设计理念、底层原理、开发模式、以及性能优化四个维度深入理解。


1. 响应式系统的升级(底层核心)

这是 Vue3 最重大的改变。Vue2 使用的是 Object.defineProperty,而 Vue3 改用了 ES6 的 Proxy

  • Vue2 的局限:
    • 无法检测到对象属性的新增或删除
    • 数组下标的直接修改和长度变化监听不到(需要特意重写数组方法)。
    • 深度嵌套的对象需要一次性递归到底,性能开销大。
  • Vue3 的优势:
    • 全方位监听: Proxy 可以直接监听整个对象而非属性,新增、删除属性都能自动触发更新。
    • 原生支持数组: 不需要额外对数组方法进行黑盒重写。
    • 惰性监听(Lazy Initialization): 只有当访问到某个嵌套属性时,才会对该属性进行响应式处理,大大提升了初始渲染性能。

2. 组合式 API (Composition API)

Vue2 采用的是 Options API(选项式),Vue3 引入了 Composition API

特性 Vue2 (Options API) Vue3 (Composition API)
逻辑组织 相同功能的代码被拆分在 data, methods, computed 中,散落在各处。 相同功能的逻辑可以组合在一起,甚至抽离成独立的 useXxx 函数。
逻辑复用 主要靠 Mixins。缺点:命名冲突、来源不明(隐式依赖)。 Custom Hooks (Hooks 模式)。优点:来源清晰、类型推导友好、无命名冲突。
TS 支持 对 TypeScript 支持较弱,需要复杂的配置。 原生支持 TS,类型推导非常精准。

面试加分点: 提到 Composition API 解决了“长组件”逻辑碎片化的问题,极大地提高了大型项目的可维护性和代码的可读性。


3. 编译与性能优化

Vue3 在编译器层面做了大量的优化,使得渲染速度提升了 2~3 倍。

  1. 静态提升 (Static Hoisting):
    Vue2 中无论节点是否变化,每次渲染都会重新创建 VNode。Vue3 会将那些永远不会变的静态节点提升到渲染函数之外,只在初始化时创建一次。
  2. 补丁标记 (Patch Flag):
    Vue3 给包含动态内容的 VNode 加上了特定的标记(如:只有 class 变,或只有 text 变)。在 Diff 算法阶段,Vue3 只对比有标记的节点,不再全量对比。
  3. 缓存事件处理函数 (Cache Handlers):
    默认情况下,Vue 会认为绑定事件的函数是动态的(每次渲染都可能变)。Vue3 会将其缓存起来,避免不必要的组件子树更新。

4. 源码体积与构建 (Tree-shaking)

  • 按需编译: Vue2 的很多全局 API 是直接挂载在原型上的,即使不用,打包时也会包含在内。
  • Tree-shaking 支持: Vue3 进行了重构,将 API 模块化。如果你没用到 watchcomputed,打包工具(如 Vite/Webpack)就会自动剔除这些代码。这意味着最终生成的 Bundle 体积更小。

5. 其他新特性与内置组件

Vue3 引入了几个解决开发痛点的内置组件:

  • Fragment(碎片): 支持多个根节点,减少了不必要的 DOM 层级(不用再为了满足 Vue2 规则强行套一层 div)。
  • Teleport(传送门): 可以将子组件(如 Modal 弹窗)挂载到 body 或其他指定的 DOM 节点上,解决了 CSS 样式嵌套导致的层级/遮挡问题。
  • Suspense: 处理异步组件的加载状态,提供类似 Loading 的优雅过渡方案。

💡 总结:Vue3 面试回答口诀 (SOP)

  1. 底层看 Proxy: 解决了 Vue2 响应式的各种“坑”,监听更全面,性能更好。
  2. 模式看 Composition: 解决了逻辑复用难、代码维护难的问题,TS 支持更完美。
  3. 编译看静态提升: 通过 Patch Flag 等手段让 Diff 算法变“聪明”了,非全量对比。
  4. 工程看 Tree-shaking: 模块化设计让体积更轻量。

更多推荐