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)
三者对比
| 特性 | MVC | MVP | MVVM |
|---|---|---|---|
| View 与 Model | 直接通信 | 完全隔离 | 通过绑定 |
| 中间层职责 | 处理输入 | 协调双方 | 数据转换 |
| View 角色 | 主动获取数据 | 被动接收数据 | 声明式绑定 |
| 测试难度 | 较难 | 容易 | 容易 |
| 典型框架 | Backbone | 早期 Android | Vue、Angular |
关键点
- MVC:View 直接监听 Model 变化,Controller 只处理用户输入
- MVP:Presenter 是中间人,View 完全被动,便于单元测试
- MVVM:双向数据绑定,View 和 ViewModel 自动同步
- 演进关系:MVP 解决了 MVC 中 View 和 Model 耦合问题,MVVM 进一步简化了 Presenter 的手动同步工作
- 选择依据:简单应用用 MVC,需要测试用 MVP,复杂 UI 交互用 MVVM
目录