1.底层响应式原理不同
1. Vue2
Vue2使用的是Object.defineProperty()来劫持各个属性的setter/getter,在数据发生变化的时候通知订阅者更新视图。 缺点:
- 无法检测到对象的属性添加和删除
- 无法检测到数组的内部变化,因此Vue2通过重写数组方法来实现数组的响应式
- 需要遍历整个对象,如果对象嵌套过深,需要递归遍历,性能会下降
2 Vue3
Vue3使用的是Proxy来劫持整个对象,从而实现响应式。Proxy可以直接监听对象和数组的变化,不需要遍历整个对象,性能会更好。 后续官方提出了ref API,可以监听基本数据类型的变化。
- 引用数据类型:Proxy
- 基本数据类型:使用对象的属性来监听------> RefImpl
优点:
- 可以监听到对象和数组的变化
- 可以监听到对象属性的添加和删除
- 浅层监听,只有当对象的深层属性被使用到时才会递归遍历监听。减少了性能开销。
2.详解响应式
1.ref & reactive
ref和reactive是Vue3中用来创建响应式数据的两个API。
- ref:用来创建基本数据类型的响应式数据。我们在使用ref时,需要通过.value来访问和修改数据。但是我们在使用模板时,不需要.value,Vue3会自动帮我们处理。本质上Vue3在编译时会使用一个函数(proxyRefs)来自动解包处理。
- reactive:用来创建引用数据类型的响应式数据。本质上是只监听一层,如果该对象的深层属性被访问到了,就会递归监听,否则不会递归监听。reactive的返回值是一个Proxy对象。
2.toRefs & toRef
toRefs和toRef是Vue3中用来将响应式对象的属性转换为响应式引用的API。
- toRef:将响应式对象的属性转换为响应式引用。可以将Proxy对象的某个属性映射为一个ref对象,在proxy对象中修改该属性,ref对象也会跟着改变。
- toRefs:将响应式对象的属性转换为响应式引用。可以将Proxy对象的全部属性映射为一个ref对象,在proxy对象中修改某个属性,ref对象也会跟着改变。返回一个对象,对象的属性名和proxy对象的属性名相同,属性值是一个ref对象。
3.watch & watchEffect
watch是Vue3中用来监听响应式数据变化的API。
- watch会返回一个停止监听的函数,调用该函数可以停止监听。
- watch第一个参数可以传递ref或者reactive,也可以传递一个getter函数或者ref或者reactive的数组。注意,在监听ref.value或者reactive的属性时,必须使用getter函数。
- onCleanUp 是Vue3中用来清除副作用的API。在watch的回调函数中,可以使用onCleanUp来清除副作用。这个函数会在watch监听的值发生变化,当前回调函数被执行之前执行或者组件销毁时执行。用于清除上一次的副作用。它会被挂载到当前watch回调函数的第三个参数上。同时Vue3也将这个API单独暴路出来,可以单独使用。在Vue3.5+,官方暴露出了
onWatcherCleanup
函数,专门用于在watch回调函数中清除副作用。作用和onCleanUp一样。但是这个函数只能同步使用,不能异步使用。即不能在await之后使用。
watchEffect 是 Vue 3 中引入的一个新特性,它会自动响应式地运行副作用,并且不需要显式指定依赖。是Vue3新增。本质上就是使用了Vue3响应式中的effect函数。
- watchEffect会返回一个停止监听的函数,调用该函数可以停止监听。
- watchEffect的回调函数会在组件渲染时执行一次,然后每次依赖的响应式数据发生变化时执行一次。
- watchEffect的回调函数中,不需要显式指定依赖,它会自动收集依赖。
- watchEffect回调函数会被挂载一个参数,该参数就是
onCleanUp
函数,用于清除副作用。也可以使用onWatcherCleanup
函数。 - 返回一个停止监听的函数,调用该函数可以停止监听。
- 如果需要同步触发watchEffect,可以使用一个新的API
watchSyncEffect
来代替。
4.effect
effect是Vue3中用来创建副作用的API。在Vue3中,effect函数会自动收集依赖,并在依赖发生变化时重新执行副作用。effect函数会返回一个停止监听的函数,调用该函数可以停止监听。
- effect函数的回调函数会在组件渲染时执行一次,然后每次依赖的响应式数据发生变化时执行一次。
- 在Vue3中使用了一个WeakMap 来存储effect函数和响应式数据的关系。当响应式数据发生变化时,会触发effect函数的回调函数。这个map的结构为:
js
Map {
target: Map {
key: Map {
effect1:effect1._trackId,
effect2:effect2._trackId
}
}
}
本质上是一个三级嵌套的Map结构,第一层是响应式数据,第二层是响应式数据的属性,第三层是effect函数。当响应式数据发生变化时,会触发effect函数的回调函数。在effect函数的回调函数中,会调用effect的run方法再次执行对应的getter函数,从而触发响应式数据的变化。
2.渲染优化
1. 编译优化 和 diff算法
Vue3中使用了新的diff算法,叫做Fiber diff算法。Fiber diff算法是一种基于Fiber树的diff算法,它可以将diff过程拆分成多个小任务,然后按照优先级依次执行这些任务,从而实现异步渲染。Fiber diff算法的优点是可以实现异步渲染,提高渲染性能。
1.Vue2 采用 双端比较算法(头尾指针对比),通过四个步骤减少 DOM 操作:**
- 旧头 vs 新头
- 旧尾 vs 新尾
- 旧头 vs 新尾
- 旧尾 vs 新头
若均不匹配,则遍历旧节点查找新头节点位置。
缺陷:
-
全量递归比对整个虚拟 DOM 树,即使某些子树完全静态。
-
对动态绑定的细粒度更新支持不足,容易触发不必要的子组件更新。
-
列表重新排序时,DOM 移动次数可能较多
2.Vue3 的 Diff 优化策略
1.靶向更新(Targeted Updates)
- 补丁标志(Patch Flags) 在编译阶段分析模板,为动态绑定的节点添加标记(如 TEXT、CLASS、PROPS),标记其动态部分类型。 效果:Diff 时只需检查标记的动态属性,跳过静态内容。
js
// 编译后的 VNode(动态 class 和 text)
createVNode("div", {
class: _normalizeClass({ active: isActive }),
text: dynamicText
}, null, 3 /* CLASS, TEXT */); // 补丁标志:3 = 1 (CLASS) + 2 (TEXT)
- 静态提升(Static Hoisting) 将静态节点(无动态绑定)提取到渲染函数外部,避免重复创建 VNode。 效果:减少内存占用和 Diff 时的比对次数。
js
// 静态节点提升到外部
const _hoisted = createVNode("div", null, "Static Content");
function render() {
return (_openBlock(), _createBlock(_hoisted));
}
2. 块树(Block Tree)与动态锚点
-
块(Block) 将模板中的动态节点(含 v-if、v-for 或动态插槽)包裹为「块」,每个块追踪其内部动态子节点。
-
动态锚点 在块内记录动态节点的位置,更新时直接定位到动态部分,跳过静态结构。
效果:Diff 范围从整棵树缩小到块内动态节点,复杂度从 O(n) 降低到 O(动态节点数)。
3. 最长递增子序列(LIS)优化列表更新
-
问题场景:当列表元素顺序变化时,Vue2 的双端比较可能导致多次 DOM 移动。
-
Vue3 解决方案: 在确定新旧子节点映射后,使用 最长递增子序列算法 找到最少的 DOM 移动路径。
-
示例: 新旧子节点索引序列为 [2, 3, 1, 4] → LIS 为 [2, 3, 4],只需移动节点 1 到正确位置。
效果:最小化 DOM 移动次数,提升列表渲染性能。 以下手写实现一个简易的LIS算法:
js
function LIS(arr) {
let len = arr.length
// 1.长度为0直接返回空数组
if(len === 0) return []
// 2.定义一个数组,用来存储最长递增子序列
let LIS = [
[arr[0]]
]
// 3.遍历数组
for(let i = 1; i < len; i++) {
const item = arr[i]
update(item)
}
const update = () => {
// 4.从LIS最后一个元素开始遍历
for(let j = LIS.length - 1; j >= 0; j--) {
const line = LIS[j]
const last = line[line.length - 1]
// 5.如果当前元素大于LIS最后一个元素,则将当前元素添加到LIS中,另外生成一行存储
if(item > last) {
LIS[j + 1] = [...line, item]
return
}
}
// 6.如果遍历完之后,没有找到比当前元素大的,则将当前元素作为第一行存储
LIS[0] = [item]
}
// 最终返回LIS数组中最后一个元素
return LIS[LIS.length - 1]
}
2.生命周期
Vue2 生命周期钩子 | Vue3 生命周期钩子(Options API) | Vue3 Composition API 等效写法 | 触发时机说明 |
---|---|---|---|
beforeCreate | setup() | setup() | 组件实例初始化前(无法访问数据) |
created | setup() | setup() | 组件实例创建完成(可访问数据) |
beforeMount | onBeforeMount | onBeforeMount | 挂载到 DOM 前 |
mounted | onMounted | onMounted | 挂载到 DOM 后 |
beforeUpdate | onBeforeUpdate | onBeforeUpdate | 数据变化导致 DOM 更新前 |
updated | onUpdated | onUpdated | DOM 更新完成后 |
beforeDestroy | onBeforeUnmount(改名) | onBeforeUnmount | 组件卸载前 |
destroyed | onUnmounted(改名) | onUnmounted | 组件卸载后 |
errorCaptured | onErrorCaptured | onErrorCaptured | 捕获子孙组件错误时 |
renderTracked(新增) | onRenderTracked | onRenderTracked | 调试用:响应式依赖被追踪时 |
renderTriggered(新增) | onRenderTriggered | onRenderTriggered | 调试用:响应式依赖触发更新时 |
3.其他优化
1.写法优化:Composition API 的革新
1. 与 Options API 对比
特性 | Options API (Vue2) | Composition API (Vue3) |
---|---|---|
代码组织 | 按选项(data、methods)分离 | 按功能逻辑聚合 |
逻辑复用 | Mixins(命名冲突风险) | 自定义 Hook 函数(函数式复用) |
TypeScript 支持 | 类型推导较弱 | 原生类型支持完善 |
代码可读性 | 简单场景直观,复杂场景分散 | 复杂场景更易维护 |
2. Composition API 核心优势
-
逻辑复用:将逻辑封装为自定义 Hook(替代 Mixins)。
-
代码组织:相关逻辑集中管理(如将数据请求与状态管理放在一起)。
-
更好的类型推导:函数式写法天然适合 TypeScript。
js
// Composition API 示例:计数器逻辑复用
import { ref } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
const increment = () => count.value++;
return { count, increment };
}
// 组件中使用
export default {
setup() {
const { count, increment } = useCounter(0);
return { count, increment };
}
}
2.组件优化:更灵活强大的组件能力
1. Fragment(多根组件)
-
Vue2 限制:组件模板必须单根节点,导致冗余包裹元素。
-
Vue3 改进:支持多根节点,减少 DOM 层级。
html
<!-- Vue3 合法模板 -->
<template>
<header>导航栏</header>
<main>内容区</main>
<footer>页脚</footer>
</template>
2. Teleport(传送门)
-
功能:将组件渲染到 DOM 任意位置(如全局弹窗)。
-
场景:解决样式隔离或 DOM 嵌套限制问题。
html
<template>
<teleport to="body">
<div class="modal">模态框内容</div>
</teleport>
</template>
3. Suspense(异步组件)
-
功能:优雅处理异步组件加载状态。
-
场景:数据请求、动态导入组件时的加载中/错误状态。
html
<template>
<suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</suspense>
</template>
3.Tree-shaking 支持:按需打包优化
1. 实现机制 模块化拆分:将 Vue 功能拆分为独立模块(如 v-model、transition、keep-alive)。
ES Module 输出:构建工具(如 Webpack、Rollup)可静态分析依赖关系。
2. 效果对比
场景 | Vue2 | Vue3 |
---|---|---|
全量引入 | ~20KB (全量) | ~12KB (核心运行时) |
使用部分功能 | 全量包含 | 仅打包使用到的模块 |
4.TypeScript 支持:全面拥抱类型系统
1. 核心改进 源码重写:Vue3 使用 TypeScript 编写,提供完整类型定义。
组合式 API 友好:函数式 API 天然适合类型推导。
模板类型检查:配合 Volar 插件实现模板内表达式类型检查。