📄 本篇一句话
MVC 把界面程序切成三权分立:Model 管数据、View 管显示、Controller 管调度;MVVM 在此之上加「数据绑定」——数据一变,界面自动跟着变,连手动刷新都省了。
📝 在本章的位置
上游:5-15~5-21 的面向对象(封装、多态、组合)是 MVC 的语法基础;5-1 的「一份事实只存一处」是它的思想源头。下游:6-7/6-8 的 DOM 操作与虚拟 DOM,正是前端框架实现 MVVM 的底层机关。
一、为什么需要切分
把数据、界面、逻辑全搅在一个函数里:改个按钮颜色得通读业务代码、换个数据源得重写界面、测试无从下手(5-3 的单元测试直接没法写)。解法=按职责切三块:
二、MVC:三权分立
用户点击 → Controller(调度员)
↓ 调用
Model(数据层:查库、算账、存状态)
↓ 通知
View(显示层:拿到数据渲染界面)
- Model:管数据与业务规则——「待办有几件、怎么算逾期」。不知道界面长什么样。
- View:管显示——「列表怎么画、按钮放哪」。不含业务逻辑。
- Controller:管调度——接收输入、调 Model、选 View。
- 收益:三块各自可换可测——换皮肤只动 View;Model 摆脱界面后 5-3 的单元测试才能跑起来(测试从来不好测界面)。
- Web 框架把它制度化:Django/Rails 里 URL 路由是 C、
models.py是 M、模板是 V。
三、MVVM:绑定自动化
MVC 有个痛点:Model 变了,Controller 得手动通知 View 刷新,代码里满是「同步」杂活。MVVM(Model-View-ViewModel)加了一层 ViewModel:
- ViewModel 把界面要用的状态做成可观察对象;
- 数据绑定:View 绑定到 ViewModel 的字段——字段一变,界面自动更新,反向亦然。
Model ⇄ ViewModel(状态+绑定逻辑) ⇄ View(自动跟随)
代价是「魔法」变多:更新在框架黑盒里飞,出 bug 得穿透绑定链排查。MVC 是手工挡把,MVVM 是自动挡。
动手 Demo:同一个计数器,MVC 手动刷新 vs MVVM 绑定
# MVC 风格:Model 变了,Controller 手动通知 View 刷新
class CounterModel:
def __init__(self): self.count = 0
def controller_add(model):
model.count += 1
view_render(model.count) # 手动调 View —— 同步是杂活
def view_render(count):
print(f"[View] 当前计数:{count}")
m = CounterModel()
controller_add(m); controller_add(m) # [View] 当前计数:1 / 2
# MVVM 风格:绑定后,改数据界面自动跟(观察者模式的最小版)
class ViewModel:
def __init__(self):
self._count = 0
self.listeners = [] # 绑定的 View
@property
def count(self):
return self._count
@count.setter
def count(self, v):
self._count = v
for fn in self.listeners: fn(v) # 数据一变,自动推送
vm = ViewModel()
vm.listeners.append(lambda c: print(f"[View·自动] 计数变为 {c}"))
vm.count += 1 # [View·自动] 计数变为 1 —— 没人手动调 View
vm.count += 1 # [View·自动] 计数变为 2
看到什么算成功:两种风格输出同样的计数;第二种里没有任何一行代码去调 View——setter 一执行,界面自动跟。
读结果:@property(5-17)+观察者回调就是 MVVM 绑定的全部原理——前端框架的「响应式」就是把这套机制做到每个字段上。
30 秒实验
在浏览器控制台跑:
// Vue 无构建版最小绑定实验(任何页面 F12 即可)
// 若页面没引 Vue 可跳过;换成读这段体会绑定思想也行
let state = { count: 0 };
const binds = [];
binds.push(c => console.log("界面显示:", c));
const vm = new Proxy(state, {
set(obj, k, v) { obj[k] = v; binds.forEach(f => f(v)); return true; }
});
vm.count = 1; // 控制台立刻打出「界面显示: 1」——改数据即刷界面
vm.count = 42; // 「界面显示: 42」
看到什么算成功:只对 vm.count 赋值,没有任何「更新界面」的调用,控制台却跟着打新值——Proxy(代理)拦截赋值动作,这正是 Vue 3 响应式的核心机关。
常见误区
- 「MVC 已经过时」——它是分层思想的教学母版;今天的 MVC/MVVM/组件化都是同一原则的不同打包方式,面试与读源码都绕不开。
- 「MVVM 一定比 MVC 好」——绑定省了同步杂活,但黑盒更深;小工具用 MVC 反而一目了然。
- 「一个 Model 文件就是 MVC」——切分的是职责不是目录;把业务写进模板、把 SQL 写进控制器,文件夹叫什么都不是 MVC。
自测三问(点击展开)
1. **MVC 三块各管什么?** 答:Model 数据与业务、View 显示、Controller 接收输入做调度。 2. **MVVM 比 MVC 多了什么?** 答:ViewModel+数据绑定——数据变界面自动变,省掉手动同步。 3. **为什么分层之后单元测试才好写?** 答:Model 摆脱界面依赖,可以脱离 UI 直接测业务逻辑(5-3)。- ☑ 6-6 完成 —— 三权分立与自动挡:MVC/MVVM
- ☐ 下一节 6-7 DOM、Ajax
相关笔记
- property —— 本篇绑定机制的语法底座
- 组合关系 —— ViewModel 持有监听者
- ⌨️ 编程原理 · 索引 —— 全课路线图