学点儿技术 |MVVM架构学习笔记

Lucas Zhang
7 min readJun 5, 2018

--

MVVM有助于将图形用户界面的开发与业务逻辑或后端逻辑(数据模型)的开发分离开来。

MVVM视图模型是一个值转换器,这意味着视图模型负责从模型中暴露(转换)数据对象,以便轻松管理和呈现对象。

MVVM也被称为model-view-binder,特别是在不涉及.NET平台的实现中。

I. MVVM模式的组成部分

Ref: MVVM-Wikipedia

  1. 模型 Model: 模型是指代表真实状态内容的领域模型(面向对象),或指代表内容的数据访问层(以数据为中心)。
  2. 视图 View: 就像在MVCMVP模式中一样,视图是用户在屏幕上看到的结构、布局和外观(UI)。
  3. 视图模型 ViewModel: 视图模型是暴露公共属性和命令的视图的抽象。MVVM没有MVC模式的控制器,也没有MVP模式的presenter,有的是一个绑定器。在视图模型中,绑定器在视图和数据绑定器之间进行通信。
  4. 绑定器: 声明性数据命令绑定隐含在MVVM模式中。在Microsoft解决方案堆中,绑定器是一种名为XAML的标记语言。绑定器使开发人员免于被迫编写样板式逻辑来同步视图模型和视图。在微软的堆之外实现时,声明性数据绑定技术的出现是实现该模式的一个关键因素。

II. 比较MVCMVVM

Ref: 被误解的MVC和被神化的MVVM-唐巧@InfoQ

1. MVC

MVC,全称是 Model View Controller,是模型 (model)-视图 (view)-控制器 (controller) 的缩写。它表示的是一种常见的客户端软件开发框架

很多人试图解决 MVC 这种架构下 Controller 比较臃肿的问题。什么样的内容才应该放到 Controller 中?设计模式很多时候是为了Don’t repeat yourself。在 MVC 这种设计模式中,我们发现 ViewModel 都是符合这种原则的。但Controller 有多少可以复用的?如果我们能够意识到 Controller 里面的代码不便于复用,我们就能知道什么代码应该写在 Controller 里面了,那就是那些不能复用的代码。

这些代码包括:

  • 在初始化时,构造相应的 ViewModel
  • 监听 Model 层的事件,将 Model 层的数据传递到 View 层。
  • 监听 View 层的事件,并且将 View 层的事件转发到 Model 层。

其实 MVC 虽然只有三层,但是它并没有限制你只能有三层。所以,我们可以将 Controller 里面过于臃肿的逻辑抽取出来,形成新的可复用模块或架构层次。通过代码的抽取,我们可以将原本的 MVC 设计模式中的 ViewController 进一步拆分,构造出网络请求层ViewModel 层、Service 层、Storage 层等其它类,来配合 Controller 工作,从而使 Controller 更加简单,我们的 App 更容易维护。

2. MVVM

MVVMModel-View-ViewModel 的简写。

MVVM 在使用当中,通常还会利用双向绑定技术,使得 Model 变化时,ViewModel 会自动更新,而 ViewModel 变化时,View 也会自动变化。所以,MVVM 模式有些时候又被称作:model-view-binder 模式。

MVVM 的作者 John Gossman 的 批评 应该是最为中肯的。John GossmanMVVM 的批评主要有两点:

  1. 数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。
  2. 对于过大的项目,数据绑定需要花费更多的内存。

III. MVVCVue

Ref: Vue.jsMVVM 的小细节

Vue.js 是一个提供了 MVVM 风格的双向数据绑定的 Javascript 库,专注于View 层。它的核心是 MVVM 中的 VM,也就是 ViewModelViewModel负责连接 ViewModel,保证视图和数据的一致性,这种轻量级的架构让前端开发更加高效、便捷。

HTML5 最大的亮点是它为移动设备提供了一些非常有用的功能,使得 HTML5 具备了开发App的能力。 HTML5开发App 最大的好处就是跨平台、快速迭代和上线,节省人力成本和提高效率,因此很多企业开始对传统的App进行改造,逐渐用H5代替Native,到2015年的时候,市面上大多数App 或多或少嵌入都了H5 的页面。

既然要用H5 来构建 App, 那View 层所做的事,就不仅仅是简单的数据展示了,它不仅要管理复杂的数据状态,还要处理移动设备上各种操作行为等等。因此,前端也需要工程化,也需要一个类似于MVC 的框架来管理这些复杂的逻辑,使开发更加高效。

这时前端开发就暴露出了三个痛点问题

  1. 开发者在代码中大量调用相同的 DOM API, 处理繁琐 ,操作冗余,使得代码难以维护。
  2. 大量的DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。
  3. Model 频繁发生变化,开发者需要主动更新到View ;当用户的操作导致 Model 发生变化,开发者同样需要将变化的数据同步到Model 中, 这样的工作不仅繁琐,而且很难维护复杂多变的数据状态。

ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而ViewModel 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

Vue.js 是采用 Object.definePropertygettersetter,并结合观察者模式来实现数据绑定的。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。

  • Observer数据监听器,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者,内部采用Object.definePropertygettersetter来实现;
  • Compile指令解析器,它的作用是对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数;
  • Watcher订阅者,作为连接 ObserverCompile 的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数;
  • Dep消息订阅器,内部维护了一个数组,用来收集订阅者Watcher),数据变动触发notify 函数,再调用订阅者update 方法。

从图中可以看出,当执行 new Vue() 时,Vue 就进入了初始化阶段,一方面Vue 会遍历 data 选项中的属性,并用 Object.defineProperty 将它们转为 getter/setter,实现数据变化监听功能;另一方面,Vue 的指令编译器Compile 对元素节点的指令进行扫描和解析,初始化视图,并订阅Watcher 来更新视图, 此时Wather 会将自己添加到消息订阅器中(Dep),初始化完毕。

当数据发生变化时,Observer 中的 setter 方法被触发,setter 会立即调用Dep.notify()Dep 开始遍历所有的订阅者,并调用订阅者的 update 方法,订阅者收到通知后对视图进行相应的更新。

--

--

Lucas Zhang

Developing an economics education web for undergraduates.