MVVM设计模式的前端应用
2019-12-16邓成孙书会
邓成 孙书会
摘要:在应用庞大到一定程度时,MVC结构模式也将变得十分复杂,特别是View的逻辑部分变得难以维护。在这个背景下,MVVM的架构模型应运而生(model-view-viewmodel)。MVVM的核心是在逻辑代码中不对View进行直接操作,而是通过ViewModel将View与Model绑定起来,且具有内置的互相同步的机制。这种模式解放了开发者在Model中维护View的大部分工作,对于前端应用更是极大地提高了开发效率和代码抽象度。前后端分离架构的普及以及sPA应用的设计思想都离不开MVVM的流行,这些都带来了前端技术的快速发展。
关键词:MVVM;代码质量;软件开发
中途分类号:TP311 文献标识码:A
文章编号:1009-3044(2019)29-0249-02
MVVM模式最早的提出并不是在Web前端领域,它最早于2005年被微软的WPF和Silverlight的架构师John Gossman提出,并且应用在微软的软件开发中。MVVM的核心概念是实现视图与模型之间的绑定关系,这个绑定包括视图状态的处理以及数据绑定和数据转换,举例来说视图中某一处的状态与模型层中数据绑定在一起,数据的变更将会反映到视图层,这个反应机制由VM实现。大部分从MVC到MVVM迁移的开发者都认为MVVM更适合大型应用。
1什么是MVVM
MVVM是Model-View-ViewModel的简称,即模型一视图一视图模型。MVVM的设计原理是基于MVC的,MVC是Model-View-Controller的简称,即模型视图控制器,如图1。MVC的设计思想常被用在服务端框架上,如SpringMVC。但在前端应用上,由于对于View的驱动过于频繁且复杂,MVC并不适合前端业务复杂度高的场景。MVVM则是借鉴MVC的思想改造的设计模式。
MVVM的最大特点就是开发者不需要直接改变View,即无须直接操作DOM。开发者只需要编写包含声明绑定的视图模版,并编写ViewModel中的数据变更逻辑,View将得到响应式的改变。这种模式极大地提高了前端的开发效率和,降低了开发复杂度。View的解耦也让前端代码的单元测试变得更简单,提升了前端整体的代码质量。
2MVVM使用现状
当今前端有三大框架:React、Vue、Angular。Angular基于传统MVC的设计思想实现,React、Vue均是使用MVVM的设计模式。React和Vue互相借鉴,实现上有很多相似之处。
1)都使用VirtualDOM
2)提供了響应式(Reactive)和组件化(Composable)的视图组件。
3)将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库。
从框架的API设计上来看Vue的更简洁易懂,下文主要参照Vue进行实现。
3实现MVVM设计模式
从MVVM底层实现人手,实现一个类Vue的简单MVVM模式。以下内容大量参考Vue源码。Vue源码地址:https://github.com/vuejs/vue。
MVVM设计模式主要包括四个部分
模版编译(Compile)
数据劫持(observerl
发布订阅(Dep)
观察者(Watcherl
代码实现可使用ECMASeript6规范。首先从开发者使用角度出发,参照Vue的实现,我们需要完成以下功能:
1)模版指令语法的实现如:v-model双向绑定指令。
2){{Object}}模版对象替换的实现。
3)数据变化的监听及视图的改变。
MVVM是MVVM模式的实现类,开发者通过使用MVVM类对象完成MVVM框架的调用。
首先将el、data对象直接复制在MVVM类上,然后通过模版编译类渲染页面。
模版编译类对开发者编写的视图模版进行编译,生成真实的DOM结构,并在需要时进行模版的重新编译。
Compile类的构造函数传人DOM选择器和MVVM实例。Fragment是DOM节点的内存片段,有着与真实DOM相同的结构,使用Fragment操作DOM会更加高效。
遍历开发者提供的目标节点所有的子节点,全部加入frag-ment中,有了DOM的内存片段,就可以开始编译该节点了。
在模版编译后,“ll中的JavaScript表达式将映射为MVVM类中的data对象的某个值。完成这部分工作后,模版的编译功能已经可用了,但是这种编译只是一个静态的编译机制,在初始编译一次模版以后,编译后的内容就不可变了。从各个MVVM框架的表现来看,模版与JavaScript之间存在着一种互相影响的关系,即双向绑定,再相应JavaScript对象的值发生变化时,我们仍然需要将这些变化体现在视图上。这就是MVVM中的数据劫持,在ES5中通过Object.defineProperty可以实现对象的劫持,ES6中的Proxy也可以实现。
我们通过写一个Observer类来实现数据劫持:
observe方法可以对传人的data进行深度递归劫持,即对data中的所有嵌套对象都进行劫持操作,以此完成data对象的响应化。DefineReactive定义了对象的响应变化。
defineProperty中的get和set拦截了对对象的取值和赋值。Dep中定义了MVVM的发布订阅模式,在这之前我们需要实现对象的观察者,完成新值与旧值的对比,如果值发生了变化才执行相应的方法。
4MVVM的意义
4.1开发体验
视图与业务逻辑的分离极大提升了开发效率,开发者不必直接对DOM操作,只需要将精力放在业务逻辑的编写上。
4.2方便测试
在MVC下业务逻辑中混合着视图的操作,基本无法完全测试,在MVVM中只需要测试VM的代码就可保证业务逻辑的正确性。
4.3代码移植
同一份代码移植在不同的终端时,只需要更改视图部分的代码即可完成兼容。如在ios中使用MVVM模式进行开发可以使应用在iPhone和iPad中简单地完成代码的移植。
4.4性能
虽然MVVM并没有直接影响到前端的性能,但是MVVM模式搭配虚拟DOM技术可以极大地减少直接的DOM操作。浏览器原生的DOM操作极大的占用浏览器的性能,对于每次的DOM操作都将重新渲染DOM树,虚拟DOM通过Diff算法节省了操作DOM所带来的开销。