MVC vs MVP vs MVVM

三种前端架构模式的区别与实现

问题

解释 MVC、MVP、MVVM 三种架构模式的区别,并给出实现示例。

解答

MVC (Model-View-Controller)

View 直接观察 Model,Controller 处理用户输入并更新 Model。

// Model - 数据和业务逻辑
class Model {
  constructor() {
    this.count = 0
    this.listeners = []
  }

  increment() {
    this.count++
    this.notify()
  }

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

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

// View - 展示层,直接监听 Model
class View {
  constructor(model) {
    this.model = model
    this.el = document.getElementById('count')
    this.btn = document.getElementById('btn')

    // View 直接订阅 Model
    model.subscribe(count => this.render(count))
  }

  render(count) {
    this.el.textContent = count
  }

  bindClick(handler) {
    this.btn.addEventListener('click', handler)
  }
}

// Controller - 处理用户输入
class Controller {
  constructor(model, view) {
    this.model = model
    this.view = view

    // 绑定事件到 Model 方法
    view.bindClick(() => model.increment())
  }
}

// 初始化
const model = new Model()
const view = new View(model)
const controller = new Controller(model, view)

MVP (Model-View-Presenter)

View 和 Model 完全隔离,所有通信通过 Presenter。

// Model - 纯数据
class Model {
  constructor() {
    this.count = 0
  }

  increment() {
    this.count++
    return this.count
  }

  getCount() {
    return this.count
  }
}

// View - 被动视图,只负责渲染
class View {
  constructor() {
    this.el = document.getElementById('count')
    this.btn = document.getElementById('btn')
  }

  render(count) {
    this.el.textContent = count
  }

  bindClick(handler) {
    this.btn.addEventListener('click', handler)
  }
}

// Presenter - 中间人,协调 Model 和 View
class Presenter {
  constructor(model, view) {
    this.model = model
    this.view = view

    // 初始渲染
    view.render(model.getCount())

    // 处理用户交互
    view.bindClick(() => {
      const count = model.increment()
      view.render(count) // Presenter 主动更新 View
    })
  }
}

const model = new Model()
const view = new View()
const presenter = new Presenter(model, view)

MVVM (Model-View-ViewModel)

通过数据绑定自动同步 View 和 ViewModel。

// 简易数据绑定实现
function reactive(obj, callback) {
  return new Proxy(obj, {
    set(target, key, value) {
      target[key] = value
      callback(key, value)
      return true
    }
  })
}

// Model - 数据
class Model {
  constructor() {
    this.count = 0
  }
}

// ViewModel - 暴露数据和方法给 View
class ViewModel {
  constructor(model) {
    this.model = model

    // 响应式数据
    this.data = reactive({ count: model.count }, (key, value) => {
      this.updateView(key, value)
    })

    this.bindElements()
  }

  bindElements() {
    this.el = document.getElementById('count')
    this.btn = document.getElementById('btn')

    this.btn.addEventListener('click', () => this.increment())
    this.updateView('count', this.data.count)
  }

  updateView(key, value) {
    if (key === 'count') {
      this.el.textContent = value
    }
  }

  increment() {
    this.data.count++ // 自动触发视图更新
    this.model.count = this.data.count
  }
}

const model = new Model()
const vm = new ViewModel(model)

三者对比

特性MVCMVPMVVM
View 与 Model直接通信完全隔离通过绑定
中间层职责处理输入协调双方数据转换
View 角色主动获取数据被动接收数据声明式绑定
测试难度较难容易容易
典型框架Backbone早期 AndroidVue、Angular

关键点

  • MVC:View 直接监听 Model 变化,Controller 只处理用户输入
  • MVP:Presenter 是中间人,View 完全被动,便于单元测试
  • MVVM:双向数据绑定,View 和 ViewModel 自动同步
  • 演进关系:MVP 解决了 MVC 中 View 和 Model 耦合问题,MVVM 进一步简化了 Presenter 的手动同步工作
  • 选择依据:简单应用用 MVC,需要测试用 MVP,复杂 UI 交互用 MVVM