Skip to content

MVC 架构模式 (Model-View-Controller)

1. 核心概念与特性

MVC (Model-View-Controller) 是软件工程中最经典、历史最悠久的架构设计模式之一。它的核心思想是**“关注点分离 (Separation of Concerns)”**:将应用程序的内部逻辑、用户界面和用户输入控制这三个维度强制性地分离开来。

在前端发展的早期(如 Backbone.js 时代),MVC 模式被广泛采用,它为混乱的“意大利面条式” DOM 操作代码带来了秩序。

核心模块中文名称职责与特性
Model模型 (数据层)应用程序的心脏。负责管理业务数据、状态以及所有的业务逻辑规则。它不关心数据是如何展示的,只负责数据的增删改查。当数据发生变化时,它通常会广播事件通知其他部分。
View视图 (表现层)应用程序的面孔。负责将 Model 中的数据渲染到屏幕上(通常是 HTML/DOM)。它本身是被动的,只负责展示,不包含任何复杂的业务计算逻辑。
Controller控制器 (逻辑层)应用程序的大脑 (协调者)。它是 View 和 Model 之间的粘合剂。负责接收用户的输入(如点击、键盘事件),解析这些输入,然后调用 Model 执行业务逻辑,最后可能还会决定让哪个 View 进行更新。

1.1 MVC 的经典数据流向

传统的 MVC 数据流向是单向的闭环:

  1. 用户在 View 上进行操作(如点击按钮)。
  2. Controller 拦截到这个操作,理解用户的意图,并向 Model 发送修改数据的指令。
  3. Model 执行业务逻辑,更新自身的数据状态。
  4. Model 数据更新后,触发事件通知 View
  5. View 收到通知,从 Model 中拉取最新数据并重新渲染屏幕。

(注意:在不同的框架和演进阶段,MVC 的具体流向可能会有变种,比如 View 直接向 Controller 报告,Controller 再去更新 View。)

2. 原生 JavaScript 模拟实现 MVC

为了深刻理解 MVC,我们用纯原生 JavaScript 实现一个最简单的“计数器”应用。

2.1 Model 层 (数据与业务逻辑)

Model 是唯一拥有状态的地方。为了能通知 View 刷新,我们需要实现一个简单的发布-订阅 (Observer) 机制。

js
class CounterModel {
  constructor() {
    this.count = 0; // 核心数据
    this.listeners = []; // 订阅者列表 (通常是 View)
  }

  // 业务逻辑:增加计数
  increment() {
    this.count += 1;
    this.notifyObservers(); // 数据变了,通知所有人
  }

  // 业务逻辑:减少计数
  decrement() {
    this.count -= 1;
    this.notifyObservers();
  }

  // 获取当前数据
  getCount() {
    return this.count;
  }

  // 注册观察者
  addObserver(callback) {
    this.listeners.push(callback);
  }

  // 广播通知
  notifyObservers() {
    this.listeners.forEach(callback => callback(this.count));
  }
}

2.2 View 层 (纯粹的渲染)

View 接收 DOM 元素,并提供渲染数据的方法。它也负责将用户的 DOM 事件委托给 Controller。

js
class CounterView {
  constructor() {
    // 假设 HTML 中有这三个元素:<div id="app"> 包含 <span id="value">, <button id="inc">, <button id="dec">
    this.app = document.getElementById('app');
    this.valueDisplay = document.getElementById('value');
    this.btnInc = document.getElementById('inc');
    this.btnDec = document.getElementById('dec');
  }

  // 唯一的渲染方法
  render(count) {
    this.valueDisplay.textContent = count;
  }

  // 将 DOM 事件绑定暴露给 Controller
  // View 自身不知道点击后该干嘛,它只负责喊 Controller 过来干活
  bindIncrement(handler) {
    this.btnInc.addEventListener('click', handler);
  }

  bindDecrement(handler) {
    this.btnDec.addEventListener('click', handler);
  }
}

2.3 Controller 层 (居中协调)

Controller 把 Model 和 View 实例化,并将它们连接起来。

js
class CounterController {
  constructor(model, view) {
    this.model = model;
    this.view = view;

    // 1. 初始化:让 View 订阅 Model 的变化
    // 当 Model 数据改变时,触发 View 的 render 方法
    this.model.addObserver(this.view.render.bind(this.view));

    // 2. 将 Controller 的业务动作绑定到 View 的 DOM 事件上
    this.view.bindIncrement(this.handleIncrement.bind(this));
    this.view.bindDecrement(this.handleDecrement.bind(this));

    // 3. 初始渲染屏幕
    this.view.render(this.model.getCount());
  }

  // 协调逻辑:收到 View 的点击通知,去命令 Model 修改数据
  handleIncrement() {
    this.model.increment();
  }

  handleDecrement() {
    this.model.decrement();
  }
}

// === 启动应用 ===
const appModel = new CounterModel();
const appView = new CounterView();
const appController = new CounterController(appModel, appView);

3. MVC 在前端的没落与演进

虽然 MVC 理念极其伟大,但纯正的 MVC 在现代前端(SPA 时代)几乎已经绝迹。

3.1 为什么前端放弃了纯 MVC?

  1. 数据流混乱(双向依赖噩梦):在复杂的业务中,往往是多个 Model 对应多个 View。View 可以修改 Model,Model 也能更新 View。当项目变大时,数据流向会变成一张极其复杂的“蜘蛛网”,只要改一个数据,很容易引发不可预知的级联更新甚至死循环。
  2. Controller 极其臃肿(Fat Controller):前端有海量的 DOM 操作和事件监听。Controller 作为中间人,很快就会被塞满各种恶心的 DOM 查找和事件绑定代码,变得难以维护。

3.2 从 MVC 到 MVVM 的伟大进化

为了解决 DOM 操作繁琐和 Controller 臃肿的问题,前端演进出了 MVVM (Model-View-ViewModel) 模式(以 Vue.js 为代表)。

  • 本质区别:MVVM 彻底消灭了 Controller,引入了 ViewModel
  • 数据绑定 (Data Binding):ViewModel 内部实现了一个极其强大的双向数据绑定引擎(例如 Vue 的响应式系统)。
  • 降维打击:开发者再也不需要像上面的原生代码那样手动写 addEventListener,也不需要手动调用 view.render()。只要 Model 里的数据一变,ViewModel 自动帮你把 DOM 刷新;只要你在输入框打字,ViewModel 自动帮你把数据存进 Model。这种**“声明式渲染”**彻底解放了前端生产力。

4. 常见问题 (FAQ) 与避坑指南

4.1 经典面试题:MVC、MVP 和 MVVM 到底有什么区别?

    • MVC:Model-View-Controller。单向/复杂的网状数据流。Controller 负责手动接收事件、手动更新 Model、手动调 View 渲染。代码耦合度依然较高。
    • MVP:Model-View-Presenter。MVC 的变种,切断了 View 和 Model 的直接联系。View 和 Model 只能通过 Presenter(中介)进行通信,解耦更好。但 Presenter 依然要写大量的模板代码来手动同步数据。
    • MVVM:Model-View-ViewModel。终极形态。通过自动化、黑盒的双向数据绑定(Data-Binding),彻底砍掉了手动同步数据的脏活累活。数据驱动视图,视图同步数据。

4.2 现在流行的 React 是 MVC 还是 MVVM?

  • :这是一个极具争议的话题,但官方给出的答案是:React 既不是 MVC,也不是 MVVM。它只是一个构建用户界面的 V (View 库)。
    • React 本身只关注视图的渲染(组件化、虚拟 DOM)。
    • 如果你非要给它套上架构模式,React 社区推崇的是 Flux / Redux (单向数据流) 架构。在 Redux 中,Action (动作) -> Dispatcher (分发) -> Store (数据/Model) -> View (视图)。数据永远只能单向流动,彻底解决了传统 MVC 晚期数据流混乱的“蜘蛛网”问题。

4.3 既然前端不用纯 MVC 了,我们还要学习它吗?

  • 绝对必须学习。
    • 服务端霸主:MVC 在前端虽然衰落,但在后端(Node.js, Java Spring, Ruby on Rails)依然是绝对的霸主统治地位。后端没有繁琐的 DOM 状态同步问题,MVC 架构能够极其清晰地划分数据库操作 (Model)、接口路由 (Controller) 和返回给前端的数据结构 (View)。
    • 底层思维:前端的 MVVM 本质上就是 MVC 思想的延续和升华。如果你不懂 MVC 分离“数据”与“表现”的初衷,你就不可能真正写出高内聚、低耦合的 Vue/React 组件代码。