Hi,我是石小石~
现代前端开发中,Vue 已成为最受欢迎的框架之一。其核心设计理念与 MVVM(Model-View-ViewModel) 架构密不可分。理解 MVVM 及 Vue 的响应式系统,可以帮助我们更高效地构建和优化前端应用。
本文将从 Vue
的底层机制与核心原理出发,深入解析 MVVM
架构及其响应式系统的实现原理。
MVVM 架构概述
MVVM(Model-View-ViewModel) 是一种用于构建用户界面的架构模式,用于现代的前端开发框架(Vue、Angular)。它通过 数据绑定 和 视图模型 提供了高效的 UI 更新和数据同步机制。
MVVM 模式主要由Model(模型)、View(视图)、ViewModel(视图模型)三个部分组成。
- Model表示程序的核心数据和业务逻辑,它不关心用户界面,只负责数据的获取、存储和处理,并提供与外界交互的接口。
- View负责展示数据和用户交互,简单来说他就是我们看到的UI 组件或 HTML 页面。
- ViewModel是连接
View
和Model
的桥梁,它不直接操作视图或模型,而是通过数据绑定将两者连接起来。
参考下面的示例哦:
js
<div id="app">
<input v-model="message"/>
<p>{{ computedValue }}</p>
</div>
<script setup>
const message = ref('Hello, MVVM!');
const computedValue = computed(()=> {
return "用户输入值变为:" + message.value
})
</script>
上述代码展示了一个输入框,当用户输入内容的时候,输入框下面的计算值会随之变化。在这个示例中,message
变量属于 Model
,它包含了应用的核心数据。输入框与页面展示就属于View,负责展示数据和用户交互。computed
和 v-model语法糖
作为 ViewModel
,用于更新视图和数据。

Vue 双向数据绑定的实现原理
Vue 实现双向数据绑定的核心是通过响应式系统 的 数据劫持 和 观察者模式来实现的。
数据劫持
Vue 2.x 使用 Object.defineProperty
对数据对象的每个属性递归添加 getter/setter
,当数据的属性被访问时,触发 getter
,当属性被修改时,触发 setter
通知视图进行更新。通过这种方式,Vue 可以监控数据的变化,并在数据变化时通知视图更新。
Vue 3.x 使用 Proxy通过代理对象拦截整个对象的操作,无需递归初始化所有属性,性能更好。
观察者模式
Vue 的响应式系统通过 观察者模式 来实现数据与视图的同步更新,简化的流程如下:
- 依赖收集 :当 Vue 组件的视图模板渲染时,它会读取数据对象的属性(例如
{{ message }}
)。在读取属性时,getter
方法会将视图组件与该数据属性建立依赖关系。

- 观察者(Watcher) :每个依赖的数据都会对应一个观察者。观察者的作用是监听数据的变化,一旦数据发生变化,观察者会收到通知,进而触发视图的更新。

- 通知视图更新(Notify View Update) :当数据通过
setter
修改时,Vue 会触发相应的观察者,通知相关的视图组件更新。

通过这种方式,Vue 可以监控数据的变化,并在数据变化时通知视图更新。
Vue 模板编译流程
Vue 的模板编译过程是将开发者编写的模板语法(例如 {{ message }}
和 v-bind
等)转换为 JavaScript 代码的过程。它主要分为三个阶段:模板解析 、AST优化 和 代码生成。
模板解析
Vue 使用其解析器将 HTML 模板转换为 抽象语法树(AST) 。在这个阶段,Vue 会分析模板中的标签、属性和指令,生成一颗树形结构。每个节点表示模板中的一个元素或属性。
如:
js
<div>
<p>{{ message }}</p>
<button v-on:click="handleClick">点击</button>
</div>
被解析成的 AST 类似于下面的结构:
js
{
type: 1, // 节点类型:1 表示元素节点
tag: 'div', // 元素的标签名
children: [ // 子节点(嵌套的 HTML 元素)
{
type: 1, // 子节点是一个元素节点
tag: 'p',
children: [
{
type: 2, // 2 表示插值表达式节点
expression: 'message' // 表达式 'message'
}
]
},
{
type: 1, // 另一个元素节点
tag: 'button',
events: { // 事件监听
click: 'handleClick' // 绑定 click 事件,执行 handleClick 方法
},
children: [
{
type: 3, // 文本节点
text: '点击' // 按钮文本
}
]
}
]
}
AST优化
Vue 在生成渲染函数前,会对 AST 进行优化。优化的核心目标是标记 静态节点,在渲染时,Vue 可以跳过这些静态节点,提升性能。
静态节点指所有的渲染过程中都不变化的内容,比如某个div标签内的静态文本
在vue3中,如果一个节点及其子树都不依赖于动态数据,那么该节点会被提升到渲染函数外部(静态提升),仅在组件初次渲染时创建。
代码生成
生成渲染函数是编译的最终阶段,这个阶段会将优化后的 AST 转换成 JavaScript 渲染函数。
例如,像这样的模板:
js
<div id="app">{{ message }}</div>
最终会生成类似这样的渲染函数:
js
function render() {
return createVNode("div", { id: "app" }, [
createTextVNode(this.message)
])
}
渲染函数的返回值是一个虚拟 DOM(VDOM)树,Vue 会根据虚拟 DOM 来更新实际的 DOM。由于渲染函数被 Vue 的响应式系统包裹,当数据发生变化时,渲染函数会被重新执行生成新的虚拟 DOM,因此页面也会实时更新。
Vue diff 算法
Vue的diff算法执行,依赖数据的的响应式系统:当数据发生改变时,set方法会让调用Dep.notify
通知所有订阅者Watcher,订阅者会重新执行渲染函数,渲染函数内部通过diff 算法用于比较新旧虚拟 DOM 树的差异,并计算出最小的更新操作,最终更新相应的视图。

Vue diff 算法流程
diff 算法的核心算法流程如下:
- 节点对比
如果新旧节点类型相同,则继续比较它们的属性。如果节点类型不同(如元素和文本节点不同),则直接替换整个节点。
- 属性更新:
如果节点类型相同,接下来检查节点的属性。对于不同的属性值进行更新,移除旧属性,添加新属性。
- 子节点比对:
对于有子节点的元素(如 div
),Vue 会使用不同的策略来优化子节点更新:
文本节点的更新:如果新旧子节点都是文本节点,直接更新文本内容。
数组类型子节点的比对 :如果新旧子节点都是数组,Vue 会通过 LIS 算法来优化节点的重新排列,避免过多的 DOM 操作。

Vue3 diff 算法做了哪些优化
- 静态标记与动态节点的区分
Vue3引入了静态标记(Static Marking)机制,通过在模板编译阶段为静态节点添加标记,避免了对这些节点的重复比较。这使得Vue3能够更高效地处理静态内容,减少不必要的DOM操作。
- 双端对比策略
Vue3的Diff算法采用了双端对比策略,即从新旧节点的头部和尾部同时开始比较,快速定位无序部分。这种策略显著减少了全量对比的复杂度,提升了性能。
- 最长递增子序列(LIS)优化
在处理节点更新时,Vue3利用最长递增子序列(LIS)算法来优化对比流程。通过找到新旧节点之间的最长递增子序列,Vue3可以减少不必要的DOM操作,从而提高更新效率。
- 事件缓存与静态提升
事件缓存:Vue3将事件缓存为静态节点,避免每次渲染时重新计算事件处理逻辑,从而减少性能开销。
静态提升:对于不参与更新的元素,Vue3将其提升为静态节点,仅在首次创建时进行处理,后续不再重复计算。
- 类型检查与属性对比
Vue3在Diff算法中增加了类型检查和属性对比功能。如果节点类型不同,则直接替换;如果类型相同,则进一步对比节点的属性,生成更新操作。
- 动态插槽的优化
Vue3对动态插槽进行了优化,通过动态节点的类型化处理,进一步提升了Diff算法的效率
总结
通过上述内容,一个清晰的Vue 组件的渲染和更新过程如下:
Vue 组件的渲染 和更新 过程涉及从模板编译 到虚拟 DOM 的构建 、更新和最终的实际 DOM 更新。下面是 Vue 组件渲染和更新的主要步骤
组件渲染过程
Vue 的组件的渲染过程核心是其模板编译过程,大致流程如下:
首先,Vue会通过其响应式系统 完成组件的 data、computed 和 props 等数据和模板的绑定,这个过程Vue 会利用 Object.defineProperty(Vue2)
或 Proxy(Vue3)
来追踪数据的依赖,保证数据变化时,视图能够重新渲染。随后,Vue会将模板编译成渲染函数 ,这个渲染函数会在每次更新时被调用,从而生成虚拟 DOM。
最终,虚拟DOM被渲染成真实的 DOM 并插入到页面中,组件渲染完成,组件渲染的过程中,Vue 会依次触发相关的生命周期钩子。
组件更新过程
当组件的状态(如 data
、props
、computed
)发生变化时,响应式数据的setter方法会让调用Dep.notify
通知所有订阅者Watcher,重新执行渲染函数触发更新。

渲染函数在执行时,会使用 diff
算法(例如:双端对比、静态标记优化等)生成新的虚拟DOM。计算出需要更新的部分后(插入、删除或更新 DOM),然后对实际 DOM 进行最小化的更新。在组件更新的过程中,Vue
会依次触发beforeUpdate
、updated
等相关的生命周期钩子。