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 上

注册
// 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

注册

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 上

注册
// 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

注册

图示流程

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 上


// 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 的演进


四、总结

  • MVCView视图--Controller控制器--Model模型--View视图 单方向流程结构,目的为分离 View视图与Model模型 ,但是实际使用过程中,View视图与Model模型 分的不够彻底,依旧有耦合行为
  • MVP 为解决这个问题,重新定义了 Controller控制器Presenter中间人 ,彻底分离 View视图与Model模型 ,两者之间的交互全部由 Presenter中间人处理
  • MVVMMVP 的演进,其中 VM 是对 Presenter中间人 的部分功能的自动化提取,例如 HTML 的操作全部由 VM 来做,解放我们的双手,我们只需要关注数据的变化即可

最后说一句大家常说的:项目开发没有万金油的架构存在,需要根据实际情况不断优化调整;不同的架构适用不同的场景,能用好才是一个合格的工程师。