什么是 MVVM 模型?🤔
🤔(好熟悉的表情包,又cp脑了)
MVVM(Model-View-ViewModel)是一种设计模式,用来组织前端项目。
- Model:数据层(业务数据,如 data)
- View:视图层(页面 UI,如 template)
- ViewModel:连接 Model 和 View 的桥梁(Vue实例,负责数据响应和 DOM 渲染同步)
在 Vue 里,ViewModel 其实就是你创建的那个 Vue实例,负责:
- 让 Model 改变时,View 自动刷新
- 让 View(比如输入框)变化时,Model 自动更新
MVVM在Vue源码中是怎么实现的?(以Vue2为例)⚙️
Vue2 核心的 MVVM 流程:
| 步骤 | 关键组件 | 源码核心逻辑 |
|---|---|---|
| 1. 观察数据变化(数据劫持) | Observer | 使用 Object.defineProperty 劫持 data 中的每个字段,给每个字段挂 getter/setter |
| 2. 收集依赖 | Dep(依赖收集器) | getter 里收集 “谁用到了我” |
| 3. 响应式更新 | Watcher(订阅者) | 数据变了,通知依赖它的 Watcher,重新渲染或执行回调 |
| 4. 模板编译 | Compiler(编译器) | 解析模板里的指令(如 v-model、{{}}),把它们和 Watcher 绑定起来 |
核心源码片段(简化版)📄
Observer(数据劫持)
function defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
dep.depend(); // 依赖收集
return val;
},
set(newVal) {
if (newVal !== val) {
val = newVal;
dep.notify(); // 通知更新
}
}
});
}
Dep(依赖收集器)
class Dep {
constructor() {
this.subs = []; // 存放 Watcher
}
depend() {
if (Dep.target) {
this.subs.push(Dep.target);
}
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
Dep.target = null; // 当前正在依赖收集的 Watcher
Watcher(订阅者)
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this; // 设置当前 Watcher
this.value = vm[key]; // 触发 getter,收集依赖
Dep.target = null;
}
update() {
const newVal = this.vm[this.key];
this.cb(newVal);
}
}
✨ Vue用 Object.defineProperty + 发布订阅模式,完成了 MVVM 的双向绑定。****
Vue3变化:用Proxy替代了 Object.defineProperty 🚀
- Object.defineProperty只能劫持对象已有的属性,新增/删除属性无法监控
- Proxy可以直接拦截整个对象(包括动态增加、删除属性)
- 效率更高,功能更强大
所以 Vue3 重构了响应式系统,核心是 Proxy + Reflect。
Vue3 响应式系统的三大支柱:
| 组件 | 作用 | 源码核心 |
|---|---|---|
| reactive | 代理数据对象 | 使用 Proxy 劫持 |
| effect | 包裹副作用函数(视图更新) | 自动收集依赖、触发更新 |
| 依赖收集(Dep表) | 追踪哪些函数依赖哪些数据 | 用 WeakMap 管理 target -> key -> effectList |
reactive:把对象变成响应式 🧩
reactive 用 Proxy 包了一层。
- 监听 get:收集依赖
- 监听 set:触发依赖
➡️ 源码核心(超简化版):
function reactive(target) {
return new Proxy(target, {
get(obj, key) {
track(obj, key); // 收集依赖
return Reflect.get(obj, key);
},
set(obj, key, value) {
const result = Reflect.set(obj, key, value);
trigger(obj, key); // 触发更新
return result;
}
});
}
effect:注册副作用函数🧩
“副作用函数”就是:当依赖的数据变化时,要重新执行的函数。 比如更新 DOM、重新计算数据。
import { effect } from 'vue'
effect(() => {
console.log(state.count);
})
-
effect会把传入的函数注册到”依赖表”里
-
并且立即执行一次收集依赖
➡️ 源码核心(超简化版):
let activeEffect = null;
function effect(fn) {
activeEffect = fn;
fn(); // 立即执行,收集依赖
activeEffect = null;
}
依赖收集:track & trigger 🧩
Vue3用了一个超级巧妙的依赖收集结构:
const targetMap = new WeakMap();
// WeakMap: target -> Map(key -> Set(effects))
-
track() 收集谁依赖了这个属性
-
trigger() 通知谁需要更新
➡️ 源码超简化版:
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let deps = depsMap.get(key);
if (!deps) {
deps = new Set();
depsMap.set(key, deps);
}
deps.add(activeEffect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const deps = depsMap.get(key);
if (deps) {
deps.forEach(effectFn => effectFn());
}
}
数据对象 (Proxy)
↙️ get(key) 时 -> track() -> 收集 effect
↘️ set(key) 时 -> trigger() -> 触发所有 effect
effect(fn)
↘️ 内部读取了哪些 key → 这些 key 就和 effect 绑定
最终的完整结构长这样:
targetMap (WeakMap)
└── target (数据对象)
└── key (属性)
└── deps (Set of effect 函数)
Vue3 响应式系统 = Proxy 劫持 + WeakMap 依赖追踪 + effect 函数机制。****
总结重点:Vue2 vs Vue3 响应式底层的巨大变化 🔚
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 劫持方式 | Object.defineProperty | Proxy |
| 深层次劫持 | 需要递归所有属性 | 懒劫持,按需触发 |
| 新增/删除属性 | 需要手动处理 $set | 天生支持 |
| 性能 | 较差(大量getter/setter对象) | 更好(只有一个Proxy对象) |
| 内存管理 | 不能回收 | WeakMap自动回收 |