JavaScript 中的 MVC、MVP、MVVM
架构的作用
参考文章:
https://www.jianshu.com/p/b42a26623aeb
一个项目在不断发展过程中,由简单变复杂,随之而来的是臃肿,难维护
这就需要一个不断演进的架构模式与之匹配,调整出合适的架构,适应项目的发展、增长的团队规模和团队能力,而不是固定不变的架构
所以我们需要一个好的架构模式,能快速、高效的配合团队开发好一个复杂项目
今天来简单聊一聊几种设计架构:MVC、MVP、MVVM
理论总是太苍白,看了就忘,下面我就拿一个登录注册功能的例子来说说我的理解,有错误和补充的地方还望指教
一、MVC(Model-View-Controller)
模型-视图-控制器
这里 View 其实就是 HTML
架构运行流程
1、View
用户点击触发 HTML 上的事件
- 初始显示登录页面,点击注册按钮触发事件
- Controller.js 注册了 HTML 中注册按钮的点击事件
- HTML 请求转发到控制器 Controller.js 上
<!-- View -->
<div>
<div class="main-content">
<!-- 登录页面内容 -->
</div>
<div class="register">注册</div>
</div>
// Controller.js
const register = document.getElementsByClassName('register')[0]
register.addEventListener('click', function(){
// 点击注册按钮响应事件
}, false)
2、Controller
Controller.js 操作 Model.js 的数据
- HTML 上触发点击事件,Controller.js 响应请求,执行事件逻辑,即请求修改 Model.js 数据
- Controller.js 通过 Model.js 对外暴露的接口操作数据
- 例如:展示注册页的变量设置为 true
- 设置注册页所需要的数据
// Controller.js
import Model from './Model.js'
const register = document.getElementsByClassName('register')[0]
register.addEventListener('click', function(){
// 点击注册按钮响应事件
Model.setSwitch(true)
let data = {}
Model.setRegisterData(data)
}, false)
3、Model
Model.js 修改数据
- Model.js 上存有 HTML 显示所用的数据,同时 Model.js 可以修改数据
- Model.js 对外暴露操作数据的接口
- 数据更新后, Model.js 操作 HTML 页面更新,即渲染并展示注册页面
// Model.js
export default {
constructor() {
this.showRegister = false
this.registerData = {}
}
setSwitch(val) {
this.showRegister = val
},
setRegisterData(data) {
this.registerData = data
// 因为 View 依赖 Model 的数据
// Model 不依赖 View
// View 和 Model 之间使用 观察者模式进行关联
// Model 变化之后,需要通知依赖他的 View,即使用
// View 注册的函数 updateContent 更新 View
updateContent({
showRegister: this.showRegister,
registerData: data
})
}
}
4、View
- HTML 包含了修改自身的逻辑代码,当数据变化后,用来修改 HTML
<!-- View -->
<div>
<div class="main-content">
<!-- 登录页面内容 -->
</div>
<div class="register">注册</div>
</div>
<script>
const classSelector = document.getElementsByClassName
const mainContent = classSelector('main-content')[0]
function updateContent(data) {
// 更新页面
mainContent.innerHTML = `
<div>
<div>展示注册页面:${data.showRegister}</div>
<div>注册页面内容:${data.registerData}</div>
</div>`
}
</script>
MVC 通讯模式实现
Model 和 View:
- Observer 模式,观察者模式
- View 依赖 Model,Model 不依赖 View,Model 变化之后,需要通知依赖他的 View(通知方式可以是调用 View 提供好的方法)
实际使用中,HTML 中可能还会绕过 Controller.js,直接调用 Model.js 操作数据,这样 HTML 中会包含一些业务逻辑
图示流程
userAction:
View --> Controller --> Model --> View
二、MVP(Model-View-Presenter)
模型-视图-中间人
这里 View 其实就是 HTML
架构运行流程
1、View
用户点击触发 HTML 上的事件
- 初始显示登录页面,点击注册按钮触发事件
- Presenter.js 注册了 HTML 中注册按钮的点击事件
- HTML 请求转发到 Presenter.js 上
<!-- View -->
<div>
<div class="main-content">
<!-- 登录页面内容 -->
</div>
<div class="register">注册</div>
</div>
// Presenter.js
const register = document.getElementsByClassName('register')[0]
register.addEventListener('click', function(){
// 点击注册按钮响应事件
}, false)
2、Presenter
Presenter.js 操作 Model.js 的数据
- HTML 上触发点击事件,Presenter.js 响应请求,执行事件逻辑,即请求修改 Model.js 数据
- Presenter.js 通过 Model.js 对外暴露的接口操作数据
- 例如:展示注册页的变量设置为 true
- 设置注册页所需要的数据
// Presenter.js
import Model from './Model.js'
const register = document.getElementsByClassName('register')[0]
register.addEventListener('click', function(){
// 点击注册按钮响应事件
Model.setSwitch(true)
let data = {}
Model.setRegisterData(data)
}, false)
3、Model
Model.js 修改数据
- Model.js 上存有 HTML 显示所用的数据,同时 Model.js 可以修改数据
- Model.js 对外暴露操作数据的接口
- 数据更新后, Model.js 调用 Presenter.js 暴露的接口,更新页面
// Model.js
export default {
constructor() {
this.showRegister = false
this.registerData = {}
}
setSwitch(val) {
this.showRegister = val
},
setRegisterData(data) {
this.registerData = data
// 渲染并展示注册页面
Presenter.updateRegister({
showRegister: this.showRegister,
registerData: data
})
}
}
4、Presenter
Presenter.js 调用 HTML 提供的方法更新页面
// Presenter.js
// ...
export default {
// 更新页面
updateRegister(data) {
updateContent(data)
}
}
5、View
Presenter.js 更新 HTML
- HTML 包含了修改自身的逻辑代码,当数据变化后,用来修改 HTML
<!-- View -->
<div>
<div class="main-content">
<!-- 登录页面内容 -->
</div>
<div class="register">注册</div>
</div>
<script>
const classSelector = document.getElementsByClassName
const mainContent = classSelector('main-content')[0]
function updateContent(data) {
// 更新页面
mainContent.innerHTML = `
<div>
<div>展示注册页面:${data.showRegister}</div>
<div>注册页面内容:${data.registerData}</div>
</div>`
}
</script>
图示流程
userAction:
View --> Presenter --> Model
View <-- Presenter <--
MVP 与 MVC 的区别
-
MVC 中 Model 可以与 View 直接交互
-
MVP 中 Model 与 View 完全解耦,交互完全交给 Presenter
-
上面例子中的区别在于:
- MVC 中,Model 直接调用 updateContent 修改 HTML,
- MVP 中,Model 借助 Presenter 去调用 updateContent 修改 HTML
- MVP 中多了 Presenter 这一步,分开了 Model 和 View
-
我们目的是实现
UI展示(CSS、HTML)、逻辑(UI动态交互逻辑和业务逻辑)和数据隔离开
,那么 MVC 是否能实现呢?- 不行,因为 Model 调用 View 提供的方法,两者耦合在一起了;若 View 方法改变,Model 需要随之改变
- 因为 UI 的交互逻辑是多变的,而 Model 的操作方式是不容易变的,所以 UI 的交互逻辑不应该放在 Model 中,应该提取到 Presenter 中处理
- 而 MVP 是通过 Presenter 实现 UI 的交互逻辑,若 UI 有变化,Model 不需要改动,修改 Presenter 即可
三、MVVM(Model-View-ViewModel)
模型-视图-视图模型
以 Vue.js 项目为例:
- View 其实就是 template
- ViewModel 是组件里面的 js 逻辑
- Model 既是 Vue 实例中的 data
架构运行流程
1、View
用户点击触发 template 上的事件
- 初始显示登录页面,点击注册按钮触发事件
- ViewModel 注册了 template 中注册按钮的点击事件
- template 请求转发到 ViewModel 上
<!-- View -->
<template>
<div>
<div class="main-content">
<template v-if="showRegister">
<!-- 注册页面内容 -->
</template>
<template v-else>
<!-- 登页面内容 -->
</template>
</div>
<div class="register" @click="handleRegister">注册</div>
</div>
</template>
// ViewModel
export default {
methods: {
handleRegister() {
// 点击注册按钮响应事件
}
}
}
2、ViewModel
ViewModel 操作 data 的数据
- template 上触发点击事件,ViewModel 响应请求,执行事件逻辑,即请求修改 data 数据
- ViewModel 通过 Vue.js 提供的方法修改 data
- 例如:展示注册页的变量设置为 true
- 设置注册页所需要的数据
// ViewModel
export default {
methods: {
handleRegister() {
// 点击注册按钮响应事件
this.setSwitch(true)
let data = {}
this.setRegisterData()
},
setSwitch() {},
setRegisterData() {},
}
}
3、Model
修改数据
- Model 上存有 template 显示所用的数据
- 数据更新后, Model 通过双向绑定,通知 ViewModel 更新页面
// Model.js
export default {
data() {
return {
showRegister: fasle,
registerData: {},
}
},
setSwitch(val) {
this.showRegister = val
},
setRegisterData(data) {
this.registerData = data
}
}
图示流程
userAction:
View --> ViewModel --> Model
View <-- ViewModel <--
MVVM 是 MVP 的演进
四、总结
MVC
是View视图--Controller控制器--Model模型--View视图
单方向流程结构,目的为分离View视图与Model模型
,但是实际使用过程中,View视图与Model模型
分的不够彻底,依旧有耦合行为MVP
为解决这个问题,重新定义了Controller控制器
为Presenter中间人
,彻底分离View视图与Model模型
,两者之间的交互全部由Presenter中间人
处理MVVM
是MVP
的演进,其中VM
是对Presenter中间人
的部分功能的自动化提取,例如 HTML 的操作全部由 VM 来做,解放我们的双手,我们只需要关注数据的变化即可
最后说一句大家常说的:项目开发没有万金油的架构存在,需要根据实际情况不断优化调整;不同的架构适用不同的场景,能用好才是一个合格的工程师。