Vue2与Vue3的差异

1.底层响应式原理不同

1. Vue2

Vue2使用的是Object.defineProperty()来劫持各个属性的setter/getter,在数据发生变化的时候通知订阅者更新视图。 缺点:

  1. 无法检测到对象的属性添加和删除
  2. 无法检测到数组的内部变化,因此Vue2通过重写数组方法来实现数组的响应式
  3. 需要遍历整个对象,如果对象嵌套过深,需要递归遍历,性能会下降

2 Vue3

Vue3使用的是Proxy来劫持整个对象,从而实现响应式。Proxy可以直接监听对象和数组的变化,不需要遍历整个对象,性能会更好。 后续官方提出了ref API,可以监听基本数据类型的变化。

  • 引用数据类型:Proxy
  • 基本数据类型:使用对象的属性来监听------> RefImpl

优点:

  1. 可以监听到对象和数组的变化
  2. 可以监听到对象属性的添加和删除
  3. 浅层监听,只有当对象的深层属性被使用到时才会递归遍历监听。减少了性能开销。

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 插件实现模板内表达式类型检查。

相关推荐
小兵张健25 分钟前
运用 AI,看这一篇就够了(上)
前端·后端·cursor
不怕麻烦的鹿丸42 分钟前
node.js判断在线图片链接是否是webp,并将其转格式后上传
前端·javascript·node.js
vvilkim1 小时前
控制CSS中的继承:灵活管理样式传递
前端·css
南城巷陌1 小时前
Next.js中not-found.js触发方式详解
前端·next.js
拉不动的猪2 小时前
前端打包优化举例
前端·javascript·vue.js
Bigger2 小时前
Tauri(十五)——多窗口之间通信方案
前端·rust·app
倔强青铜三2 小时前
WXT浏览器插件开发中文教程(3)----WXT全部入口项详解
前端·javascript·vue.js
Aphasia3112 小时前
快速上手tailwindcss
前端·css·面试
程序员荒生2 小时前
基于 Next.js 搞定个人公众号登陆流程
前端·微信·开源
deckcode3 小时前
css基础-选择器
前端·css