MVVM模式

MVVM 是前端一种非常流行的开发模式,利用 MVVM 模式可以使我们的代码更专注于处理业务逻辑而不是过多关注 DOM 操作。目前著名的 MVVM 框架有 Vue 、Angular 、Knockout 等,各有优势,但是实现的思想大致上是相同的:数据绑定及视图刷新。

演化之路

  1. MVC(Model-View-Controller):

    • View(视图层)

    • Controller(业务逻辑层)

    • Model(数据层)

    流程:

    • 用户交互进行输入

    • Controller将用户输入转化为Model所需要进行的更改

    • Model中的更改结束之后,Controller通知View进行更新以表现出当前Model的状态

与观察者模式结合流程(会造成View和Model的强耦合):

  • 用户交互进行输入

  • Controller将用户输入转化为Model所需要进行的更改

  • View作为Observer会监听Model中的更新,一旦有更新事件发出,View会自动触发更新以展示最新的Model状态

  1. MVP(Model-View-Presenter):

    • View(视图层)

    • Controller(视图与数据的中间层)

    • Model(数据层)

    流程(View与Model解耦):

    • 用户交互进行输入

    • View将用户输入转化为发送给Presenter

    • Presenter控制Model的更新

    • Model将更新之后的值返回给Presenter

    • Presenter将更新之后的模型返回给View

  1. MVVM(Model-View-ViewModel):

    • View(视图层)

    • ViewModel(视图与数据的中间层)

    • Model(数据层)

    流程(View的无状态和数据绑定):

    • 用户交互进行输入

    • View将数据直接传送给ViewModel,ViewModel保存这些状态数据

    • 在有需要的情况下,ViewModel会将数据传送给Model

    • Model在更新完成之后通知ViewModel

    • ViewModel从Model中获取最新的模型,并且更新自己的数据状态

    • View根据最新的ViewModel的数据进行重新渲染

MVC、MVP和 MVVM 的总结

此类模型都包含如下三个方面:

  • Model:负责业务逻辑相关的数据与实现对数据的操作

  • View:负责将数据渲染展示给用户,并且响应用户输入

  • Controller/Presenter/ViewModel:往往作为Model与View之间的中间件,接收View传来的用户事件并且传递给Model,同时利用从Model传来的最新模型控制更新View

演化缘由:

  MVC模式在一些小型项目或者简单的界面上仍旧有极大的可用性,但是在现代富客户端开发中导致职责分割不明确、功能模块重用性、View的组合性较差。作为继任者MVP模式分割了View与Model之间的直接关联,MVP模式中也将更多的ViewLogic转移到Presenter中进行实现,从而保证了View的可测试性。而最年轻的MVVM将ViewLogic与View剥离开来,保证了View的无状态性、可重用性、可组合性以及可测试性。

演化核心:

  随着View层日趋复杂,有必要将ViewLogic与View的分离,使开发者只需关注业务逻辑本身,而不需要过多考虑DOM操作及数据视图同步问题。

MVVM 模式的框架实现

1.Vue框架

VM(ViewModel)主要实现了两件事:

  • M 到 V 的映射(Data Binding):

    • VM初始化时可通过模板引擎实现,不同框架有不同的实现方式,简单原理如下:
    
        // template
        var template = '<p>{{ text }}</p>';
        // data
        var data = {
          text: 'This is demo'
        };
        // magic process
        modelToView(template, data); // '<p>This is demo</p>'
    
    
    • 当Model发生变化时,Vue的实现方式是对数据(Model)进行劫持,数据会触发劫持时绑定的方法,对视图进行更新。具体来说,当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。当数据发生变化时,Observer 中的 setter 方法被触发,setter 会立即调用Dep.notify(),Dep订阅器开始遍历所有的订阅者,并调用订阅者的 update 方法,订阅者收到通知后对视图进行相应的更新。
  • V 到 M 的事件监听 (DOM Listeners)

    View 到 Model 主要在于解析模板指令,将模板中的属性替换成数据,并将每个指令对应的节点绑定listener函数,添加监听节点的订阅者,一旦节点属性有变动,收到通知,更新model

总体来说,Model到View需要模板引擎来实现,同样View到Model需要解析器来实现,而当View及Model改变时要触发相应的Model及View改变,这需要引入发布-订阅模式和观察者模式来实现。整体流程图如下:

2.Angular框架

  Angular框架的MVVM实现原理与Vue基本一致,在数据绑定上有所不同,Vue采用Object.defineProperty来进行数据劫持,而Angular采用脏检查机制,具体实现如下: Angular 在 scope 模型上设置了一个 监听队列,用来监听数据变化并更新 view 。每次绑定一个数据到 view 上时,就会往 $watch 队列里插入一条 $watch,用来检测它监视的 model 里是否有变化。当浏览器接收到可以被 angular context 处理的事件时(即UI事件,ajax请求或者 timeout 延迟事件),$digest 循环就会触发,遍历所有的 $watch,最后更新 dom。

参考链接

  • MVVM:

    https://zhuanlan.zhihu.com/p/26799645

    https://www.zhihu.com/question/36929146

    https://segmentfault.com/a/1190000004847657#articleHeader8

  • Vue双向绑定:

    https://segmentfault.com/a/1190000006599500

    https://github.com/DDFE/DDFE-blog/issues/7

  • Angluar双向绑定:

    http://www.ituring.com.cn/article/39865

    https://github.com/xufei/blog/issues/10

  • API of the DefineProperty

    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty