首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
MVC、MVP、MVVM三大前端架构模式对比与工程选型实战指南
📅 2026/10/13 20:11:20
✍️ 爱科研究院
👁 阅读 3,247
在面试和技术社区里MVC、MVP、MVVM这三种架构模式几乎是绕不开的标配话题。但真正能在白板上把三者画清楚、讲明白并且能带着具体项目思考去做选型的开发者其实远比想象中少。这篇内容我会站在实际项目的角度把三种架构模式从头拆解一遍包括它们各自的由来、核心职责、代码长什么样、适合什么场景以及我在多个业务项目里踩过的坑和最终的选型思路。无论你是刚入门的新人还是正在维护老项目的中级开发者都可以把这篇当作一份「架构选型参考手册」来用。1. 为什么需要架构模式从无序到有序的第一步1.1 一个按钮背后的“事故现场”不妨先还原一个非常典型的场景一个管理后台页面页面上有一个“保存”按钮。第一次写业务的时候大家都在同一个文件里堆代码。点击按钮之后收集表单数据、调用接口、处理返回结果、刷新列表、弹提示、更新某个图表——全部用 jQuery 的$(#saveBtn).on(click, function(){ ... })一把梭塞进去。第一天没问题第一百天就有问题了。产品说保存后要滚动到顶部你说好的测试说失败时要保留已填内容你说好的后来列表还要支持批量保存你发现原方法里的第二行$(this)已经不知道指向谁了。于是你复制粘贴一个改一版页面文件从 300 行涨到 3000 行再后来谁也不敢动这个文件除了手动新增逻辑任何重构都可能引发雪崩。这不是代码能力的问题而是代码组织方式的失效。所谓“没有架构”最典型的状态就是一份代码里混着数据、状态、DOM 操作和业务规则彼此的边界全部糊在一起。当程序规模小到只有几十行时这种混乱只是难看当程序规模成长到几千行、多个人协作时混乱就会直接转变成交付速度和线上事故。1.2 架构模式解决的四个核心问题MVC、MVP、MVVM 这三套方案本质上都在解决同样四个问题职责分离数据、展示、交互逻辑各自要有清晰的归属地不能所有东西都堆在一个回调函数里。可维护性改一个功能时影响范围要尽量可控。改表单校验逻辑不应该连带把弹窗文案改坏。可测试性没有 UI 的情况下核心业务逻辑能不能被独立验证这是判断一个架构好不好的硬指标。协作效率多人同时开发时每个人改动文件的重合度越低越好。职责边界清晰了git 冲突自然减少。三种模式就是在解决这些问题时因为不同的历史背景和侧重点演化出了三种不同的职责划分方案。它们没有绝对的先进与落后只有适不适合当前场景。1.3 三种模式的基本脉络把 MVC、MVP、MVVM 摆在一起看其实能看到一条清晰的演化线索Model 和 View 之间的距离越来越远中间层承担的职责越来越重。MVC 时代Controller 是协调者Model 可以直接把状态变化推给 ViewView 也能直接读 Model所以耦合还在。MVP 时代Presenter 把 View 和 Model 彻底隔开View 只负责“长什么样”所有状态同步都通过 Presenter 中转界面这一侧变得非常“被动”。MVVM 时代ViewModel 把 View 和 Model 之间的同步工作交给了数据绑定机制。View 不再直接监听 ModelViewModel 通过响应式系统自动把数据变化映射到界面上开发者需要手写的手工同步代码大幅度减少。理解这条脉络之后再回头看具体的概念和代码思路就会清晰很多。下面我们逐步拆解。2. MVC经典中的经典也是最容易被误读的模式2.1 三个角色各管一摊MVC 最早是面向桌面应用和早期 Web 后端产生的它的三个角色分别是Model模型负责数据和业务规则。比如用户信息、订单状态、金额计算规则都属于 Model 的职责。它不关心数据最终显示成什么颜色、放在页面的哪个位置。View视图负责给用户看的东西也就是界面的渲染。在 Web 场景下早期 View 就是一个 HTML 模板或者一套操作 DOM 的代码。Controller控制器负责接收用户的输入调用 Model 去更新数据然后让 View 刷新界面。它是连接用户意图和系统行为的那根“线”。这里有一个必须说清楚的点很多人以为 MVC 就是把代码分成 Model、View、Controller 三个文件夹。这是非常片面的理解。三种模式的核心不在于“分三个类”而在于依赖方向和通信规则。MVC 的典型通信流程是用户操作 View → View 触发事件传给 Controller → Controller 修改 Model → Model 变更后通知 View → View 重新渲染。也就是说View 和 Model 之间存在直接的订阅关系Model 变化会主动通知 View。这个设计在后端渲染时代很自然因为服务端拿到数据后直接填充模板就行。2.2 早期实践中的三种变体在实际项目中很多人会发现书上讲的 MVC 落到代码里总有出入原因在于 MVC 存在多个变体第一种是经典被动 MVC也就是上面说的流程View 完全不感知 Model 的内部结构只在收到通知后调用接口获取最新数据。这种最干净但需要写大量事件订阅和反订阅代码。第二种是主动 MVCView 会主动去 Model 拉取数据。优点是可以随时获取最新状态代价是 View 对 Model 的依赖变强了。也就是耦合。第三种是 Model 直接操作 View严格来说已经是退化版。多见于早期项目为了省事Model 里写$(el).html(...)看代码的时候你会觉得自己在看连环车祸一个数据对象里到处都是 DOM 痕迹。在真实的早期前端项目中因为浏览器能力和 JavaScript 语言的限制MVC 落地时大多会退化成为类似“回调函数集中营”的形态。Controller 里塞满了 DOM 查询和事件绑定Model 的通知机制又依赖手写的事件系统代码很快就不可控了。2.3 一个最小 MVC 实现计数器用原生 JavaScript 写一个最小计数器可以直观地看到三者交互关系// Model纯数据与业务规则 class CounterModel { constructor() { this.count 0; } increment() { this.count 1; } getCount() { return this.count; } } // View负责渲染与事件采集不包含业务判断 class CounterView { constructor() { this.displayEl document.getElementById(display); this.btnEl document.getElementById(addBtn); } render(count) { this.displayEl.textContent count; } bindIncrement(handler) { this.btnEl.addEventListener(click, handler); } } // Controller协调者接收 View 的事件并更新 Model class CounterController { constructor(model, view) { this.model model; this.view view; // 初始化渲染 this.view.render(this.model.getCount()); // 绑定事件 this.view.bindIncrement(() { this.model.increment(); this.view.render(this.model.getCount()); }); } } const app new CounterController(new CounterModel(), new CounterView());这段代码里你注意几个关键位置Model 里没有document也没有按钮事件它甚至不知道自己会被显示出来。View 也很“傻”它只管把数字写进元素和把按钮的点按事件交出去不判断点击之后发生了什么。真正把事情串起来的是 Controller它把 View 的事件转化为 Model 的动作再把 Model 的结果推给 View 去刷新。这个例子能跑但它也暴露了 MVC 在纯前端场景下的本质问题Controller 逐渐会变成一个大管家。每次 UI 交互都要写一套“取数据 → 更新 Model → 通知 View → 重新渲染”逻辑一旦复杂Controller 就开始膨胀。而且 Model 通知 View 这件事在原生 JS 里需要自己实现观察者模式项目一大类似代码满天飞。2.4 MVC 的痛点与适用边界说几个我在真实项目里亲身经历的问题Controller 膨胀当页面交互多了之后Controller 会同时承担事件绑定、参数校验、状态管理、渲染调度等多重职责。表面上是三层分离实际上 Controller 变成了新的上帝对象。我见过一个表单页面Controller 文件 6000 多行没人敢重构连删除一个字段都要全局搜索三遍。View 与 Model 仍然耦合因为 View 需要订阅 Model 的通知当 Model 结构变化时View 的更新逻辑往往也得跟着改。尤其是在数据嵌套很深的情况下视图里到处是data.user.profile.avatarModel 一旦调整层级View 里的取值代码集体报错。测试依然困难Controller 和 View 绑定太紧想在 Node 环境里测试某个交互逻辑你得先模拟出一整个 DOM。写测试的成本比写业务还高团队很快就放弃测试了。适用边界MVC 比较适合数据结构稳定、渲染方式单一、交互链路短的项目。比如传统的服务端渲染页面或者形态相对固定的后台系统。如果项目已经开始大量使用前端路由、复杂状态交互、频繁局部刷新继续依赖 MVC 会寸步难行。3. MVP把可测试性放到第一位的中转方案3.1 View 与 Model 的彻底隔离MVP 的全称是 Model-View-Presenter它最大的调整就一句话View 不再与 Model 直接沟通所有交互都经由 Presenter 中转。打个比方。MVC 时代View 和 Model 像两个员工有事可以直接凑在一起讨论MVP 时代中间加了一个“项目经理” Presenter所有需求、反馈、状态同步都必须经过项目经理传达。界面不需要知道数据从哪来也不需要知道业务规则是什么它只负责一个非常纯粹的任务把 Presenter 给的数据展示出来把用户的操作转交给 Presenter。这种设计带来了一个关键收益View 可以完全被动化只暴露几个简单的接口。Presenter 只依赖这些接口而不依赖具体的 DOM 实现。3.2 被动视图与主动视图在 MVP 实践里View 有两种形态被动视图是更极致的方案。View 几乎不含任何逻辑连if (count 0)这种判断都不做所有显示条件都直接由 Presenter 算出最终结果传给它。好处是 View 可以像说明书一样被审查任何界面状态都对应唯一的数据结果坏处是 Presenter 会变得非常厚因为所有界面状态的推导逻辑都要在里面完成。主动视图允许 View 自己处理一部分展示逻辑比如表单字段的可见性切换、某些 CSS 类的拼接。Presenter 只处理业务级别的状态同步。这种方案减轻了 Presenter 的压力但也损失了一点可测试性因为 View 内部逻辑无法完全在非 DOM 环境下验证。3.3 用 MVP 实现一个待办事项列表用代码看会更直观。这里我给一个轻量的 TypeScript 风格示例重点看结构// Model纯数据层 interface Todo { id: number; text: string; done: boolean; } class TodoModel { private todos: Todo[] []; addTodo(text: string): void { this.todos.push({ id: Date.now(), text, done: false }); } toggleTodo(id: number): void { const todo this.todos.find(item item.id id); if (todo) todo.done !todo.done; } getTodos(): Todo[] { return this.todos; } } // View被动界面只暴露接口 interface TodoView { renderTodos(todos: Todo[]): void; getNewTodoText(): string; clearInput(): void; bindAddTodo(handler: (text: string) void): void; bindToggleTodo(handler: (id: number) void): void; } class DomTodoView implements TodoView { private listEl!: HTMLElement; private inputEl!: HTMLInputElement; private addBtnEl!: HTMLButtonElement; renderTodos(todos: Todo[]): void { // 只做 DOM 渲染不做任何业务判断 this.listEl.innerHTML todos.map(todo li>script setup import { ref } from vue; // ViewModel 的核心状态 const count ref(0); // 业务方法 function increment() { count.value 1; } /script template div span{{ count }}/span button clickincrement1/button /div /template对比第 2 章的原生 MVC 计数器最大的感触就是同步代码消失了。在 MVC 例子里你必须显式调用this.view.render(this.model.getCount())在 MVVM 例子里你只需要改变count.value的数值界面上的数字就会自动跟着变。这就是数据绑定的价值。4.3 MVVM 的新问题边界和过度依赖MVVM 让开发变快但也带来了新问题这里说两个我在实际项目里反复踩的坑。第一个坑ViewModel 变成万能筐。很多项目把接口请求、字段校验、权限判断、埋点上报全塞进 ViewModel 里。你打开一个组件文件上面是几十个 setup 里定义的变量中间是十几个方法底部还有 computed 和 watch文件很长令人窒息。严格来说这些内容里有相当一部分应该属于 Model 或独立的 service 层但因为绑定机制太方便大家本能地就往同一个地方堆。第二个坑状态管理中的隐式依赖。MVVM 依赖响应式系统自动追踪依赖听起来很智能但自动追踪带来的副作用是依赖关系变得隐蔽。一个父组件的状态变化可能通过 props 层层传递在一个子组件中被 watch 监听又触发另一个操作。开发者很难通过阅读代码快速判断一个界面状态是从哪来的、被谁影响。数据流反而比 MVP 更不透明。这也是为什么有经验的团队会额外引入一些约束比如单向数据流、状态管理模块化、禁止跨组件直接修改状态等。MVVM 提供了便利但没有提供纪律纪律需要团队自己建立。4.4 ViewModel 的设计边界针对“What goes where”的纠结我个人的建议是这样划分放 ViewModel 的组件级交互状态、由用户操作驱动的临时数据、UI 展示需要的派生状态。放 Model 的业务实体数据及其不可变规则比如订单状态机、金额精度换算、权限判断逻辑。放独立模块的需要跨组件共享的全局数据、需要持久化的数据、与业务实体无关但多处复用的工具逻辑。判断标准其实很简单如果一套数据规则不依赖界面存在就应该放 Model如果一套逻辑必须在界面存在时才有效放 ViewModel 更合适。比如“点击按钮后弹出确认框”是 ViewModel 级别的交互逻辑而“余额不足时禁止付款”是业务规则应该放在业务模型层由更上层统一做判断。5. 三种模式对比与理性选型5.1 一张表看清核心差异与其听我长篇大论不如直接上一张对比表把三种模式同步时的核心差异列清楚维度MVCMVPMVVM数据流方向View ↔ Controller ↔ ModelView → Presenter → ModelView ⇄ ViewModel ⇄ ModelView 与 Model 是否直接依赖是存在耦合否完全隔离否由绑定机制中转同步界面的方式手动刷新手动刷新自动数据绑定可测试性较低依赖 DOM高View 可接口化较高但依赖框架代码复杂度低结构简单高需要多写接口与中转类中模板与逻辑分离上手难度低较高中高依赖框架概念理解适合场景简单页面、传统后端渲染复杂业务、长周期维护、强测试需求快速迭代、复杂交互、现代前端组件化项目这张表格不是想告诉你“MVVM 最好其他的过时了”。真实项目里的情况永远是场景决定方案。5.2 选型判断逻辑这是我的私人思路如果你正在做一个新项目不知道怎么选不妨按下面这个决策路径走一遍第一步看项目复杂度。页面交互不复杂、几乎没有状态流转MVP 和 MVVM 都有点小题大做用最朴素的组件化结构甚至一个清晰的类也能扛住。架构是为了控制复杂度不是为了让文件结构更好看。第二步看测试要求。如果你的系统是金融、物联网、医疗这类强合规领域业务逻辑必须稳定覆盖测试MVP 比 MVVM 更占优势因为它的依赖关系最显式测试环境里最容易 mock。第三步看开发效率和团队平均水平。MVVM 对开发者的心智负担最低尤其适合需要快速铺量的业务开发团队。可测试性稍弱一点没关系可以通过接口层和代码评审补偿。第四步看维护周期。一个要维护十年以上的核心系统架构约束带来的收益比短期开发效率更大一个三个月就要上线的营销活动页用重架构纯属浪费加班时间。5.3 三个容易犯的选型误区误区一听说某个大厂用了 MVVM所以我们也必须用。大厂用 MVVM 是因为它的业务形态复杂、迭代频率高如果你的项目形态差异很大照搬反而别扭。架构选择永远基于自己的约束而不是别人的热闹。误区二用 MVP 就代表工程化程度高。工程化程度高不高看的是代码质量、测试覆盖、模块边界而不是用了哪个模式。MVP 写成一坨屎的项目我见过MVVM 写得赏心悦目的我也见过模式只是工具不是光环。误区三一个项目从头到尾只能用一种模式。没有人规定一栋楼只能用一种建材。不同模块复杂度不同完全可以混用——核心交易流程用 MVP 保测试营销展示页用 MVVM 保速度只要模块边界清晰混用并不是罪过。6. 架构迁移实战与经验复盘6.1 一次真实的迁移实验我参与过一个典型的遗留系统重构一个基于 jQuery 的报表管理页面JS 文件 4000 多行里面全部是选择器、字符串拼接和全局函数。每次新增指标都要复制一大段 HTML 拼接逻辑改个字段名必须全文搜索。我们决定迁移到 MVVM 风格的组件化方案。迁移步骤拆成了四步第一步给业务数据画边界。把页面的数据全部列出来梳理出哪些是服务端返回的原始数据哪些是界面临时状态哪些是派生数据。这一步花了最多时间但也最值得因为数据边界理清了后面的迁移就是体力活。第二步按边界拆组件。把页面按功能区拆成若干组件每个组件内部先不做数据绑定只用最基础的模板渲染替换掉手动字符串拼接。这一步跑通后页面表现完全一致但代码结构已经清晰了不少。第三步引入 ViewModel 层。把事件处理逻辑挪进组件状态和方法中替换掉原来的$(xxx).click全局绑定。每拆完一个交互就做一次回归验证。这个过程大概花了两周每天推进一部分。第四步处理跨组件数据流。原来很多数据是全局变量到处读写的迁移后改成显式的 props 传递并引入模块化的共享状态。这一步之后原来一些隐蔽的 bug 反而自己暴露出来了——因为数据流可变透明了不合理的地方一眼就能看出来。6.2 迁移中的四个常见问题第一迁移期间不要奢望零回归。新旧代码并存阶段某些状态会出现重复维护的问题。更好的做法是按业务模块切割每完成一个模块就彻底删除旧代码避免一个模块里两套逻辑长期并存。第二数据绑定不是万能药。我们在迁移表格的时候起初把所有字段都绑上响应式状态结果性能明显下降。后来对高频更新、不需要响应的数据改用普通变量只对影响界面的核心状态启用绑定性能问题立刻缓解。第三组件化不等于 MVVM。不少人在迁移时只把代码拆成了组件但组件内部依然是命令式的 DOM 操作。这样得到的是一个看起来结构化的页面内在依然混乱。MVVM 的落地必须体现在组件内部状态驱动视图而不是视图操作状态。第四警惕过度抽象。迁移时容易走向另一个极端为了让组件“复用”把通用逻辑抽了一层又一层最后改需求时要在五层抽象之间来回跳。复用是有代价的抽到什么程度应该由“未来确实有多处需要”来决定而不是提前设计癖。6.3 我的最终建议从 MVC 到 MVP 再到 MVVM整个演进过程的内核是一致的把不确定的东西隔离掉把确定的东西显式化。MVC 隔离了数据和展示的职责MVP 补上了 View 与 Model 之间的强耦合漏洞MVVM 又用数据绑定解决了同步代码的重复劳动。每一种方案都在前一种的基础上更进一步但没有哪一种能够真正“一劳永逸”。以我个人经验来说选架构最忌的是只看名气或者只图顺手。做架构决策之前先回答三个问题这个项目要活多久这个团队擅长什么出问题后排查成本高不高把这三个问题想清楚答案自己就浮出来了。如果真让我对某一种模式暧昧地投一票我会说MVVM 是当前前端项目和迭代速度下性价比最高的选择而 MVP 则是在复杂业务和长期维护语境里最值得尊敬的方案。至于 MVC它更像是一本你需要反复学习、但不必原样照搬的教科书。理解了它的本质你才能更懂后面两种模式为什么要做那些“看起来多余”的事。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/13 20:11:20
复杂 Agent 对话上下文压缩与遗忘机制:基于 Rust 滑动窗口与向量聚类的高性能状态管理
2026/10/13 20:11:20
IPD流程管理工具选型指南与2026年主流方案对比
2026/10/13 20:06:12
Vapor 模式下自定义指令的编译重塑:原生 DOM 生命周期钩子绑定实操
2026/10/13 22:26:46
5分钟快速上手 web-search-mcp:从零搭建本地 MCP 网页搜索服务器的完整教程
2026/10/13 22:26:46
AI编程工具实战:选型、提示词与工作流嵌入,告别低效代码
2026/10/13 22:26:46
AI编程工具实战:从任务拆解到代码审查的完整工作流方法论
2026/10/13 22:26:46
游戏引擎基础系统解析:内存管理设计与生命周期实践
2026/10/13 22:26:46
物理引擎与动画系统协同架构:从碰撞检测到IK的深度拆解
2026/10/13 22:21:46
频域滤波实战指南:三种低通滤波器与高通实现全解析
2026/10/13 0:00:40
物联网系统集成避坑指南:从选型到验收的关键要点
2026/10/13 0:00:40
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查
2026/10/13 0:00:40
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析
2026/10/13 12:25:09
流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南
2026/10/13 18:15:04
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别
2026/10/13 8:14:22
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容
2026/10/12 19:53:23
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/11 21:41:11
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/11 23:43:10
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)