还在死记 Vue 2 和 Vue 3 的区别?12个核心模块对比,让你彻底告别面试难题!

以往文章

📚 JS 基础笔记:前端面试复习笔记:JS 基础核心知识点梳理

📚 浏览器原理:面试被问浏览器原理就慌?这份笔记帮你吃透10 个核心模块

📚 CSS基础:10 个基础模块笔记(Flex/Sticky/BFC 全拆解 + 陷阱提示)

📚 吃透前端项目优化系列(一):从构建提速开始,分节拆解工程化方案

📚 吃透前端项目优化系列(二):首屏渲染优化 +性能指标拆解

📚 吃透前端项目优化系列(三):Webpack 核心基础全解析

📚 吃透前端项目优化系列(四):Webpack 进阶

📚 一篇通关Vue(一):Vue 基础核心 5 大模块全解析

引言

还在为 Vue 2 和 Vue 3 的选择题发愁?Composition API 到底比 Options API 好在哪?Proxy 响应式为什么比 Object.defineProperty 更快?

这篇笔记帮你一站式解决所有疑问。我们不止步于表面差异,而是深入虚拟 DOM、Diff 算法、组件通信、路由和状态管理等 12 个核心模块,帮你从 "是什么" 到 "为什么" 彻底吃透两大版本的本质区别,让你在面试和实战中都能游刃有余。

开始

本节是 一篇通关 Vue(二) ,聚焦两大版本的深度对比:

序号 Vue 专题 本节聚焦 核心价值
1 Vue 基础核心 已更(系列一) 掌握核心概念与基础逻辑
2 Vue 2 vs Vue 3 对比 本节内容 深入辨析版本差异,掌握进阶原理

本节将围绕以下 12 个模块,全面对比 Vue 2 与 Vue 3 的核心差异:

  1. VUE2和VUE3的区别:宏观概览与核心变化

  2. VUE2和VUE3的虚拟DOM:底层节点结构的异同

  3. VUE2和VUE3的diff算法:更新效率的关键差异

  4. VUE2和VUE3的组件通信方式:API 变化与新增方案

  5. VUE2和VUE3获取实例对象属性的差异this 指向与 API 调用

  6. VUE2和VUE3的directives:生命周期钩子的变化

  7. VUE2和VUE3的$nextTick:实现原理的微调

  8. VUE2和VUE3的slot插槽:作用域与写法的统一

  9. VUE2和VUE3的路由Router:集成与 API 差异

  10. Vue2和Vue3的状态管理:Vuex 的演进与 Pinia 的兴起

  11. VUE2和VUE3的Computed 和 watch:响应式 API 的精细化

  12. VUE2和VUE3的内置指令v-forv-if 的优先级等

介绍

1:VUE2和VUE3的区别

一、性能与底层优化

  • 性能提升

    • 打包体积减少:Vue3 通过 Tree-shaking 移除未使用的代码(如未使用的生命周期、API),相比 Vue2 打包体积平均减少 40%+。

    • 渲染速度提升:重写虚拟 DOM,编译时对静态节点标记「静态提升」(避免重复对比),动态节点通过「补丁标志」精准定位更新位置,渲染性能提升 55%+,更新性能提升 133%+。

    • 内存占用减少:优化了响应式系统的内存管理,避免不必要的依赖追踪。

    • 注意:Vite 是构建工具(并非 Vue3 的特性),但 Vue3 的 ES 模块设计更适配 Vite 的「原生 ESM 按需加载」,两者配合提升开发体验,这点需区分开。

  • 响应式系统重构

    • Vue2 基于Object.defineProperty,存在缺陷:无法监听数组索引变化、对象新增 / 删除属性、Map/Set 等数据结构。

    • Vue3 改用Proxy,原生支持监听:对象新增 / 删除属性、数组索引 / 长度变化、Map/Set 等集合类型,且无需递归初始化所有属性(懒代理),性能更优。


二、语法与 API 设计

  • 组合式 API(Composition API)

    • 并非「抛弃选项式 API」 :Vue3 完全兼容 Vue2 的选项式 API(Options API),只是新增并推荐组合式 API(通过setup函数或<script setup>语法)。

    • 优势 :解决选项式 API 中「逻辑碎片化」问题(如一个功能的代码分散在datamethodswatch中),支持逻辑复用(通过自定义 Hook),更适合大型项目。

  • TypeScript 支持

    • Vue2 对 TS 支持较弱(需通过vue-class-component等库补充);Vue3 本身用 TS 重写,类型定义更完善,组合式 API 与 TS 结合更自然,开发时可获得完整的类型提示。

三、核心功能与组件

  • 新增内置组件

    • <teleport> :将组件内容「传送」到指定 DOM 节点(如 body),解决模态框嵌套在父组件中样式受限制的问题。

    • <Fragment> :允许组件模板有多个根节点(无需像 Vue2 那样用一个空 div 包裹),使用时直接写多个根元素即可(无需显式声明<Fragment>)。

    • <Suspense> :配合异步组件或异步数据加载,在等待时显示「加载态」,加载完成后显示内容(需与defineAsyncComponent或异步 setup 配合)。

  • 非兼容性变更(核心)

    • 全局 API 重构

      • Vue2 通过Vue构造函数调用全局 API(如Vue.component()Vue.use());

      • Vue3 通过createApp()创建应用实例,全局配置(如全局组件、插件)绑定在实例上(app.component()app.use()),避免全局污染。 例:const app = createApp(App); app.mount('#app')

    • Provide/Inject 增强

      • Vue2 中只能在选项式 API 的provide/inject中使用;

      • Vue3 中可在组合式 API 的setup中通过provide()/inject()使用,支持传递响应式数据,且注入时可指定默认值。并非「任意组件可使用」,仍基于组件树层级传递。

    • v-model 语法统一

      • Vue2v-model.sync修饰符功能重叠;

      • Vue3 移除.sync,统一用v-model,支持通过modelValue自定义绑定名,且一个组件可绑定多个v-model(如<MyComp v-model:name="name" v-model:age="age" />)。

    • 事件总线(EventBus)

      • Vue2 常用new Vue()作为事件总线;

      • Vue3 移除内置事件总线,推荐用第三方库(如mitt)实现。

    • 状态管理

      • Vuex(Vue2) 被 Pinia 取代,Pinia 简化了 API(无需mutations,直接通过actions修改状态),天然支持 TS,且更轻量。
    • 组件事件声明

      • Vue3 中组件触发的自定义事件必须在emits选项中声明(如emits: ['change']),增强代码可读性和类型检查,避免与原生事件混淆。
    • 函数组件与异步组件

      • 函数组件 :Vue2 中通过functional: true声明;Vue3 中直接定义为纯函数(无需选项),且需用defineComponent包裹以获得类型支持。
      • 异步组件 :Vue2 中直接返回() => import(...);Vue3 中必须用defineAsyncComponent包裹(如const AsyncComp = defineAsyncComponent(() => import('./Comp.vue')))。
    • h 函数导入方式

      • Vue2render函数自动注入hcreateElement);

      • Vue3h需显式导入(import { h } from 'vue')。

    • attrs透传

      • Vue2attrs不包含classstyle;

      • Vue3 中包含classstyle,由开发者决定在哪个元素上应用(通过v-bind=attrs

    • 生命周期钩子重命名

      • Vue2beforeDestroyVue3beforeUnmountdestroyedunmounted,其他生命周期逻辑不变。
    • 其他变更

      • 过滤器(filter) 被移除:推荐用计算属性或方法替代。

      • 自定义指令钩子重命名 :Vue2 的bindbeforeMountinsertedmountedupdatebeforeUpdatecomponentUpdatedupdatedunbindunmounted

      • v-if与v-for优先级 :Vue2 中v-forv-if优先级高;Vue3 中v-ifv-for优先级高(避免循环中判断,推荐在父级用v-if)。

        分类对比维度 Vue2 Vue3
        一、性能与底层优化
        打包体积 无 Tree-shaking,未使用代码无法剔除 支持 Tree-shaking,打包体积平均减少 40%+
        渲染 / 更新性能 全量虚拟 DOM 对比,静态节点参与 Diff 静态节点「静态提升」(跳过 Diff),动态节点通过「补丁标志(PatchFlag)」精准更新,渲染性能提升 55%+,更新性能提升 133%+
        响应式系统 基于 Object.defineProperty,缺陷: 1. 无法监听数组索引 / 对象新增属性 2. 需递归初始化所有属性(性能损耗) 基于 Proxy,优势: 1. 原生支持监听数组索引、对象增删、Map/Set 等 2. 懒代理(无需提前递归初始化,性能更优)
        二、语法与 API 设计
        核心 API 风格 选项式 API(Options API): 逻辑分散在 data/methods/watch 新增组合式 API(Composition API): 通过 setup<script setup> 聚合逻辑,支持自定义 Hook 复用,兼容选项式 API
        TypeScript 支持 支持较弱,需依赖第三方库(如 vue-class-component 原生 TS 重写,类型定义完善,组合式 API 与 TS 结合自然,类型提示完整
        三、核心功能与组件
        新增内置组件 无特殊组件,需手动处理多根节点(包裹空 div) 新增: - <teleport>:传送内容到指定 DOM 节点 - <Fragment>:支持多根节点(无需包裹) - <Suspense>:异步加载时显示加载态
        全局 API 调用方式 基于 Vue 构造函数(如 Vue.component()),全局污染风险 基于 createApp() 创建实例(如 app.component()),配置绑定到实例,避免全局污染
        v-model 语法 .sync 功能重叠,单个组件仅支持一个 v-model 移除 .sync,统一用 v-model,支持自定义绑定名(如 v-model:name),可绑定多个
        事件总线(EventBus) 基于 new Vue() 实现 移除内置支持,推荐用第三方库(如 mitt
        状态管理 依赖 Vuex,需通过 mutations 修改状态 推荐 Pinia(Vuex 继任者): 1. 无需 mutations,直接在 actions 中修改 2. 天然支持 TS,更轻量
        组件事件声明 无需声明,可能与原生事件混淆 需在 emits 选项中声明(如 emits: ['change']),增强可读性和类型检查
        四、非兼容性变更
        生命周期钩子 beforeDestroydestroyed 重命名为 beforeUnmountunmounted(逻辑不变)
        过滤器(filter) 支持过滤器(如 `{{ msg format }}`)
        自定义指令钩子 bind/inserted/update/componentUpdated/unbind 重命名为 beforeMount/mounted/beforeUpdate/updated/unmounted
        v-if 与 v-for 优先级 v-for 优先级更高(可能导致无效循环) v-if 优先级更高(推荐在父级判断,避免循环内条件)
        attrs 透传 不包含 classstyle 包含 classstyle,由开发者决定应用位置(v-bind="$attrs"

3:VUE2和VUE3的虚拟DOM

一、虚拟 DOM(Virtual DOM)的核心概念

虚拟 DOM 是用 JavaScript 对象模拟真实 DOM 结构的抽象表示,它包含了真实 DOM 的标签名、属性、子节点等关键信息,本质是 "对 DOM 的轻量描述"。

核心价值:作为真实 DOM 和业务逻辑之间的 "中间层",避免直接频繁操作真实 DOM(因 DOM 操作代价高,易引发重绘 / 回流),通过 "批量对比更新" 提升性能。


二、虚拟 DOM 的核心优势

1. 性能优化:减少无效 DOM 操作
  • 真实 DOM 的问题:真实 DOM 不仅包含标签、属性,还内置了大量浏览器特性(如事件监听、样式计算等),直接操作(尤其是频繁操作)会触发频繁的重绘 / 回流,导致性能损耗。

  • 虚拟 DOM 的优化逻辑

    1. 先在 JavaScript 中对虚拟 DOM 进行修改(如增删节点、修改属性),这一步是 "内存级操作",代价极低;
    2. 通过 "Diff 算法" 对比修改前后的虚拟 DOM,找出 "最小变更集"(即只需要更新的部分);
    3. 只将 "最小变更集" 映射到真实 DOM 并执行操作,大幅减少 DOM 操作次数(例如:10 次修改可能只需要 1 次真实 DOM 更新)。
2. 跨平台能力:一次描述,多端渲染

虚拟 DOM 是 "平台无关的抽象描述",同一虚拟 DOM 对象可被不同的 "渲染器" 转换为对应平台的视图:

  • 浏览器端:渲染为真实 DOM 节点;

  • 移动端(如 Vue Native) :渲染为原生控件(如 iOS 的 UIView、Android 的 TextView);

  • 服务端(SSR) :渲染为字符串(HTML 片段);

  • 其他平台:如 Canvas、SVG 等。

Vue3 的 "自定义渲染器 API"(createRenderer)正是基于这一特性,允许开发者为特定平台编写渲染逻辑。


三、虚拟 DOM 的结构(Vue2 vs Vue3 对比)

虚拟 DOM 的结构没有统一标准,但 Vue 中通常包含描述节点的核心属性。Vue2 和 Vue3 的虚拟 DOM 结构有细微差异:

1. Vue2 的 VNode 结构(简化版)
JavaScript 复制代码
{
    // 核心属性(用户可配置)
    tag: 'div', // 标签名或组件名(如 'div' 或 'MyComponent')
    data: { // 节点属性/数据
        class: 'container',
        style: {
            color: 'red'
        },
        attrs: {
            id: 'app'
        }, // 原生HTML属性(非Vue绑定)
        props: {
            msg: 'hello'
        }, // 组件props
        on: {
            click: handleClick
        }, // 事件监听
        directives: [{ // 自定义指令
            name: 'focus',
            value: true
        }],
        // ...其他特殊属性(如slot、scopedSlots等)
    },
    children: [ // 子节点
        {
            tag: 'span',
            children: 'Hello'
        },
        'World' // 文本节点
    ],
    key: 'unique-key', // 用于优化Diff算法
​
    // 内部属性(框架使用,用户一般不直接操作)
    context: VueInstance, // 渲染上下文(当前Vue实例)
    componentOptions: { // 组件选项(仅组件节点有)
        Ctor: VueComponent, // 组件构造函数
        propsData: {
            msg: 'hi'
        },
        listeners: {
            click: fn
        },
        children: [...] // 组件子节点
    },
    componentInstance: null, // 组件实例(延迟创建)
    parent: VNode, // 父VNode
    raw: false, // 是否为原始HTML(非Vue解析)
    isStatic: false, // 是否为静态节点(优化用)
    isRootInsert: true, // 是否为主根节点
    isComment: false, // 是否为注释节点
    isCloned: false, // 是否为克隆节点
    // ...其他内部属性
}
2. Vue3 的 VNode 结构(简化版,更简洁)

Vue3 对 VNode 结构进行了优化,减少冗余属性,增加了对 Fragment、Teleport 等新特性的支持,结构更扁平,减少了嵌套层级。

JavaScript 复制代码
{
    // 核心属性(用户可配置)
    type: 'div', // 节点类型(标签名、组件、Fragment等)
    props: { // 节点属性(合并了Vue2的props、attrs、on等)
        class: 'container',
        style: {
            color: 'red'
        },
        id: 'app', // 原生属性(等同于Vue2的attrs)
        msg: 'hello', // 组件props
        onClick: handleClick // 事件(直接用on+首字母大写)
    },
    children: [ // 子节点
        {
            type: 'span',
            children: 'Hello'
        },
        'World'
    ],
    key: 'unique-key',
​
    // 新增的编译时优化标记(Vue3特有)
    patchFlag: 1, // 标记节点变化类型(二进制标记:1=TEXT, 2=CLASS等)
    dynamicProps: ['class'], // 动态props列表,用于靶向更新
    dynamicChildren: [], // 动态子节点集合
    shapeFlag: 1, // 节点形状标记(1=ELEMENT, 4=TEXT_CHILDREN等)
​
    // 内部属性
    el: null, // 对应的真实DOM元素(延迟挂载)
    anchor: null, // 锚点(用于Fragment等场景)
    parentComponent: null, // 父组件实例
    parentVNode: null, // 父VNode
    component: null, // 组件实例(延迟创建)
    suspense: null, // Suspense组件关联
    dirs: null, // 指令
    transition: null, // 过渡效果
    // ...其他内部属性
}
核心差异点
维度 Vue2 Vue3
类型标识 tag(仅字符串) type(支持字符串、Symbol、组件构造函数等)
属性组织 嵌套在data对象中(分propsattrson等) 扁平化到props对象(直接包含所有属性和事件)
优化机制 无编译时优化标记 patchFlagshapeFlag(二进制标记节点类型,加速 Diff)
组件相关 componentOptionscomponentInstance 简化为component属性
特殊节点支持 有限(如无 Fragment) 直接支持 Fragment、Teleport、Suspense 等

四、常见误区:虚拟 DOM "一定比真实 DOM 快"?

  • 并非绝对:虚拟 DOM 的性能优势体现在 "频繁、复杂的 DOM 操作" 中。对于简单场景(如单次 DOM 修改),直接操作真实 DOM 可能更快(因虚拟 DOM 需要额外的 Diff 计算成本)。

  • Vue 的优化逻辑 :Vue3 通过patchFlag等机制减少 Diff 范围,结合编译时优化(如静态节点提升),进一步缩小了与 "直接操作 DOM" 的性能差距。


五、总结

虚拟 DOM 是 JavaScript 对象对真实 DOM 的抽象,核心价值是通过批量更新和跨平台能力提升开发效率和性能。

Vue2 和 Vue3 的虚拟 DOM 结构相似,但 Vue3 通过type、patchFlag等优化,让 Diff 算法更高效。理解虚拟 DOM 有助于掌握 Vue 等框架的渲染原理,避免陷入 "盲目依赖虚拟 DOM" 的误区 ------ 它的优势在于 "平衡性能和开发效率",而非单纯的 "速度更快"。


3:VUE2和VUE3的diff算法

一、Diff 算法的核心概念

Diff 算法是虚拟 DOM 的核心机制,用于对比新旧虚拟 DOM 的差异,只更新需要变化的真实 DOM 节点。其设计遵循以下原则:

  • 虚拟 DOM 对比 ≠ 完全比对真实 DOM:通过 JS 对象(虚拟 DOM)的轻量对比,避免直接操作昂贵的真实 DOM;

  • 启发式算法:基于两个假设(实际场景中 90% 以上成立)提升性能:

    1. 相同节点 :若两个虚拟 DOM 节点的type(标签名 / 组件)相同,认为它们可能只是属性或内容变化,不会完全替换;
    2. 不跨层级移动 :节点只会在同层级移动,不会跨层级移动(如从div子节点移到span子节点)。

二、Vue2 的 Diff 算法(基于 Snabbdom)

1. 整体策略:同层比较 + 双指针 + key 辅助

Vue2 采用双端比较法(新旧虚拟 DOM 各维护头尾两个指针,向中间移动),通过key识别可复用的节点。

2. 核心流程:
JavaScript 复制代码
// 伪代码示意Vue2的双端比较
function updateChildren(parentElm, oldCh, newCh) {
    let oldStartIdx = 0;
    let oldEndIdx = oldCh.length - 1;
    let newStartIdx = 0;
    let newEndIdx = newCh.length - 1;
​
    // 四指针循环比较
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        if (isSameVnode(oldStartVnode, newStartVnode)) {
            // 头头比较:相同则递归patch子节点
            patchVnode(oldStartVnode, newStartVnode);
            oldStartIdx++;
            newStartIdx++;
        } else if (isSameVnode(oldEndVnode, newEndVnode)) {
            // 尾尾比较
            patchVnode(oldEndVnode, newEndVnode);
            oldEndIdx--;
            newEndIdx--;
        } else if (isSameVnode(oldStartVnode, newEndVnode)) {
            // 头尾比较(移动旧节点到尾部)
            patchVnode(oldStartVnode, newEndVnode);
            parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling);
            oldStartIdx++;
            newEndIdx--;
        } else if (isSameVnode(oldEndVnode, newStartVnode)) {
            // 尾头比较(移动旧节点到头部)
            patchVnode(oldEndVnode, newStartVnode);
            parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm);
            oldEndIdx--;
            newStartIdx++;
        } else {
            // 以上都不匹配:通过key查找可复用节点(哈希表优化)
            const idxInOld = findIndexInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
            if (idxInOld >= 0) {
                const vnodeToMove = oldCh[idxInOld];
                patchVnode(vnodeToMove, newStartVnode);
                oldCh[idxInOld] = null; // 标记为已处理
                parentElm.insertBefore(vnodeToMove.elm, oldStartVnode.elm);
            } else {
                // 无复用节点:创建新节点
                parentElm.insertBefore(createElm(newStartVnode), oldStartVnode.elm);
            }
            newStartIdx++;
        }
    }
​
    // 处理剩余节点(新增或删除)
    if (oldStartIdx > oldEndIdx) {
        // 旧节点处理完,新增新节点
        addVnodes(parentElm, newStartIdx, newEndIdx);
    } else {
        // 新节点处理完,删除旧节点
        removeVnodes(parentElm, oldStartIdx, oldEndIdx);
    }
}
3. 局限性:
  • 全量对比:对所有动态节点(无论是否变化)都进行运行时 Diff;

  • 无编译时优化:无法提前知晓哪些节点会变化,只能依赖运行时对比。


三、Vue3 的 Diff 算法(基于 PatchFlag + 最长递增子序列)

Vue3 对 Diff 算法进行了颠覆性优化,结合编译时分析和运行时优化,大幅减少了不必要的比较:

1. 编译时优化:PatchFlag 与 ShapeFlag
  • PatchFlag(补丁标记) :编译时为动态节点添加二进制标记,指明哪些属性会变化,运行时只比较这些属性:

    JavaScript 复制代码
    // 示例:编译后生成的VNode带有PatchFlag
    createVNode("div", {
        class: "container",
        onClick: handleClick
    }, null, 8 /* PatchFlag: PROPS */ )

    常见的 PatchFlag 值

    • 1:TEXT(文本内容变化)
    • 2:CLASS(类名变化)
    • 4:STYLE(样式变化)
    • 8:PROPS(普通 props 变化)
    • 64:HYDRATE_EVENTS(事件监听变化)
  • ShapeFlag(节点形状标记) :标识节点类型(元素、组件、文本等),加速类型判断:

    JavaScript 复制代码
    // 示例:ShapeFlag二进制表示
    const ELEMENT = 1; // 000001:元素节点
    const STATEFUL_COMPONENT = 4; // 000100:有状态组件
    const TEXT_CHILDREN = 16; // 010000:子节点为文本
2. 运行时优化:最长递增子序列(LIS)

当节点发生顺序变化时,Vue3 通过LIS 算法计算最小移动次数:

css 复制代码
// 示例:新旧子节点顺序变化
旧节点:[A, B, C, D]
新节点:[B, A, D, C]
​
// Vue3通过LIS找到最长不需要移动的序列(如B、D),只移动A和C
3. 核心流程优化:
JavaScript 复制代码
// 伪代码示意Vue3的优化Diff(仅对比有PatchFlag的节点)
function patchElement(n1, n2, parentComponent) {
    const el = n2.el = n1.el; // 复用DOM元素
    const oldProps = n1.props || EMPTY_OBJ;
    const newProps = n2.props || EMPTY_OBJ;
​
    // 1. 处理有PatchFlag的动态props
    if (n2.patchFlag & PatchFlags.PROPS) {
        // 只更新被标记为动态的props
        for (const key in newProps) {
            if (newProps[key] !== oldProps[key]) {
                patchProp(el, key, oldProps[key], newProps[key]);
            }
        }
    }
​
    // 2. 处理子节点
    if (n2.patchFlag & PatchFlags.CHILDREN) {
        patchChildren(n1, n2, el, parentComponent);
    }
}
​
// 处理子节点时,对有key的列表使用LIS优化移动操作
function patchKeyedChildren(c1, c2) {
    // ...双指针比较
​
    // 当发现节点顺序变化时,计算LIS
    const seq = getLongestIncreasingSubsequence(movedIndices);
​
    // 只移动不在最长递增子序列中的节点
}

四、Vue2 与 Vue3 的 Diff 算法核心差异

维度 Vue2 Vue3
优化方式 纯运行时 Diff,依赖 key 编译时 + 运行时结合,通过 PatchFlag 减少比较范围
节点比较 全量比较所有动态节点 仅比较带 PatchFlag 的节点
顺序变化处理 双端比较 + 哈希表查找 双端比较 + 最长递增子序列(LIS)
静态节点处理 每次都参与 Diff 编译时提升(hoistStatic),仅创建一次
特殊节点支持 无特殊优化 直接支持 Fragment、Teleport 等新特性

五、性能对比与最佳实践

  • Vue3 性能优势

    • 编译时优化使静态节点(如纯文本、常量)完全跳过 Diff;

    • PatchFlag 使动态节点的比较范围缩小到 "真正变化的部分"(如仅比较class或style);

    • LIS 算法减少了节点移动的操作次数。

  • 最佳实践

    • 始终为列表项添加唯一 key:帮助 Diff 算法识别可复用节点(Vue3 的 LIS 依赖 key);

    • 减少不必要的动态节点:Vue3 对静态内容自动优化,尽量将不变的部分移出动态渲染;

    • 避免跨层级移动节点:Diff 算法假设节点不跨层级移动,违反此假设会导致性能下降。


六、总结

Vue3 的 Diff 算法通过编译时分析和更高效的运行时策略,将虚拟 DOM 的性能推向了新高度。理解这些差异有助于写出更优化的 Vue 代码,尤其是在处理大型列表或复杂交互时,合理使用key和减少动态节点能显著提升应用性能。


4:VUE2和VUE3的组件通信方式

Vue2 依赖选项式 API(props/$emit/$parent),Vue3 强化组合式 API(defineProps/defineEmits)并优化生态(Pinia 替代 Vuex),核心通信逻辑一致,但写法更简洁、耦合更低。

一、父传子(Parent → Child)

核心逻辑:父主动传递数据 / 内容给子组件,子被动接收。

方式 Vue2 支持 Vue3 支持 说明
props 最基础方式,父通过属性传值,子用 props 声明接收(Vue3 用 defineProps)。
v-model 语法糖(本质是 props + 事件),父用 v-model 绑定值,子通过 modelValue + update:modelValue 同步。
$refs 父通过 ref 获取子组件实例,直接访问子的属性 / 方法(需等子挂载完成)。
插槽(Slot) - 匿名插槽 :父传默认内容; - 具名插槽 :父用 v-slot:name 传指定内容; - 作用域插槽:子传数据给父插槽。

二、子传父(Child → Parent)

核心逻辑:子主动触发事件 / 更新,父监听并响应。

方式 Vue2 支持 Vue3 支持 说明
自定义事件 子用 $emit(Vue2)或 defineEmits(Vue3)触发事件,父用 @事件名 监听。
v-model 语法糖(本质是 props + update 事件),子触发 update:xxx 事件同步父值。
$parent Vue2 可用 this.$parent 直接访问父实例(强耦合,Vue3 移除)。
作用域插槽 子通过 slot-scope(Vue2)或 v-slot(Vue3)向父插槽传数据,父在插槽中接收。

三、跨层级(祖孙 / 多层嵌套)

核心逻辑:跳过中间层传递,避免逐层透传冗余。

方式 Vue2 支持 Vue3 支持 说明
<math xmlns="http://www.w3.org/1998/Math/MathML"> a t t r s / attrs / </math>attrs/listeners - <math xmlns="http://www.w3.org/1998/Math/MathML"> a t t r s ∗ ∗ :父传的非 ' p r o p s ' 属性自动透传给子( V u e 3 包含 ' c l a s s / s t y l e ' ); − ∗ ∗ attrs**:父传的非 `props` 属性自动透传给子(Vue3 包含 `class/style`); - ** </math>attrs∗∗:父传的非'props'属性自动透传给子(Vue3包含'class/style');−∗∗listeners :Vue2 中收集父监听事件,Vue3 已合并到 $attrs
provide / inject 父用 provide 提供数据,子孙用 inject 注入(Vue3 支持响应式传递)。

四、任意组件(兄弟 / 无关联)

核心逻辑:无层级限制,全局或第三方工具实现通信。

方式 Vue2 支持 Vue3 支持 说明
事件总线(EventBus) Vue2 常用 new Vue() 做总线;Vue3 移除内置,推荐用 mitt 库替代。
Pinia / Vuex 全局状态管理库: - Vuex (Vue2 主流); - Pinia(Vue3 推荐,更轻量、支持 TS)。
全局变量 挂载数据到 window 或全局对象(简单场景用,不推荐复杂逻辑)。

五、关键差异(Vue2 vs Vue3)

  • 响应式语法

    • Vue3 用 defineProps/defineEmits 替代 Vue2 的 props/$emit 选项式写法。

    • $parent/$children 等强耦合 API 在 Vue3 中弱化或移除。

  • 语法糖优化

    • Vue3 的 v-model 支持多绑定(v-model:xxx),更灵活。
  • 生态迁移

    • Vuex → Pinia 是趋势;事件总线推荐第三方库(如 mitt)。

5:VUE2和VUE3获取实例对象属性的差异

简单说:

Vue2 是「实例属性随手用」,Vue3 是「实例访问需规范」,核心目的是降低耦合、提升可维护性。

Vue3 对实例属性的访问做了隔离和规范: 避免 this 滥用导致的命名冲突,强制开发者显式获取实例(更符合函数式编程思想)。

推荐用 getCurrentInstance()(组合式 API)或 defineExpose(暴露子组件属性),减少强耦合的实例访问(如 $parent)。

一、Vue2:实例属性「直接挂载」

1. 实例创建与属性挂载
  • 核心逻辑new Vue(options) 创建实例时,会把 datamethodsprops 等直接挂载到实例(this)上,与 Vue 内置方法(如 $emit$nextTick)平级。

  • 示例

    JavaScript 复制代码
    // Vue2 组件
    export default {
        data() {
            return {
                count: 1
            }
        },
        methods: {
            add() {
                this.count++
            }
        },
        mounted() {
            console.log(this.count) // 直接访问实例属性
            console.log(this.$el) // 内置属性也挂载在 this 上
        }
    }
  • 特点

    • 所有属性(用户定义 + 内置 API)混合在 this 中,可通过 this.xxx 直接访问。

    • 存在命名冲突风险(如用户定义 $el 会覆盖内置属性)。

2. 获取实例的方式
  • 默认方式 :组件选项内直接通过 this 访问实例(如 mountedmethods 中)。

  • 跨组件访问

    • 父组件通过 $refs 获取子组件实例this.$refs.child(子组件需用 ref 标记)。

    • 子组件通过 $parent 访问父实例this.$parent(强耦合,不推荐)。


二、Vue3:实例属性「代理隔离」

1. 实例创建与属性挂载
  • 核心逻辑

    • Vue3 中,createApp 创建应用实例,组件实例的用户属性(propssetup 定义的变量) 与内置 API($emit$el) 分离:

    • 用户属性 :通过代理对象(Proxy) 暴露,避免与内置 API 冲突。

    • 内置 API :挂载在 getCurrentInstance() 返回的 ctxproxy 上。

  • 示例(<script setup> 语法)

    代码段 复制代码
    <script setup>
    import { ref, getCurrentInstance } from 'vue'
    ​
    const count = ref(1)
    const instance = getCurrentInstance()
    ​
    // 用户属性:通过 proxy 访问(代理后的值)
    console.log(instance.proxy.count) // 1
    ​
    // 内置 API:通过 ctx 或 proxy 访问
    console.log(instance.ctx.$el) // DOM 元素
    console.log(instance.proxy.$el) // 同 $el(proxy 代理了 ctx)
    </script>
  • 特点

    • 用户属性与内置 API 隔离,避免命名冲突。

    • 需通过 getCurrentInstance()ref 显式获取实例,禁止直接用 this<script setup>thisundefined)。

2. 获取实例的方式
场景 Vue2 方式 Vue3 方式
组件选项内(非 setup) this 直接访问 仍可通过 this 访问(兼容选项式 API),但推荐用组合式 API。
组合式 API(setup) 无(Vue2 无 setup 语法) - getCurrentInstance() :返回实例对象,包含 proxy(用户属性代理)和 ctx(内置 API)。 - 注意 :生产环境需配置 __VUE_PROD_DEVTOOLS__ 才能访问。
父组件访问子组件 this.$refs.child - 子组件用 defineExpose 暴露属性vue <script setup> defineExpose({ count: 1 }) </script> - 父组件通过 ref 获取const childRef = ref(null); childRef.value.count(仅能访问暴露的属性)。
跨层级访问(祖孙) $parent / $children 推荐用 provide / inject 或状态管理库(Pinia),避免直接访问实例(Vue3 弱化 $parent)。

三、核心差异对比

维度 Vue2 特点 Vue3 特点
属性挂载方式 所有属性(用户 + 内置)混合挂载到 this 用户属性通过 Proxy 代理,内置 API 挂载到 ctx,隔离更清晰。
实例访问方式 依赖 this,选项式 API 天然支持。 组合式 API 需通过 getCurrentInstance()ref + defineExpose 显式获取,更严格。
命名冲突风险 高(用户定义属性可能覆盖内置 API,如 $el)。 低(用户属性与内置 API 分离,通过代理访问)。
开发模式限制 无特殊限制。 getCurrentInstance() 在生产环境默认隐藏,需手动开启调试模式。

6:VUE2和VUE3的directives

一、Vue2 指令生命周期钩子

  • bind :只调用一次,指令第一次绑定到元素时调用,可进行初始化设置,比如初始化一些数据、添加自定义属性等。该钩子函数的参数有 el(指令所绑定的元素)、binding(包含指令相关信息的对象,如指令的值、名称等)、vnode(当前虚拟节点)、oldVnode(上一个虚拟节点,首次绑定为 undefined)。

  • inserted :被绑定元素插入父节点时调用,仅保证父节点存在,但不一定会被插入到 DOM 中(比如在 display: none 的元素内插入)。参数与 bind 相同。

  • update :所在组件的 vNode 更新时调用,可能发生在其子 vNode 更新之前。在这个钩子中,你可以获取到更新前后的虚拟节点来做一些对比和处理。参数有 elbindingvnodeoldVnode

  • componentUpdated :指令所在组件的 VNode 及其子 VNode 全部更新后调用,可用于在组件及其子组件都更新完成后执行一些操作,例如重新计算元素的尺寸等。参数与上述钩子一致。

  • unbind :只调用一次,指令与元素解绑时调用,比如移除在 bind 钩子中添加的事件监听器、清理定时器等。参数有 elbindingvnodeoldVnode


二、Vue3 指令生命周期钩子

  • created :在绑定元素的 attribute 前或事件监听器应用前调用。和 Vue2 相比,它提供了一个更早的触发时机,可用于一些更前置的初始化操作。参数有 el(指令所绑定的元素)、binding(包含指令相关信息的对象,如指令的值、名称等)、vnode(当前虚拟节点)、prevVnode(上一个虚拟节点,首次绑定为 null)。

  • beforeMount :在元素被插入到 DOM 前调用,对应 Vue2 中的 bind 钩子,但含义稍有不同,bind 更强调指令首次绑定,而 beforeMount 更侧重于 DOM 插入前这个时间点。参数与 created 相同。

  • mounted :在绑定元素的父组件及其自己的所有子节点都挂载完成后调用,类似于 Vue2 中的 inserted,但更明确强调了所有子节点挂载完成这一条件。参数有 elbindingvnodeprevVnode

  • beforeUpdate :绑定元素的父组件更新前调用,它可以让开发者在父组件更新之前做一些准备工作,比如保存当前元素的状态等。参数有 elbindingvnodeprevVnode

  • updated :在绑定元素的父组件及其自己的所有子节点都更新后调用,替代了 Vue2 中的 componentUpdated 钩子,含义基本一致。参数有 elbindingvnodeprevVnode

  • beforeUnmount :绑定元素的父组件卸载前调用,提供了在元素即将被卸载时执行清理操作的时机,比如移除事件监听器等。参数有 elbindingvnodeprevVnode

  • unmounted :绑定元素的父组件卸载后调用,只调用一次,用于完成一些最终的清理工作,类似于 Vue2 中的 unbind 钩子,但更强调父组件卸载后这一时间点。参数有 elbindingvnodeprevVnode


三、Vue2 和 Vue3 对比总结

  • 新增钩子Vue3 新增了 createdbeforeUpdatebeforeUnmount 这几个钩子,提供了更多在不同阶段进行操作的时机,使得开发者对指令生命周期的控制更加精细。

  • 钩子对应关系

    • Vue2bind 对应 Vue3beforeMount,不过侧重点有细微差异。

    • Vue2inserted 对应 Vue3mounted,Vue3 的 mounted 条件更明确。

    • Vue2componentUpdated 对应 Vue3updated


7:VUE2和VUE3的$nextTick

一、核心概念与原理

1. 异步更新队列
  • Vue 响应式更新机制: 当修改响应式数据时,Vue 不会立即更新 DOM,而是将 DOM 更新任务放入异步队列中,等同一事件循环内的所有数据变化完成后,再批量更新 DOM,以避免频繁重渲染。

    JavaScript 复制代码
    this.message = 'Hello';
    this.message = 'World'; // 仅触发一次 DOM 更新
2. $nextTick 的作用
  • 获取更新后的 DOM$nextTick(callback) 用于在 DOM 更新完成后执行回调函数,确保回调中能获取到最新的 DOM 状态。

    JavaScript 复制代码
    this.message = 'Updated';
    console.log(this.$el.textContent); // 旧值
    ​
    this.$nextTick(() => {
        console.log(this.$el.textContent); // 新值 'Updated'
    });
3. 实现原理
  • 微任务 vs 宏任务

    • Vue2 优先使用 Promise.then(微任务),降级到 MutationObserver(微任务)或 setTimeout(宏任务);

    • Vue3 直接使用 Promise.then(现代浏览器均支持)。

  • 微任务:在当前任务完成后立即执行(如 Promise),优先级高于宏任务。

  • 宏任务:在当前任务队列的尾部添加新任务(如 setTimeout),可能导致延迟。


二、使用场景

1. DOM 操作依赖更新结果

例如修改数据后获取元素尺寸、位置:

JavaScript 复制代码
this.isVisible = true;
this.$nextTick(() => {
    const height = this.$el.offsetHeight;
    // 使用更新后的 DOM 尺寸
});
2. 组件初始化后执行操作

mounted 钩子中若需访问渲染后的 DOM,需用 $nextTick

JavaScript 复制代码
mounted() {
    this.$nextTick(() => {
        // 初始化第三方库(如 Chart.js)
    });
}
3. 表单操作与验证

修改表单值后立即获取焦点:

JavaScript 复制代码
this.inputValue = 'New Value';
this.$nextTick(() => {
    this.$refs.input.focus();
});
4. 测试场景

在单元测试中等待组件更新后断言:

JavaScript 复制代码
test('should update text', async () => {
    wrapper.vm.message = 'Updated';
    await wrapper.vm.$nextTick();
    expect(wrapper.text()).toContain('Updated');
});

三、Vue2 vs Vue3 的差异

1. 调用方式
  • Vue2

    • 组件内通过 this.$nextTick(callback) 调用。

    • 全局通过 Vue.nextTick(callback) 调用。

    JavaScript 复制代码
    // Vue2
    export default {
        methods: {
            updateAndRender() {
                this.message = 'Updated';
                this.$nextTick(() => {
                    // 访问更新后的 DOM
                });
            }
        }
    }
  • Vue3

    • 组件内通过 this.$nextTick(callback)(选项式 API)或 import { nextTick } from 'vue'(组合式 API)调用。

    • 全局通过 app.config.globalProperties.$nextTick 调用(需先创建应用实例 app)。

    JavaScript 复制代码
    // Vue3 组合式 API
    import { nextTick } from 'vue';
    ​
    export default {
        setup() {
            const updateAndRender = async () => {
                // 修改响应式数据
                await nextTick(); // 可使用 Promise 风格
                // 访问更新后的 DOM
            };
            return { updateAndRender };
        }
    }
2. 异步支持
  • Vue2

    • $nextTick 仅支持回调函数,需手动处理异步逻辑。
    JavaScript 复制代码
    this.$nextTick(function() {
        // 回调函数方式
    });
  • Vue3

    • $nextTick 支持 Promise,可使用 await 语法,更符合现代异步编程习惯。
    JavaScript 复制代码
    await this.$nextTick(); // 等待 DOM 更新
    // 直接使用更新后的 DOM
3. 内部实现
  • Vue2: 兼容旧浏览器,使用多种异步策略(Promise → MutationObserver → setTimeout)。

    • 在不支持 Promise 的环境(如 IE)中,可能有轻微延迟(使用 setTimeout)。
  • Vue3: 仅依赖 Promise,不再兼容不支持 Promise 的浏览器(如 IE11),简化实现且性能更优。


四、最佳实践

1. 优先使用 Promise 风格(Vue3 推荐):
JavaScript 复制代码
await this.$nextTick();
// 后续代码自动等待 DOM 更新完成
2. 避免在循环中频繁调用:

若需批量更新数据,可在所有修改完成后调用一次 $nextTick

JavaScript 复制代码
// 批量修改数据
this.list = newList;
this.isLoading = false;
this.count = this.list.length;
​
this.$nextTick(() => {
    // 处理所有更新后的 DOM
});
3. 单元测试中使用:

在 Vue Test Utils 中,等待组件更新后再断言:

JavaScript 复制代码
await wrapper.vm.$nextTick();
expect(wrapper.find('div').text()).toBe('Updated');

五、总结

维度 Vue2 Vue3
调用方式 this.$nextTick(callback) this.$nextTick(callback)await nextTick()
异步支持 仅回调函数 支持 Promise/await
浏览器兼容 兼容 IE(使用多种异步策略) 仅支持支持 Promise 的浏览器
内部实现 复杂(多层降级) 简单(仅 Promise)

8:VUE2和VUE3的slot插槽

一、Vue2 中 Slot 插槽的使用

Vue2 中插槽通过 slot 属性(具名插槽)和 slot-scope 指令(作用域插槽)实现,语法相对分散。

1. 基础内容插槽(默认插槽)
  • 含义 :子组件中用 <slot> 占坑,父组件在使用子组件时,标签内的内容会填充到该插槽。

  • 特性

    • 可包含文本、HTML、其他组件(父组件作用域内的组件)。

    • 父组件插槽内容仅能访问父组件作用域,无法直接访问子组件数据。

  • 示例

    代码段 复制代码
    <template>
        <div>
            <slot></slot> </div>
    </template>
    ​
    <template>
        <Child>
            <p>父组件的内容</p> <MyComponent></MyComponent> </Child>
    </template>
2. 后备内容(默认内容)
  • 含义 :子组件的 <slot> 内可定义默认内容,当父组件未传递内容时显示。

  • 示例

    代码段 复制代码
    <template>
        <div>
            <slot>这是默认内容(父组件没传内容时显示)</slot>
        </div>
    </template>
    ​
    <template>
        <Child></Child>
    </template>
3. 具名插槽
  • 含义 :当子组件需要多个插槽时,用 name 属性给插槽命名,父组件通过 slot 属性指定对应插槽。

  • 语法

    • 子组件<slot name="插槽名"></slot>

    • 父组件 :在内容元素上用 slot="插槽名" 绑定

  • 示例

    代码段 复制代码
    <template>
        <div>
            <slot name="header"></slot> <slot name="content"></slot> <slot></slot> </div>
    </template>
    ​
    <template>
        <Child>
            <h1 slot="header">这是头部</h1> <p slot="content">这是内容</p> <p>这是默认插槽内容</p> </Child>
    </template>
4. 作用域插槽(访问子组件数据)
  • 含义 :父组件需要访问子组件数据时,子组件通过 slot 绑定数据(插槽 prop),父组件用 slot-scope 接收。

  • 语法

    • 子组件<slot :子数据="变量名"></slot>(绑定插槽 prop)

    • 父组件slot-scope="接收名"(接收子组件传递的 prop)

  • 示例

    代码段 复制代码
    <template>
        <div>
            <slot name="user" :user="user" :age="18"></slot>
        </div>
    </template>
    <script>
        export default {
            data() {
                return {
                    user: {
                        name: '张三'
                    }
                }
            }
        }
    </script>
    ​
    <template>
        <Child>
            <template slot="user" slot-scope="scope">
                {{ scope.user.name }} - {{ scope.age }} </template>
        </Child>
    </template>

    注意slot-scope 可直接用在元素上(非 template),但推荐用 template 包裹(更清晰)。

5. 解构插槽 Prop(Vue2.5+ 支持)
  • 含义slot-scope 支持 ES6 解构语法,简化对插槽 prop 的访问。

  • 示例

    代码段 复制代码
    <template slot="user" slot-scope="{ user, age }">
        {{ user.name }} - {{ age }} </template>
    ​
    <template slot="user" slot-scope="{ user: person, age = 20 }">
        {{ person.name }} - {{ age }} </template>

二、Vue3 中 Slot 插槽的使用

Vue3 统一了插槽语法,废弃 slotslot-scope,改用 v-slot 指令(可缩写为 #),更简洁且规范。

1. 基础内容插槽(默认插槽)
  • 语法 :子组件仍用 <slot> 占坑,父组件通过 v-slot:default(可省略)绑定默认插槽。

  • 示例

    代码段 复制代码
    <template>
        <div>
            <slot></slot> </div>
    </template>
    ​
    <template>
        <Child>
            <p>默认插槽内容</p>
    ​
            <template v-slot:default>
                <p>显式默认插槽内容</p>
            </template>
        </Child>
    </template>
2. 后备内容(与 Vue2 一致)

子组件 <slot> 内的内容为默认值,父组件未传递时显示。

代码段 复制代码
<slot>默认内容</slot>
3. 具名插槽(v-slot 语法)
  • 语法

    • 子组件<slot name="插槽名"></slot>(同 Vue2)

    • 父组件<template v-slot:插槽名> 或缩写 <template #插槽名>

  • 示例

    代码段 复制代码
    <template>
        <div>
            <slot name="header"></slot>
            <slot name="content"></slot>
        </div>
    </template>
    ​
    <template>
        <Child>
            <template #header> <h1>头部</h1>
            </template>
            <template v-slot:content> <p>内容</p>
            </template>
        </Child>
    </template>

    注意v-slot 只能用在 <template> 或组件标签上(不能直接用在普通元素上)。

4. 作用域插槽(v-slot 接收 prop)
  • 语法

    • 子组件<slot :子数据="变量名"></slot>(同 Vue2 绑定插槽 prop)

    • 父组件v-slot:插槽名="接收名"(用 v-slot 接收,替代 Vue2 的 slot-scope)

  • 示例

    代码段 复制代码
    <template>
        <slot name="user" :user="user"></slot>
    </template>
    <script setup>
        const user = {
            name: '李四'
        }
    </script>
    ​
    <template>
        <Child>
            <template #user="scope">
                {{ scope.user.name }} </template>
        </Child>
    </template>
5. 独占默认插槽的简化写法

当组件只有默认插槽时,v-slot 可直接写在组件标签上(无需 template)。

  • 示例

    代码段 复制代码
    <template>
        <slot :msg="msg"></slot>
    </template>
    <script setup>
        const msg = 'hello'
    </script>
    ​
    <template>
        <Child v-slot="scope">
            {{ scope.msg }} </Child>
    </template>

    注意 :若存在多个插槽,必须用 <template> 包裹(否则作用域混乱)。

6. 解构插槽 Prop(Vue3 增强)

同 Vue2 的解构逻辑,但基于 v-slot 语法,支持更灵活的 ES6 特性。

  • 示例

    代码段 复制代码
    <template #user="{ user: person }">
        {{ person.name }}
    </template>
    ​
    <template #user="{ user = { name: '默认名' } }">
        {{ user.name }}
    </template>
7. 动态插槽名

v-slot:[动态变量] 定义动态插槽名(Vue3 新增,Vue2 不支持)。

  • 示例

    代码段 复制代码
    <template>
        <Child>
            <template v-slot:[slotName]="scope">
                {{ scope.data }}
            </template>
        </Child>
    </template>
    <script setup>
        const slotName = 'dynamicSlot' // 动态插槽名
    </script>
8. 具名插槽缩写(#)

v-slot:插槽名 可缩写为 #插槽名,但默认插槽必须显式写 #default

  • 示例

    代码段 复制代码
    <template>
        <Child>
            <template #header>头部</template> 
            <template #default>默认内容</template> 
        </Child>
    </template>

三、Vue2 与 Vue3 插槽核心区别

特性 Vue2 语法 Vue3 语法
具名插槽绑定 slot="插槽名"(元素属性) v-slot:插槽名#插槽名(template 上)
作用域插槽接收 slot-scope="接收名" v-slot:插槽名="接收名"#插槽名="接收名"
独占默认插槽写法 不支持(必须用 slot-scope 在 template 上) 支持 v-slot 直接写在组件上
动态插槽名 不支持(需通过其他 hack 实现) 支持 v-slot:[变量]
缩写语法 # 代替 v-slot:

四、面试高频考点

  • 插槽的作用:实现组件内容分发,提高组件复用性(父组件自定义子组件部分内容)。

  • 作用域插槽的使用场景:父组件需要根据子组件数据动态渲染内容时(如:自定义表格列、列表项样式)。

  • Vue2 与 Vue3 插槽语法区别 :重点强调 v-slot 替代 slotslot-scope,以及缩写和动态插槽的差异。

  • 默认插槽与具名插槽的优先级 :未指定名称的内容默认进入 default 插槽,具名插槽需显式匹配。


9:VUE2和VUE3的路由Router

一、Vue Router 基础概念(Vue2 & Vue3 通用)

Vue Router 是 Vue.js 的官方路由管理器,实现单页面应用(SPA)的路由功能。核心功能包括:

  • 路由配置:定义路径与组件的映射关系

  • 导航守卫:控制路由访问权限和跳转逻辑

  • 路由参数:支持动态路由匹配和参数传递

  • 路由懒加载:按需加载组件,提升性能


二、Vue2 与 Vue3 路由的核心差异

特性 Vue2(Vue Router 3.x) Vue3(Vue Router 4.x)
安装与导入 import VueRouter from 'vue-router' Vue.use(VueRouter) import { createRouter, createWebHistory } from 'vue-router' const router = createRouter(...)
历史模式 new VueRouter({ mode: 'history' }) createRouter({ history: createWebHistory() })
实例属性 this.$router(路由实例) this.$route(当前路由信息) 同上(保持兼容性)
组合式 API 支持 不支持(需通过 getCurrentInstance 间接访问) 支持 useRouter()useRoute() 组合式函数
导航守卫 写法:router.beforeEach((to, from, next) => {...}) 同上,但 next() 不再强制调用(直接 return)
路由懒加载 component: () => import('./Page.vue') 同上

三、路由配置详解

1. 基础配置(必选属性)
JavaScript 复制代码
// Vue2 写法
const router = new VueRouter({
    routes: [{
        path: '/home', // 路由路径
        component: Home // 对应组件(可直接导入或懒加载)
    }]
})
​
// Vue3 写法
const router = createRouter({
    history: createWebHistory(),
    routes: [{
        path: '/home',
        component: () => import('./views/Home.vue') // 推荐懒加载
    }]
})
2. 嵌套路由(children)
JavaScript 复制代码
{
    path: '/user',
    component: User,
    children: [{
        path: 'profile', // 实际路径为 /user/profile
        component: UserProfile
    }, {
        path: '', // 空路径表示 /user 的默认子路由
        component: UserHome
    }]
}
3. 命名路由(name)
JavaScript 复制代码
{
    path: '/user/:id',
    name: 'UserDetail', // 路由标识符
    component: UserDetail
}
​
// 跳转时使用
this.$router.push({
    name: 'UserDetail',
    params: {
        id: 123
    }
})
4. 路由参数传递

(1)params 参数(动态路由匹配)

JavaScript 复制代码
// 路由配置
{
    path: '/user/:id', // 动态路径参数
    component: User
}
​
// 跳转方式
this.$router.push({
    name: 'User',
    params: {
        id: 123
    }
}) // 必须用 name
​
// 组件内获取
this.$route.params.id // 123

(2)query 参数(类似 URL 参数)

JavaScript 复制代码
// 跳转方式
this.$router.push({
    path: '/user',
    query: {
        id: 123,
        name: '张三'
    }
})
// 生成 URL: /user?id=123&name=张三
​
// 组件内获取
this.$route.query.id // 123
5. Props 解耦(参数注入组件)
JavaScript 复制代码
// 方式一:静态值
{
    path: '/static',
    component: StaticPage,
    props: {
        title: '静态标题'
    } // 组件通过 props: ['title'] 接收
}
​
// 方式二:params 参数自动解耦(布尔模式)
{
    path: '/user/:id',
    component: User,
    props: true // 组件通过 props: ['id'] 接收
}
​
// 方式三:自定义函数(支持 query)
{
    path: '/search',
    component: Search,
    props: route => ({
        keyword: route.query.keyword
    }) // 动态映射
}

四、路由跳转方法

1. 声明式导航(模板中)
代码段 复制代码
<router-link to="/home">Home</router-link>
​
<router-link :to="{ name: 'User', params: { id: 123 } }">User</router-link>
​
<router-link :to="{ path: '/search', query: { q: 'vue' } }">Search</router-link>
2. 编程式导航(JS 中)
JavaScript 复制代码
// 基本跳转
this.$router.push('/home')
this.$router.push({
    name: 'User',
    params: {
        id: 123
    }
})
​
// 替换当前历史记录
this.$router.replace('/about')
​
// 后退/前进
this.$router.go(-1) // 后退一步

五、导航守卫(路由拦截)

1. 全局前置守卫
JavaScript 复制代码
// Vue2
router.beforeEach((to, from, next) => {
    if (to.meta.requiresAuth && !isAuthenticated()) {
        next('/login') // 未登录时跳转到登录页
    } else {
        next() // 继续路由跳转
    }
})
​
// Vue3(无需调用 next,直接 return)
router.beforeEach((to, from) => {
    if (to.meta.requiresAuth && !isAuthenticated()) {
        return '/login' // 直接返回路径或 false 阻止跳转
    }
})
2. 路由独享守卫
JavaScript 复制代码
{
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from, next) => {
        // 仅针对 /admin 路由的权限验证
        if (!isAdmin()) {
            next('/forbidden')
        } else {
            next()
        }
    }
}
3. 组件内守卫(Vue2 特有)
JavaScript 复制代码
export default {
    beforeRouteEnter(to, from, next) {
        // 在渲染该组件的路由确认前调用
        // 无法访问 this(组件实例还未创建)
        next(vm => {
            // 通过 vm 访问组件实例
        })
    },
    beforeRouteUpdate(to, from, next) {
        // 路由更新时调用(如 params 变化但组件复用)
        this.fetchData(to.params.id)
        next()
    },
    beforeRouteLeave(to, from, next) {
        // 导航离开该组件的路由时调用
        if (this.hasUnsavedChanges()) {
            next(false) // 阻止离开
        } else {
            next()
        }
    }
}

六、Vue3 路由新增特性

1. 组合式 API 支持
JavaScript 复制代码
import {
    useRouter,
    useRoute
} from 'vue-router'
​
export default {
    setup() {
        const router = useRouter()
        const route = useRoute()
​
        const goToUser = () => {
            router.push({
                name: 'User',
                params: {
                    id: 123
                }
            })
        }
​
        return {
            currentPath: route.path,
            goToUser
        }
    }
}
2. 路由懒加载(与 Vue2 一致,但推荐写法)
JavaScript 复制代码
// 推荐使用
const Home = () => import('./views/Home.vue')
​
// 带加载状态
const Home = () => import( /* webpackChunkName: "home" */ './views/Home.vue')
3. 路由别名与重定向
JavaScript 复制代码
{
    path: '/',
    redirect: '/home' // 重定向
}, {
    path: '/home',
    component: Home,
    alias: '/' // 别名,访问 / 等价于 /home
}

七、面试高频考点

  • paramsquery 的区别

    • params :必须在路由配置中定义占位符(如 /user/:id),只能通过 name 跳转

    • query :无需定义占位符,生成类似 ?key=value 的 URL 参数,支持 path 跳转

  • Vue2 与 Vue3 路由的主要差异

    • 安装方式(Vue.usecreateRouter

    • 组合式 API 支持

    • 导航守卫的调用方式简化

  • 导航守卫的执行顺序

    • 全局前置守卫 → 路由独享守卫 → 组件内守卫 → 全局解析守卫 → 全局后置钩子
  • 路由懒加载的原理

    • 通过动态 import 实现组件的按需加载,减少首屏加载时间
  • 路由钩子中 next() 的注意事项

    • Vue2 中必须调用 next(),否则路由跳转被阻止

    • Vue3 中可直接 return 路径或 false,无需调用 next()


10:Vue2和Vue3的状态管理

一、Vue2 状态管理:Vuex

VuexVue2 官方推荐的状态管理库,基于 单向数据流 设计,用于集中管理组件共享状态。核心思想是将共享状态抽离为一个全局的「Store」,供所有组件访问。

1. 核心概念
  • State:存储全局状态的对象(唯一数据源)。

  • Getter:类似组件的计算属性,用于派生 State 中的数据(缓存特性)。

  • Mutation:唯一修改 State 的方式(同步操作),必须是纯函数。

  • Action:处理异步操作(如接口请求),通过提交 Mutation 修改 State。

  • Module:拆分 Store 为多个模块(解决状态过多导致的臃肿)。

2. 基本使用(Vue2 + Vuex)

(1)安装与配置

JavaScript 复制代码
// 安装:npm install vuex@3 (Vue2 对应 Vuex 3.x)
​
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
​
Vue.use(Vuex)
​
const store = new Vuex.Store({
    // 1. 状态
    state: {
        count: 0,
        user: {
            name: '张三',
            age: 20
        }
    },
    // 2. 派生状态(计算属性)
    getters: {
        // 计算用户信息字符串
        userInfo: (state) => {
            return `姓名:${state.user.name},年龄:${state.user.age}`
        },
        // 带参数的 getter(返回函数)
        getUserAge: (state) => (minAge) => {
            return state.user.age >= minAge
        }
    },
    // 3. 同步修改状态(必须是同步函数)
    mutations: {
        // 第一个参数固定为 state
        increment(state) {
            state.count++
        },
        // 接收额外参数(payload)
        setUserAge(state, payload) {
            state.user.age = payload
        }
    },
    // 4. 处理异步操作(可提交 mutations)
    actions: {
        // 第一个参数为 context(包含 commit、state 等)
        async fetchUserInfo(context) {
            // 模拟接口请求
            const res = await axios.get('/api/user')
            // 通过 commit 调用 mutation 修改状态
            context.commit('setUserAge', res.data.age)
        },
        // 简化写法(解构 context)
        async updateCount({
            commit
        }, num) {
            commit('increment', num)
        }
    },
    // 5. 模块拆分(复杂项目用)
    modules: {
        // 购物车模块
        cart: {
            // 开启命名空间(避免 mutation/action 重名)
            namespaced: true,
            state: {
                items: []
            },
            mutations: {
                addItem(state, item) {
                    state.items.push(item)
                }
            }
        }
    }
})
​
export default store

(2)在组件中使用

JavaScript 复制代码
// 在 main.js 中挂载 store
import store from './store'
new Vue({
    el: '#app',
    store, // 挂载后组件可通过 this.$store 访问
    render: h => h(App)
})
​
// 组件中使用
export default {
    mounted() {
        // 访问 state
        console.log(this.$store.state.count)
        // 访问 getter
        console.log(this.$store.getters.userInfo)
        // 调用 mutation(同步)
        this.$store.commit('increment')
        // 调用 action(异步)
        this.$store.dispatch('fetchUserInfo')
        // 调用模块中的 mutation(需加命名空间)
        this.$store.commit('cart/addItem', {
            id: 1,
            name: '商品'
        })
    }
}

(3)辅助函数(简化代码)

Vuex 提供 mapState、mapGetters、mapMutations、mapActions 辅助函数,直接将 Store 中的内容映射到组件:

JavaScript 复制代码
import {
    mapState,
    mapGetters,
    mapMutations,
    mapActions
} from 'vuex'
​
export default {
    computed: {
        // 映射 state 到计算属性
        ...mapState(['count']),
        // 映射 getter 到计算属性
        ...mapGetters(['userInfo'])
    },
    methods: {
        // 映射 mutation 到方法(直接调用 this.increment())
        ...mapMutations(['increment', 'setUserAge']),
        // 映射 action 到方法(直接调用 this.fetchUserInfo())
        ...mapActions(['fetchUserInfo'])
    }
}

二、Vue3 状态管理:Pinia

PiniaVue3 官方推荐的状态管理库,是 Vuex 的继任者,简化了 API 并原生支持 TypeScript,更符合 Vue3 的 Composition API。

1. 核心改进(对比 Vuex)
  • 移除 Mutation ,直接在 Action 中修改状态(同步 / 异步均可)。

  • 无嵌套模块,通过「Store 拆分」替代(每个 Store 独立)。

  • 原生支持 TypeScript,无需额外类型声明。

  • 简化语法,更贴合 Composition API。

2. 基本使用(Vue3 + Pinia)

(1)安装与配置

JavaScript 复制代码
// 安装:npm install pinia
​
// store/index.js
import {
    createPinia
} from 'pinia'
// 创建 pinia 实例
const pinia = createPinia()
export default pinia
​
// 在 main.js 中挂载
import {
    createApp
} from 'vue'
import App from './App.vue'
import pinia from './store'
​
createApp(App).use(pinia).mount('#app')

(2)定义 Store(核心)

JavaScript 复制代码
// store/user.js(用户相关状态)
import {
    defineStore
} from 'pinia'
​
// 第一个参数:store 唯一标识(必须唯一)
// 第二个参数:配置对象(state、getters、actions)
export const useUserStore = defineStore('user', {
    // 状态(返回初始值的函数)
    state: () => ({
        count: 0,
        user: {
            name: '张三',
            age: 20
        }
    }),
    // 派生状态(类似计算属性)
    getters: {
        // 接收 state 作为参数
        userInfo: (state) => `姓名:${state.user.name},年龄:${state.user.age}`,
        // 访问其他 getter(用 this)
        doubleCount: (state) => state.count * 2,
        // 带参数的 getter(返回函数)
        getUserAge: (state) => (minAge) => state.user.age >= minAge
    },
    // 方法(同步/异步均可,直接修改 state)
    actions: {
        // 同步修改
        increment() {
            this.count++ // 直接通过 this 访问 state
        },
        // 接收参数
        setUserAge(age) {
            this.user.age = age
        },
        // 异步操作(直接修改状态,无需 commit)
        async fetchUserInfo() {
            const res = await axios.get('/api/user')
            this.user.age = res.data.age
        },
        // 调用其他 action
        async updateAndFetch() {
            this.increment() // 调用同步 action
            await this.fetchUserInfo() // 调用异步 action
        }
    }
})

(3)在组件中使用(Options API)

JavaScript 复制代码
export default {
    // 引入 store
    import {
        useUserStore
    } from '@/store/user'
​
    data() {
        return {
            userStore: useUserStore() // 实例化 store
        }
    },
    mounted() {
        // 访问 state
        console.log(this.userStore.count)
        // 访问 getter
        console.log(this.userStore.userInfo)
        // 调用 action
        this.userStore.increment()
        this.userStore.fetchUserInfo()
    }
}

(4)在组件中使用(Composition API)

JavaScript 复制代码
import {
    useUserStore
} from '@/store/user'
import {
    onMounted
} from 'vue'
​
export default {
    setup() {
        const userStore = useUserStore()
        onMounted(() => {
            console.log(userStore.count) // 访问状态
            userStore.increment() // 调用方法
        })
        return {
            userStore
        }
    }
}

(5)状态更新的高级用法

  • 批量修改状态 :使用 $patch(更高效,适合多属性修改)

    JavaScript 复制代码
    // 方式1:对象形式
    userStore.$patch({
        count: 10,
        'user.age': 25
    })
    ​
    // 方式2:函数形式(复杂逻辑)
    userStore.$patch((state) => {
        state.count += 5
        state.user.age = 30
    })
  • 重置状态 :使用 $reset(恢复到初始值)

    JavaScript 复制代码
    userStore.$reset() // 重置所有状态
  • 解构状态 :使用 toRefs 保持响应式

    JavaScript 复制代码
    import {
        toRefs
    } from 'vue'
    ​
    const userStore = useUserStore()
    const {
        count,
        user
    } = toRefs(userStore) // 解构后仍为响应式
3. Store 拆分与组合

Pinia 无需嵌套模块,通过多个独立 Store 实现拆分,需要时在组件中组合使用:

JavaScript 复制代码
// 引入多个 store
import {
    useUserStore
} from '@/store/user'
import {
    useCartStore
} from '@/store/cart'
​
export default {
    setup() {
        const userStore = useUserStore()
        const cartStore = useCartStore()
        return {
            userStore,
            cartStore
        }
    }
}

三、Vuex 与 Pinia 核心区别对比

特性 Vuex(Vue2) Pinia(Vue3)
状态修改 必须通过 Mutation(同步) 直接在 Action 中修改(同步 / 异步)
异步操作 必须在 Action 中,通过 commit 直接在 Action 中修改状态
模块管理 嵌套模块(namespaced) 多个独立 Store 拆分
TypeScript 支持 需手动声明类型 原生支持
语法简洁度 较繁琐(需 commit、dispatch) 简洁(直接操作)
生态兼容性 仅 Vue2 仅 Vue3(推荐)

四、面试高频考点

  • Vuex 为什么要求 Mutation 必须是同步函数?

    • :因为 Vuex 的 devtools 工具需要追踪状态变化,异步操作会导致 devtools 无法准确记录 mutation 调用顺序,难以调试。
  • Pinia 相比 Vuex 有哪些优势?

    • :移除 Mutation 简化逻辑、原生支持 TypeScript、更贴合 Vue3 Composition API、无需嵌套模块等。
  • 如何在 Vue 组件中高效使用 Vuex/Pinia 状态?

    • :Vuex 用辅助函数(mapState 等);Pinia 直接通过 store 实例访问,结合 toRefs 保持响应式。
  • 状态管理的适用场景?

    • :多组件共享数据(如用户信息、购物车)、跨层级组件通信、需要持久化的状态(如登录令牌)。

11:VUE2和VUE3的Computed 和 watch

一、Computed 和 Watch 的核心区别

1. 核心差异对比表
特性 Computed Watch
缓存机制 有缓存,依赖项不变时直接返回缓存值 无缓存,数据变化时立即执行回调
触发时机 依赖项变化后,下次访问时更新 数据变化时立即执行回调
适用场景 依赖多个数据计算一个新值 监听特定数据变化并执行副作用(如异步操作)
异步支持 不支持(异步操作会导致缓存失效) 支持(可在回调中执行异步操作)
初始执行 默认不执行,访问时计算 可通过 immediate: true 强制初始执行
深度监听 不支持(需手动处理嵌套对象) 支持(通过 deep: true 监听对象所有属性)
回调参数 无(通过返回值获取结果) 接收 newValoldVal(深度监听时 oldVal 可能与 newVal 相同)
2. 代码示例对比
JavaScript 复制代码
// Computed 示例
export default {
    data() {
        return {
            firstName: '张',
            lastName: '三'
        }
    },
    computed: {
        // 基础用法(缓存特性)
        fullName() {
            return this.firstName + ' ' + this.lastName
        }
    }
}
​
// Watch 示例
export default {
    data() {
        return {
            userId: 1,
            userInfo: {}
        }
    },
    watch: {
        // 监听简单数据变化
        userId(newVal, oldVal) {
            this.fetchUserInfo(newVal)
        },
        // 深度监听对象
        userInfo: {
            handler(newVal) {
                // 处理对象变化
            },
            deep: true, // 深度监听
            immediate: true // 初始执行一次
        }
    },
    methods: {
        async fetchUserInfo(id) {
            const res = await axios.get(`/api/user/${id}`)
            this.userInfo = res.data
        }
    }
}

二、响应式原理(Vue2 vs Vue3)

1. Vue2 响应式原理
  • Computed

    1. 创建 Computed Watcher,并将 dirty 标记设为 true

    2. 访问计算属性时触发 getter

      • dirtytrue,重新计算值并缓存

      • dirty 设为 false,依赖收集(将计算属性的 Watcher 添加到依赖项的 Dep 中)

    3. 依赖项变化时,触发计算属性的 Watcher 更新,将 dirty 设为 true

  • Watch

    1. 创建 User Watcher,若 immediate: true 则立即执行回调

    2. 触发被监听属性的 getter,完成依赖收集(将 Watcher 添加到 Dep 中)

    3. 被监听属性变化时,Dep 通知 Watcher 执行回调

    4. sync: true,同步执行回调

    5. 否则将回调放入队列,通过 nextTick 异步执行

2. Vue3 响应式原理

Vue3 基于 Proxy 重构响应式系统,核心机制类似但更高效:

  • Computed

    1. 创建 ComputedRefImpl 对象,包含 gettersetter

    2. 访问计算属性时触发 getter

      • 通过 track 收集依赖(将计算属性与依赖项建立关联)
      • 缓存计算结果,返回值
    3. 依赖项变化时,通过 trigger 触发更新,标记计算属性为「脏」

  • Watch

    1. 创建 WatchEffectWatchOptions 实例
    2. 执行 source 函数获取初始值,收集依赖
    3. 依赖项变化时,执行回调函数
    4. 支持 flush 选项控制回调执行时机(pre/post/sync

三、高频面试考点

1. Computed 缓存的意义
  • 避免重复计算,提升性能(尤其复杂计算场景)

  • 对比 methods:每次调用都会重新执行函数,而 computed 仅依赖项变化时才重新计算

2. Watch 如何监听对象属性变化?
  • 深度监听(deep: true :递归遍历对象所有属性,为每个属性创建依赖

    JavaScript 复制代码
    watch: {
        obj: {
            handler(newVal) { /* ... */ },
            deep: true
        }
    }
  • 监听特定属性:通过路径监听

    JavaScript 复制代码
    watch: {
        'obj.prop': { /* ... */ }
    }
3. Vue3 中如何使用?
  • Computed

    JavaScript 复制代码
    import {
        computed
    } from 'vue'
    ​
    const count = ref(0)
    const doubleCount = computed(() => count.value * 2)
  • Watch

    JavaScript 复制代码
    import {
        watch,
        ref
    } from 'vue'
    ​
    const count = ref(0)
    ​
    watch(count, (newVal, oldVal) => {
        console.log(`count changed: ${oldVal} -> ${newVal}`)
    })
4. 执行时机差异
  • Computed:同步更新,依赖项变化后,下次访问时生效

  • Watch :默认异步执行(通过 nextTick),可通过 sync: true 强制同步

5. 循环依赖问题
  • Computed 依赖自身:Vue2 会导致无限循环(Vue3 会警告)

  • Watch 修改被监听值:可能导致无限循环,需谨慎处理


四、最佳实践建议

  • 优先使用 Computed:当结果依赖于其他响应式数据时

  • 使用 Watch 处理副作用:如异步操作、跨组件通信

  • 避免在 Computed 中使用异步:会破坏缓存机制,改用 Watch

  • 深度监听慎用:对象层级过深时会影响性能,优先监听具体属性

  • Vue3 推荐 Composition API:更灵活的依赖控制和类型支持


12:VUE2和VUE3的内置指令

一、Vue 常用内置指令(按使用频率排序)

1. v-textv-html:文本渲染
指令 作用 注意事项
v-text 渲染文本(等价于 {{}} 会覆盖元素原内容,无 XSS 风险
v-html 渲染 HTML 片段 有 XSS 风险(避免用于用户输入内容),Vue 模板语法(如 {{}})不会被解析

示例

javascript 复制代码
    ````代码段
    <div v-text="message"></div> <div v-html="rawHtml"></div> ```
    ​
    ##### 2. `v-show` 与 `v-if/v-else/v-else-if`:条件渲染
    | 指令组合 | 作用 | 核心差异 |
    | :--- | :--- | :--- |
    | **v-show** | 基于条件显示元素 | 通过 `display: none` 隐藏,初始渲染开销高,切换开销低 |
    | **v-if/v-else** | 基于条件渲染元素 | 条件为 false 时完全移除元素,初始渲染开销低,切换开销高 |
    **注意**:
    - `v-if` 可与 `v-else-if`/`v-else` 组合,需紧邻使用(否则报错)
    - `v-show` 不支持 `<template>` 标签,`v-if` 支持
    **示例**:
    ```vue
    <div v-show="isVisible">始终存在于 DOM 中</div>
    ​
    <div v-if="type === 'A'">A</div>
    <div v-else-if="type === 'B'">B</div>
    <div v-else>Other</div>
    ````
3. v-for:列表渲染
  • 作用:基于可迭代数据(数组、对象、字符串等)重复渲染元素

  • 语法v-for="(item, index) in items"(数组)或 v-for="(value, key) in object"(对象)

  • 关键要求:必须绑定 key(提升 diff 算法效率,避免复用问题)

    示例:

    代码段 复制代码
    <ul>
        <li v-for="(item, index) in list" :key="item.id">
            {{ index }}: {{ item.name }}
        </li>
    </ul>
    ​
    <div v-for="(value, key) in user" :key="key">
        {{ key }}: {{ value }}
    </div>

注意

  • Vue3 中 v-forv-if 优先级调整:v-if 现在可以访问 v-for 的变量

  • 避免用 index 作为 key(数组排序 / 过滤时会导致问题)

4. v-on(缩写 @):事件绑定
  • 作用:绑定 DOM 事件或组件自定义事件

  • 修饰符(核心常用)

    • .stop :阻止事件冒泡(event.stopPropagation()

    • .prevent :阻止默认行为(event.preventDefault()

    • .once:事件仅触发一次

    • .native:监听组件根元素的原生事件(Vue2 常用,Vue3 移除)

    • .enter/.esc 等:按键修饰符(监听特定按键)

      示例:

      代码段 复制代码
      <button @click="handleClick">点击</button>
      ​
      <form @submit.prevent="handleSubmit">
          <input @keyup.enter="handleEnter">
      </form>
      ​
      <button @click="handleClickWithEvent($event, arg)">传参</button>
5. v-bind(缩写 :):属性绑定
  • 作用:动态绑定 HTML 属性、组件 props 或 CSS 类

  • 修饰符

    • .camel :将短横线命名转换为驼峰(如 :svg-icon.camel="iconName"

    • .prop :强制绑定为 DOM property(如 :value.prop="value"

    • .attr:强制绑定为 DOM attribute(默认行为,可省略)

      示例:

      代码段 复制代码
      <img :src="imageUrl" :alt="imageAlt">
      ​
      <div :class="{ active: isActive }" :style="{ color: textColor }"></div>
      ​
      <ChildComponent :user="currentUser"></ChildComponent>
6. v-model:双向绑定
  • 作用:在表单元素或组件上创建双向数据绑定

  • 支持元素input(文本 / 复选框 / 单选)、selecttextarea、自定义组件

  • 修饰符

    • .lazy :监听 change 事件(而非 input),适用于失焦后同步

    • .number:将输入值转为数字(如表单输入的字符串转为 Number)

    • .trim:自动移除输入值两端空格

      示例:

      代码段 复制代码
      <input v-model="message" placeholder="输入文本">
      ​
      <input type="checkbox" v-model="isAgreed">
      ​
      <input v-model.lazy="username"> <input v-model.number="age"> <input v-model.trim="searchQuery"> ```
      ​
      ##### 7. `v-slot`(缩写 `#`):插槽绑定
      - **作用**:声明具名插槽或作用域插槽(详见「插槽」章节)
      - **语法**:`v-slot:name` 或 `#name`,仅用于 `<template>` 或组件标签
      - **注意**:Vue3 中统一用 `v-slot` 替代 Vue2 的 `slot` 属性
      **示例**:
      ```vue
      <ChildComponent>
          <template #header>头部内容</template>
          <template #default>默认内容</template>
      </ChildComponent>
      ​
      <ChildComponent>
          <template #item="slotProps">
              {{ slotProps.item.name }}
          </template>
      </ChildComponent>
8. 性能与编译相关指令
指令 作用 使用场景
v-once 仅渲染一次,后续更新跳过 静态内容(如版权信息),优化性能
v-pre 跳过编译,直接渲染原始内容 显示 Vue 模板语法(如 {{}} 本身),或加速大量静态内容渲染
v-memo 缓存子树,依赖数组不变则不更新 海量列表渲染(如 1000+ 项的 v-for),减少重复计算
v-cloak 隐藏未编译的模板 无构建工具的环境,避免页面加载时闪现 {{}}

示例

xml 复制代码
<div v-once>{{ staticMessage }}</div> <div v-pre>{{ 这里的 {{}} 会被原样显示 }}</div>
​
<div v-for="item in bigList" :key="item.id" v-memo="[item.id, item.status]">
    {{ item.name }} - {{ item.status }}
</div>
​
<style> [v-cloak] { display: none; } </style>
<div v-cloak>{{ message }}</div> 

二、Vue2 与 Vue3 指令差异

指令 Vue2 特性 Vue3 特性
v-model 组件中需用 value prop + input 事件 统一为 modelValue prop + update:modelValue 事件
v-slot 需与 slot 属性配合 完全替代 slot,支持动态插槽名
v-memo 新增,优化性能
v-on 支持 .native 修饰符 移除 .native,组件事件需显式声明

三、面试高频考点

  • v-ifv-show 的选择依据

    • 频繁切换用 v-show(切换开销低)

    • 条件不常变化或初始条件为 false 用 v-if(初始渲染开销低)

  • v-forkey 的作用

    • 帮助 Vue 的 diff 算法识别节点唯一性,避免不必要的 DOM 操作

    • 不推荐用 index 作为 key(数组排序 / 过滤时会导致节点复用错误)

  • v-model 的实现原理

    • 本质是语法糖:v-bind 绑定 value + v-on 监听 input 事件

    • 示例<input v-model="val"> 等价于 <input :value="val" @input="val = $event.target.value">

  • v-prev-cloak 的区别

    • v-pre:跳过编译,直接显示原始内容(主动不编译)

    • v-cloak:编译完成前隐藏内容(等待编译完成后显示)

  • v-memo 的适用场景

    • 仅推荐用于 1000+ 项的长列表,普通场景使用会增加内存开销

最后

本节我们从虚拟 DOM 到状态管理,通过 12 个核心模块的深度对比,彻底理清了 Vue 2 和 Vue 3 的技术脉络。掌握这些不仅能帮你应对面试,更能让你在技术选型和项目重构时做出更优决策。

Vue 系列暂时告一段落,下个系列我们将进入全新的【常用算法主题】,敬请期待!

觉得有用的话别忘了点赞收藏~ 你认为 Vue 3 最大的痛点或最香的特性是什么?欢迎在评论区留言讨论!

更多

💻 Vue3 多端统一开发框架:vue3-multi-platform

📊 HuggingFaceAI论文智能分析系统:ai-paper-analyzer

相关推荐
天天摸鱼的java工程师4 分钟前
如何实现数据实时同步到 ES?八年 Java 开发的实战方案(从业务到代码)
java·后端·面试
iccb10136 分钟前
独立开发在线客服系统 5 年,终于稳如老狗了:记录我踩过的坑(一)
面试
南北是北北7 分钟前
一、Kotlin Flow源码结构
面试
Java水解9 分钟前
Java开发实习超级详细八股文
java·后端·面试
似水流年流不尽思念11 分钟前
描述一下 Spring Bean 的生命周期 ?
后端·面试
GISBox37 分钟前
GISBox支持WMS协议的技术突破
vue.js·json·gis
顾林海38 分钟前
网络江湖的两大护法:TCP与UDP的爱恨情仇
网络协议·面试·性能优化
Juchecar38 分钟前
Vue3 v-if、v-show、v-for 详解及示例
前端·vue.js
小高00744 分钟前
⚡️ Vue 3.5 正式发布:10× 响应式性能、SSR 水合黑科技、告别 .value!
前端·javascript·vue.js
撰卢1 小时前
总结一下vue3的组件之间数据转递,子组件传父组件,父组件传子组件
前端·javascript·vue.js