以往文章
📚 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 的核心差异:
-
VUE2和VUE3的区别:宏观概览与核心变化
-
VUE2和VUE3的虚拟DOM:底层节点结构的异同
-
VUE2和VUE3的diff算法:更新效率的关键差异
-
VUE2和VUE3的组件通信方式:API 变化与新增方案
-
VUE2和VUE3获取实例对象属性的差异 :
this
指向与 API 调用 -
VUE2和VUE3的directives:生命周期钩子的变化
-
VUE2和VUE3的$nextTick:实现原理的微调
-
VUE2和VUE3的slot插槽:作用域与写法的统一
-
VUE2和VUE3的路由Router:集成与 API 差异
-
Vue2和Vue3的状态管理:Vuex 的演进与 Pinia 的兴起
-
VUE2和VUE3的Computed 和 watch:响应式 API 的精细化
-
VUE2和VUE3的内置指令 :
v-for
与v-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 中「逻辑碎片化」问题(如一个功能的代码分散在
data
、methods
、watch
中),支持逻辑复用(通过自定义 Hook),更适合大型项目。
-
-
TypeScript 支持
- Vue2 对 TS 支持较弱(需通过
vue-class-component
等库补充);Vue3 本身用 TS 重写,类型定义更完善,组合式 API 与 TS 结合更自然,开发时可获得完整的类型提示。
- Vue2 对 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 语法统一:
-
Vue2 中
v-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,且更轻量。
- Vuex(Vue2) 被 Pinia 取代,Pinia 简化了 API(无需
-
组件事件声明:
- Vue3 中组件触发的自定义事件必须在
emits
选项中声明(如emits: ['change']
),增强代码可读性和类型检查,避免与原生事件混淆。
- Vue3 中组件触发的自定义事件必须在
-
函数组件与异步组件:
- 函数组件 :Vue2 中通过
functional: true
声明;Vue3 中直接定义为纯函数(无需选项),且需用defineComponent
包裹以获得类型支持。 - 异步组件 :Vue2 中直接返回
() => import(...)
;Vue3 中必须用defineAsyncComponent
包裹(如const AsyncComp = defineAsyncComponent(() => import('./Comp.vue'))
)。
- 函数组件 :Vue2 中通过
-
h 函数导入方式:
-
Vue2 中
render
函数自动注入h
(createElement
); -
Vue3 中
h
需显式导入(import { h } from 'vue'
)。
-
-
attrs透传:
-
Vue2 中
attrs
不包含class
和style
; -
Vue3 中包含
class
和style
,由开发者决定在哪个元素上应用(通过v-bind=attrs
)
-
-
生命周期钩子重命名:
- Vue2 的
beforeDestroy
→Vue3 的beforeUnmount
;destroyed
→unmounted
,其他生命周期逻辑不变。
- Vue2 的
-
其他变更:
-
过滤器(filter) 被移除:推荐用计算属性或方法替代。
-
自定义指令钩子重命名 :Vue2 的
bind
→beforeMount
,inserted
→mounted
,update
→beforeUpdate
,componentUpdated
→updated
,unbind
→unmounted
。 -
v-if与v-for优先级 :Vue2 中
v-for
比v-if
优先级高;Vue3 中v-if
比v-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 复用,兼容选项式 APITypeScript 支持 支持较弱,需依赖第三方库(如 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']
),增强可读性和类型检查四、非兼容性变更 生命周期钩子 beforeDestroy
、destroyed
重命名为 beforeUnmount
、unmounted
(逻辑不变)过滤器(filter) 支持过滤器(如 `{{ msg format }}`) 自定义指令钩子 bind/inserted/update/componentUpdated/unbind
重命名为 beforeMount/mounted/beforeUpdate/updated/unmounted
v-if 与 v-for 优先级 v-for
优先级更高(可能导致无效循环)v-if
优先级更高(推荐在父级判断,避免循环内条件)attrs 透传 不包含 class
和style
包含 class
和style
,由开发者决定应用位置(v-bind="$attrs"
)
-
-
3:VUE2和VUE3的虚拟DOM
一、虚拟 DOM(Virtual DOM)的核心概念
虚拟 DOM 是用 JavaScript 对象模拟真实 DOM 结构的抽象表示,它包含了真实 DOM 的标签名、属性、子节点等关键信息,本质是 "对 DOM 的轻量描述"。
核心价值:作为真实 DOM 和业务逻辑之间的 "中间层",避免直接频繁操作真实 DOM(因 DOM 操作代价高,易引发重绘 / 回流),通过 "批量对比更新" 提升性能。
二、虚拟 DOM 的核心优势
1. 性能优化:减少无效 DOM 操作
-
真实 DOM 的问题:真实 DOM 不仅包含标签、属性,还内置了大量浏览器特性(如事件监听、样式计算等),直接操作(尤其是频繁操作)会触发频繁的重绘 / 回流,导致性能损耗。
-
虚拟 DOM 的优化逻辑:
- 先在 JavaScript 中对虚拟 DOM 进行修改(如增删节点、修改属性),这一步是 "内存级操作",代价极低;
- 通过 "Diff 算法" 对比修改前后的虚拟 DOM,找出 "最小变更集"(即只需要更新的部分);
- 只将 "最小变更集" 映射到真实 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 对象中(分props 、attrs 、on 等) |
扁平化到props 对象(直接包含所有属性和事件) |
优化机制 | 无编译时优化标记 | patchFlag 和shapeFlag (二进制标记节点类型,加速 Diff) |
组件相关 | componentOptions 和componentInstance |
简化为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% 以上成立)提升性能:
- 相同节点 :若两个虚拟 DOM 节点的
type
(标签名 / 组件)相同,认为它们可能只是属性或内容变化,不会完全替换; - 不跨层级移动 :节点只会在同层级移动,不会跨层级移动(如从
div
子节点移到span
子节点)。
- 相同节点 :若两个虚拟 DOM 节点的
二、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
),更灵活。
- Vue3 的
-
生态迁移:
- Vuex → Pinia 是趋势;事件总线推荐第三方库(如
mitt
)。
- Vuex → Pinia 是趋势;事件总线推荐第三方库(如

5:VUE2和VUE3获取实例对象属性的差异
简单说:
Vue2 是「实例属性随手用」,Vue3 是「实例访问需规范」,核心目的是降低耦合、提升可维护性。
Vue3 对实例属性的访问做了隔离和规范: 避免 this 滥用导致的命名冲突,强制开发者显式获取实例(更符合函数式编程思想)。
推荐用 getCurrentInstance()(组合式 API)或 defineExpose(暴露子组件属性),减少强耦合的实例访问(如 $parent)。
一、Vue2:实例属性「直接挂载」
1. 实例创建与属性挂载
-
核心逻辑 :
new Vue(options)
创建实例时,会把data
、methods
、props
等直接挂载到实例(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
访问实例(如mounted
、methods
中)。 -
跨组件访问:
-
父组件通过
$refs
获取子组件实例 :this.$refs.child
(子组件需用ref
标记)。 -
子组件通过
$parent
访问父实例 :this.$parent
(强耦合,不推荐)。
-
二、Vue3:实例属性「代理隔离」
1. 实例创建与属性挂载
-
核心逻辑:
-
Vue3 中,
createApp
创建应用实例,组件实例的用户属性(props
、setup
定义的变量) 与内置 API($emit
、$el
) 分离: -
用户属性 :通过代理对象(
Proxy
) 暴露,避免与内置 API 冲突。 -
内置 API :挂载在
getCurrentInstance()
返回的ctx
或proxy
上。
-
-
示例(
<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>
中this
为undefined
)。
-
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
更新之前。在这个钩子中,你可以获取到更新前后的虚拟节点来做一些对比和处理。参数有el
、binding
、vnode
、oldVnode
。 -
componentUpdated
:指令所在组件的VNode
及其子VNode
全部更新后调用,可用于在组件及其子组件都更新完成后执行一些操作,例如重新计算元素的尺寸等。参数与上述钩子一致。 -
unbind
:只调用一次,指令与元素解绑时调用,比如移除在bind
钩子中添加的事件监听器、清理定时器等。参数有el
、binding
、vnode
、oldVnode
。
二、Vue3 指令生命周期钩子
-
created
:在绑定元素的attribute
前或事件监听器应用前调用。和 Vue2 相比,它提供了一个更早的触发时机,可用于一些更前置的初始化操作。参数有el
(指令所绑定的元素)、binding
(包含指令相关信息的对象,如指令的值、名称等)、vnode
(当前虚拟节点)、prevVnode
(上一个虚拟节点,首次绑定为null
)。 -
beforeMount
:在元素被插入到 DOM 前调用,对应 Vue2 中的bind
钩子,但含义稍有不同,bind
更强调指令首次绑定,而beforeMount
更侧重于 DOM 插入前这个时间点。参数与created
相同。 -
mounted
:在绑定元素的父组件及其自己的所有子节点都挂载完成后调用,类似于 Vue2 中的inserted
,但更明确强调了所有子节点挂载完成这一条件。参数有el
、binding
、vnode
、prevVnode
。 -
beforeUpdate
:绑定元素的父组件更新前调用,它可以让开发者在父组件更新之前做一些准备工作,比如保存当前元素的状态等。参数有el
、binding
、vnode
、prevVnode
。 -
updated
:在绑定元素的父组件及其自己的所有子节点都更新后调用,替代了 Vue2 中的componentUpdated
钩子,含义基本一致。参数有el
、binding
、vnode
、prevVnode
。 -
beforeUnmount
:绑定元素的父组件卸载前调用,提供了在元素即将被卸载时执行清理操作的时机,比如移除事件监听器等。参数有el
、binding
、vnode
、prevVnode
。 -
unmounted
:绑定元素的父组件卸载后调用,只调用一次,用于完成一些最终的清理工作,类似于 Vue2 中的unbind
钩子,但更强调父组件卸载后这一时间点。参数有el
、binding
、vnode
、prevVnode
。
三、Vue2 和 Vue3 对比总结
-
新增钩子 :Vue3 新增了
created
、beforeUpdate
、beforeUnmount
这几个钩子,提供了更多在不同阶段进行操作的时机,使得开发者对指令生命周期的控制更加精细。 -
钩子对应关系:
-
Vue2 的
bind
对应 Vue3 的beforeMount
,不过侧重点有细微差异。 -
Vue2 的
inserted
对应 Vue3 的mounted
,Vue3 的mounted
条件更明确。 -
Vue2 的
componentUpdated
对应 Vue3 的updated
。
-

7:VUE2和VUE3的$nextTick
一、核心概念与原理
1. 异步更新队列
-
Vue 响应式更新机制: 当修改响应式数据时,Vue 不会立即更新 DOM,而是将 DOM 更新任务放入异步队列中,等同一事件循环内的所有数据变化完成后,再批量更新 DOM,以避免频繁重渲染。
JavaScriptthis.message = 'Hello'; this.message = 'World'; // 仅触发一次 DOM 更新
2. $nextTick
的作用
-
获取更新后的 DOM :
$nextTick(callback)
用于在 DOM 更新完成后执行回调函数,确保回调中能获取到最新的 DOM 状态。JavaScriptthis.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
仅支持回调函数,需手动处理异步逻辑。
JavaScriptthis.$nextTick(function() { // 回调函数方式 });
-
Vue3:
$nextTick
支持Promise
,可使用await
语法,更符合现代异步编程习惯。
JavaScriptawait 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 统一了插槽语法,废弃 slot
和 slot-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
替代slot
和slot-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
}
七、面试高频考点
-
params
和query
的区别-
params
:必须在路由配置中定义占位符(如/user/:id
),只能通过name
跳转 -
query
:无需定义占位符,生成类似?key=value
的 URL 参数,支持path
跳转
-
-
Vue2 与 Vue3 路由的主要差异
-
安装方式(
Vue.use
到createRouter
) -
组合式 API 支持
-
导航守卫的调用方式简化
-
-
导航守卫的执行顺序
- 全局前置守卫 → 路由独享守卫 → 组件内守卫 → 全局解析守卫 → 全局后置钩子
-
路由懒加载的原理
- 通过动态
import
实现组件的按需加载,减少首屏加载时间
- 通过动态
-
路由钩子中
next()
的注意事项-
Vue2 中必须调用
next()
,否则路由跳转被阻止 -
Vue3 中可直接
return
路径或false
,无需调用next()
-
10:Vue2和Vue3的状态管理
一、Vue2 状态管理:Vuex
Vuex 是 Vue2 官方推荐的状态管理库,基于 单向数据流 设计,用于集中管理组件共享状态。核心思想是将共享状态抽离为一个全局的「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
Pinia 是 Vue3 官方推荐的状态管理库,是 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
(恢复到初始值)JavaScriptuserStore.$reset() // 重置所有状态
-
解构状态 :使用
toRefs
保持响应式JavaScriptimport { 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
保持响应式。
- 答 :Vuex 用辅助函数(
-
状态管理的适用场景?
- 答:多组件共享数据(如用户信息、购物车)、跨层级组件通信、需要持久化的状态(如登录令牌)。
11:VUE2和VUE3的Computed 和 watch
一、Computed 和 Watch 的核心区别
1. 核心差异对比表
特性 | Computed | Watch |
---|---|---|
缓存机制 | 有缓存,依赖项不变时直接返回缓存值 | 无缓存,数据变化时立即执行回调 |
触发时机 | 依赖项变化后,下次访问时更新 | 数据变化时立即执行回调 |
适用场景 | 依赖多个数据计算一个新值 | 监听特定数据变化并执行副作用(如异步操作) |
异步支持 | 不支持(异步操作会导致缓存失效) | 支持(可在回调中执行异步操作) |
初始执行 | 默认不执行,访问时计算 | 可通过 immediate: true 强制初始执行 |
深度监听 | 不支持(需手动处理嵌套对象) | 支持(通过 deep: true 监听对象所有属性) |
回调参数 | 无(通过返回值获取结果) | 接收 newVal 和 oldVal (深度监听时 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:
-
创建
Computed Watcher
,并将dirty
标记设为true
-
访问计算属性时触发
getter
:-
若
dirty
为true
,重新计算值并缓存 -
将
dirty
设为false
,依赖收集(将计算属性的 Watcher 添加到依赖项的 Dep 中)
-
-
依赖项变化时,触发计算属性的 Watcher 更新,将
dirty
设为true
-
-
Watch:
-
创建
User Watcher
,若immediate: true
则立即执行回调 -
触发被监听属性的
getter
,完成依赖收集(将 Watcher 添加到 Dep 中) -
被监听属性变化时,Dep 通知 Watcher 执行回调
-
若
sync: true
,同步执行回调 -
否则将回调放入队列,通过
nextTick
异步执行
-
2. Vue3 响应式原理
Vue3 基于 Proxy 重构响应式系统,核心机制类似但更高效:
-
Computed:
-
创建
ComputedRefImpl
对象,包含getter
和setter
-
访问计算属性时触发
getter
:- 通过
track
收集依赖(将计算属性与依赖项建立关联) - 缓存计算结果,返回值
- 通过
-
依赖项变化时,通过
trigger
触发更新,标记计算属性为「脏」
-
-
Watch:
- 创建
WatchEffect
或WatchOptions
实例 - 执行
source
函数获取初始值,收集依赖 - 依赖项变化时,执行回调函数
- 支持
flush
选项控制回调执行时机(pre
/post
/sync
)
- 创建
三、高频面试考点
1. Computed
缓存的意义
-
避免重复计算,提升性能(尤其复杂计算场景)
-
对比
methods
:每次调用都会重新执行函数,而 computed 仅依赖项变化时才重新计算
2. Watch
如何监听对象属性变化?
-
深度监听(
deep: true
) :递归遍历对象所有属性,为每个属性创建依赖JavaScriptwatch: { obj: { handler(newVal) { /* ... */ }, deep: true } }
-
监听特定属性:通过路径监听
JavaScriptwatch: { 'obj.prop': { /* ... */ } }
3. Vue3 中如何使用?
-
Computed:
JavaScriptimport { computed } from 'vue' const count = ref(0) const doubleCount = computed(() => count.value * 2)
-
Watch:
JavaScriptimport { 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-text
与 v-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-for
与v-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
(文本 / 复选框 / 单选)、select
、textarea
、自定义组件 -
修饰符:
-
.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-if
与v-show
的选择依据-
频繁切换用
v-show
(切换开销低) -
条件不常变化或初始条件为 false 用
v-if
(初始渲染开销低)
-
-
v-for
中key
的作用-
帮助 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-pre
与v-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