MVC 与 MVVM 架构模式

【面试速答版】

Q1: “MVC 是什么?它的三个组成部分分别负责什么?”

MVC 是 Model-View-Controller 的缩写,是一种软件架构模式,把应用拆为三层:Model(模型)——管理数据和业务逻辑,数据变化时通知 View;View(视图)——负责 UI 展示,监听用户操作并转发给 Controller;Controller(控制器)——接收用户输入,调用 Model 更新数据,决定哪个 View 来展示结果。数据流是单向的:用户操作 → Controller → Model → View。早期前端框架(Backbone.js)和大部分后端框架(Spring MVC、ASP.NET MVC)都采用这种模式。MVC 的核心思想是分离关注点——让数据、UI、控制逻辑各自独立,方便维护和测试。

Q2: “MVVM 是什么?它和 MVC 的核心区别是什么?”

MVVM 是 Model-View-ViewModel 的缩写,是 MVC 在前后端分离时代的演进版本。核心变化:用 ViewModel 替代 Controller,ViewModel 通过双向数据绑定自动同步 View 和 Model——不需要手写 Controller 的同步代码。Vue 是 MVVM 的典型实现(Vue 实例充当 ViewModel,template 是 View,data 是 Model)。核心区别:MVC 中 Controller 需要手动操作 View 和 Model,MVVM 中 ViewModel 通过数据绑定自动同步,开发者写更少的”胶水代码”。

Q3: “现代前端框架(React、Vue、Angular)分别属于 MVC 还是 MVVM?”

严格来说,Vue 和 Angular 属于 MVVM——它们有双向绑定(v-model[(ngModel)]),视图和数据通过 ViewModel 自动同步。React 既不是 MVC 也不是 MVVM——它是”View 层”的纯 UI 库,强调的是”UI = f(state)”的函数式思想,没有 Controller 或 ViewModel 的概念,状态管理靠 Hooks/Redux 自行组织。但如果在 React 中使用 Redux 组织数据流,可以看作借鉴了 MVC 的”单向数据流”思想(View → Action → Reducer → Store → View)。总体来说,MVC/MVVM 是指导框架设计的模式,但现代框架往往结合了多种模式的优点。

【深入理解版】

1. 这个知识点要解决什么问题?

假设你要开发一个”用户列表”页面:从服务器获取用户数据,展示在表格中,点击”删除”按钮可以移除用户。

没有架构模式时,代码可能写成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 全部写在一起,数据和 UI 混在一起
const userList = []
const table = document.getElementById('user-table')

function loadUsers() {
fetch('/api/users')
.then(r => r.json())
.then(data => {
userList.length = 0
userList.push(...data)
renderTable()
})
}

function renderTable() {
table.innerHTML = userList.map(user => `
<tr>
<td>${user.name}</td>
<td>${user.email}</td>
<td><button onclick="deleteUser(${user.id})">删除</button></td>
</tr>
`).join('')
}

function deleteUser(id) {
fetch(`/api/users/${id}`, { method: 'DELETE' })
.then(() => {
const idx = userList.findIndex(u => u.id === id)
userList.splice(idx, 1)
renderTable()
})
}

loadUsers()

这个代码的问题:数据(userList)、展示(renderTable)、控制逻辑(loadUsers/deleteUser)全部混在一个文件里。如果需求增加——增加搜索、分页、批量删除——所有代码都在一个文件中膨胀,改一个功能可能影响其他功能。MVC 和 MVVM 就是用来把这些问题拆开的——让数据、展示、控制逻辑各自独立,互不干扰。

2. 核心原理/执行过程

2.1 MVC 的工作流程

MVC 把应用拆为三个角色:

1
2
3
4
5
6
7
View(视图)—— 用户看到的界面
↓ 用户操作(点击、输入)
Controller(控制器)—— 接收输入,调度逻辑
↓ 调用
Model(模型)—— 数据和业务逻辑
↓ 通知数据变化
View 更新

用 MVC 模式重写上面的用户列表例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// Model —— 管理数据和业务逻辑
class UserModel {
constructor() {
this.users = []
this.listeners = [] // 订阅者(通常是 View)
}

subscribe(listener) {
this.listeners.push(listener)
}

notify() {
this.listeners.forEach(fn => fn(this.users))
}

async fetchUsers() {
const res = await fetch('/api/users')
this.users = await res.json()
this.notify()
}

async deleteUser(id) {
await fetch(`/api/users/${id}`, { method: 'DELETE' })
this.users = this.users.filter(u => u.id !== id)
this.notify()
}
}

// View —— 负责 UI 渲染(不包含业务逻辑)
class UserView {
constructor() {
this.table = document.getElementById('user-table')
}

render(users) {
this.table.innerHTML = users.map(user => `
<tr>
<td>${user.name}</td>
<td>${user.email}</td>
<td><button data-id="${user.id}" class="delete-btn">删除</button></td>
</tr>
`).join('')
}
}

// Controller —— 接收用户操作,协调 Model 和 View
class UserController {
constructor(model, view) {
this.model = model
this.view = view

// Model 变化后通知 View 更新
this.model.subscribe((users) => this.view.render(users))

// 监听 View 中的用户操作
document.addEventListener('click', (e) => {
if (e.target.classList.contains('delete-btn')) {
const id = Number(e.target.dataset.id)
this.model.deleteUser(id)
}
})
}

async init() {
await this.model.fetchUsers()
}
}

// 启动
const model = new UserModel()
const view = new UserView()
const controller = new UserController(model, view)
controller.init()

MVC 的好处是职责清晰:Model 只管数据和业务逻辑(fetchUsersdeleteUser),View 只管 UI(render),Controller 负责”接线”——Model 和 View 之间通过 Controller 沟通。

MVC 的问题:Controller 代码里仍然有很多”胶水代码”——每次 Model 变化后要手动调用 view.render(),每次用户操作要手动调用 model.xxx()。如果 Model 的字段特别多、View 需要更新的部分特别细,Controller 会变得非常臃肿。

2.2 MVVM 的改进

MVVM 用 ViewModel 替代 Controller,核心改进是引入数据绑定——ViewModel 中的数据和 View 之间自动同步:

1
2
3
4
5
View —— 用户界面(模板)
↓ 数据绑定(声明式)
ViewModel —— 数据和行为(自动同步)
↓ 调用
Model —— 数据和业务逻辑

ViewModel 是 MVVM 的核心:它持有 Model 的数据,通过”数据绑定”把数据”投射”到 View 上。当数据变化时,View 自动更新;当用户在 View 中操作时,数据自动同步回 ViewModel。开发者不需要写 Controller 中那些 view.render()model.deleteUser() 的接线代码。

用 Vue 这个 MVVM 框架重写用户列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<script setup>
// ViewModel — Vue 组件
import { ref, onMounted } from 'vue'

const users = ref([])

// 这些是 Model 的逻辑
async function fetchUsers() {
const res = await fetch('/api/users')
users.value = await res.json()
}

async function deleteUser(id) {
await fetch(`/api/users/${id}`, { method: 'DELETE' })
users.value = users.value.filter(u => u.id !== id)
}

onMounted(fetchUsers)
</script>

<template>
<!-- View — 声明式模板,通过数据绑定自动展示 users -->
<table>
<tr v-for="user in users" :key="user.id">
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td><button @click="deleteUser(user.id)">删除</button></td>
</tr>
</table>
</template>

对比 MVC 版本,MVVM 版本少了什么?

  • **不需要手动调用 view.render(users)**——template 中的 v-for="user in users" 就是声明式的绑定,users 变化时 View 自动更新。
  • 不需要 Controller 接线的代码——@click="deleteUser(user.id)" 直接在模板中绑定,Vue 负责把用户的点击事件映射到 ViewModel 的方法。
  • 不需要 subscribe/notify 机制——Vue 的响应式系统自动做了依赖收集和更新通知。

这就是 MVVM 的核心优势:声明式编程替代命令式接线。开发者专注于”数据是什么””用户操作后数据怎么变”,而不是”数据变化后怎么更新 UI”。

2.3 MVVM 中”数据绑定”的本质

数据绑定是 MVVM 的关键技术。以 Vue3 为例:

1
2
3
4
5
6
7
8
// 开发者的代码(声明式):
const count = ref(0)
// <p>{{ count }}</p>

// Vue 响应式系统替开发者做的事(自动的):
// 1. 编译 template 时,发现 {{ count }} 依赖了 count
// 2. 当 count.value++ 时,通知使用了 count 的 DOM 节点
// 3. 只更新这个 DOM 节点的 textContent → count 的新值

开发者不需要写 document.querySelector('p').textContent = count,这个”接线”工作被框架替代了。

3. 实际应用场景

场景 1:MVC 在后端的典型实现(Spring MVC)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
浏览器请求 /users

DispatcherServlet(前端控制器)

Controller(处理请求映射)

Service(业务逻辑层)— 类似 Model

DAO / Repository(数据访问)

返回数据

View(JSP/Thymeleaf 模板)

渲染 HTML 返回浏览器
1
2
3
4
5
6
7
8
9
10
@Controller
public class UserController {
// Controller 接收请求,调用 Model,选择 View
@GetMapping("/users")
public String listUsers(Model model) {
List<User> users = userService.findAll(); // 调用 Model
model.addAttribute("users", users); // 把数据传给 View
return "user-list"; // 返回 View 名称
}
}

场景 2:MVVM 在前端的典型实现(Vue)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- View(模板)—— 声明式描述 UI -->
<template>
<div>
<input v-model="searchQuery" placeholder="搜索" />
<ul>
<li v-for="item in filteredList" :key="item.id">{{ item.name }}</li>
</ul>
<p>总数:{{ totalCount }}</p>
</div>
</template>

<script setup>
// ViewModel — 数据和行为
const searchQuery = ref('')
const list = ref([])

const filteredList = computed(() =>
list.value.filter(item => item.name.includes(searchQuery.value))
)

const totalCount = computed(() => filteredList.value.length)
</script>

v-model 是双向绑定的体现:用户在 <input> 中输入 → searchQuery 自动更新 → filteredList 自动重新计算 → 列表自动刷新。

4. 常见误区 & 实际项目中的坑

误区 1:认为 MVC 和 MVVM 是互斥的,一个框架只能属于其中一种

现代前端框架往往混合了多种模式。Vue 常被称为 MVVM,但它的组件也可以看作”MVVM + 组件化”的混合。React 本质上不是 MVC 也不是 MVVM,但搭配 Redux 后(单向数据流 + 纯函数 reducer)可以看作借鉴了 MVC 的思想(Action → Reducer → Store → View)。框架是”工具”,模式是”指导思想”,同一个项目可以结合多种模式。

误区 2:认为 MVVM 就是”双向绑定”的代名词

双向绑定是 MVVM 的常见特征,但不是 MVVM 的全部。MVVM 的核心是 ViewModel 和 View 之间的声明式绑定——既可以是”自动双向”(如 Vue 的 v-model),也可以是”单向”(如 React 的 state + setState)。模式名称不重要,理解”让开发者少写接线代码”这个目标才是关键。

坑:MVVM 中 ViewModel 过于臃肿

在 Vue 项目中,把所有的 API 请求、数据转换、业务逻辑都写在组件(ViewModel)中,导致组件文件几千行。正确做法是把业务逻辑拆分到独立的 service 层或 composables 中,保持 ViewModel 的职责是”粘合 View 和 Model”,而不是包含所有业务逻辑。

5. 与相关知识的关联 & 对比

对比维度MVCMVVM前端框架举例
核心组件Model / View / ControllerModel / View / ViewModel
数据流单向(用户→Controller→Model→View)声明式绑定(自动同步)
接线成本高(手动操作 View 和 Model)低(框架自动绑定)
测试性Controller 和 Model 可分别测试ViewModel 可测试
典型后端实现Spring MVC、Ruby on Rails
典型前端实现Backbone.js(早期)Vue、AngularReact(不属于任一模式)
模式优点缺点
MVC职责清晰、分层明确Controller 接线代码多、View 操作分散
MVVM数据绑定减少模板代码、声明式编程复杂场景下绑定性能需关注、调试相对困难(数据自动变化难以追踪)
无模式(面条代码)快速原型难以维护、不可测试

6. 现代最佳实践(2024-2025)

  1. 理解模式的本质,不要被模式名称束缚。不管是 MVC、MVVM 还是”UI = f(state)”,核心都是”分离关注点”——把数据、展示、控制逻辑拆开。比起纠结”Vue 是不是纯 MVVM”,更重要的是你的代码是否做到了职责分离。
  2. Vue/Angular 项目中善用 ViewModel 的分层——把 API 请求、数据转换、缓存逻辑提取到独立的 service/composable 中,ViewModel 只做数据绑定和事件绑定。
  3. React 项目中借鉴 MVVM 的思想——组件是 View,Hook/Store 是 ViewModel,Server 是 Model。让组件保持”薄”(只做渲染和事件绑定),数据逻辑放在 hooks/composables 中。
  4. 测试你的 ViewModel/Controller——MVVM 的一个关键优势是 ViewModel 不依赖 DOM,可以纯 JS 测试。Vue 组件可以用 @vue/test-utils 测试,React 组件可以用 @testing-library/react 测试。

7. 常见疑问解答

Q:Vue 算严格的 MVVM 吗?

A:Vue 官方文档中曾自述是”MVVM 模式的实现”。严格来说,Vue 实例(组件)充当 ViewModel,$data 对应 Model,template 对应 View。但 Vue 也融合了其他模式——组件系统实际上是 Web Component 思想的体现,<script setup> 更接近函数式组件。可以说 Vue 是以 MVVM 为基础、融合了组件化和声明式渲染的一种”混合模式”。

Q:为什么 React 不属于 MVC 或 MVVM?

A:React 官方定义自己为”UI 库”(View 层),只解决”UI 如何根据数据渲染”这个问题。它没有内置 Controller 或 ViewModel 的概念。React 的数据流是”你告诉我 state 是什么,我帮你渲染 UI”——UI = f(state)。至于 state 怎么管理(useState、useReducer、Redux),那是开发者选择的问题,不是 React 自带的能力。所以 React 是”专注于 View 层的库”,不套用 MVC/MVVM 的分类。

Q:后端项目用 MVC,前端项目用 MVVM,为什么会有这种分化?

A:后端 MVC 中,Controller 接收 HTTP 请求,Model 处理数据,View 渲染 HTML(通常是模板引擎)。Controller 是必要的——因为 HTTP 请求需要一个”入口点”来路由和处理。前端 MVVM 中,ViewModel 替代了 Controller,因为前端应用不需要路由 HTTP 请求(这是浏览器和 Web 服务器做的事),前端需要的是”数据和界面自动同步”。两者解决的核心问题不同,所以演进出不同的模式。

关联知识点索引

  • Vue3 核心变化与 Composition API.md — Vue 作为 MVVM 框架的具体实现
  • React 核心概念.md — React 不同于 MVC/MVVM 的设计哲学
  • React vs Vue 对比.md — 框架设计理念差异