MVVM是Model-View-ViewModel的简写。它本质上就是MVC (Model-View- Controller)的改进版。即模型-视图-视图模型。
【模型】指的是后端传递的数据。
【视图】指的是所看到的页面。
【视图模型】mvvm模式的核心,它是连接view和model的桥梁。
它有两个方向:
一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。
二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。
这两个方向都实现的,我们称之为数据的双向绑定。
总结:在MVVM的框架下视图和模型是不能直接通信的。它们通过ViewModel来通信,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel也能监听到视图的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。并且MVVM中的View 和 ViewModel可以互相通信。
MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。当然这些事 ViewModel 已经帮我们做了,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。MVVM(Model-View-ViewModel)框架的由来便是MVP(Model-View-Presenter)模式与WPF结合的应用方式时发展演变过来的一种新型架构框架。它立足于原有MVP框架并且把WPF的新特性糅合进去,以应对客户日益复杂的需求变化。
对于第一次接触的人看了上面的解释之后,可能还是似懂非懂,那么让我们从基础的看起吧,刚才我们说MVVM是它本质上就是MVC的改进版。那么MVC又是什么?它为什么需要改进呢?
MVC全名是Model View Controller,MVC是Model-View- Controller的简写。即模型-视图-控制器。M和V指的意思和MVVM中的M和V意思一样。C即Controller指的是页面业务逻辑。使用MVC的目的就是将M和V的代码分离。‘MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下。MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。由于mvc出现的时间比较早,前端并不那么成熟,很多业务逻辑也是在后端实现,所以前端并没有真正意义上的MVC模式。而我们今天再次提起MVC,是因为大前端的来到,出现了MVVM模式的框架,我们需要了解一下MVVM这种设计模式是如何一步步演变过来的。
为什么会有MVVM框架?
在过去的10年中,我们已经把很多传统的服务端代码放到了浏览器中,这样就产生了成千上万行的javascript代码,它们连接了各式各样的HTML 和CSS文件,但缺乏正规的组织形式,这也就是为什么越来越多的开发者使用javascript框架。比如:angular、react、vue。浏览器的兼容性问题已经不再是前端的阻碍。前端的项目越来越大,项目的可维护性和扩展性、安全性等成了主要问题。当年为了解决浏览器兼容性问题,出现了很多类库,其中最典型的就是jquery。但是这类库没有实现对业务逻辑的分成,所以维护性和扩展性极差。综上两方面原因,才有了MVVM模式一类框架的出现。比如vue,通过数据的双向绑定,极大了提高了开发效率。
(PS: 这里的MVP, MVVM甚至MXXX都只是MVC的变体而已,至于将重点放在C点还是V点(没人会希望放到M点吧?!)就自然引出了若干种所谓的模式,其实模式只有一种,你称为P也好VM也好,它都只是C的实例而已。总是设法弄出一些所谓的Business Word的家伙们,是前端开发最大的敌人!)
又经过上面对MVC和MVVM的介绍,大家应该对他们应该有比较清楚的认识了吧。
1. 双向绑定
号称是最难理解的地方,
标准的数据驱动开发,应该如上图所示,在一个View的生命周期内,一个ViewModule会管理一个DomainObject(业务模型),一个DO可能包括多个Module数据模型,一个Module可能来自多个数据源,而不是想很多所谓的MVVM框架那样强迫一个M来一个数据源
按照上图标准分层方式来划分的好处,在于,逻辑清晰,Module层粒度够细,可以被多次复用
DO层与VM层View层属于一一对应关系,方便对数据做增删改查的同步
每一层应该是独立的,非一定要使用MVVM框架的紧耦合,可以用自己使用不同的js插件或者模块实现MVVM
我们抛弃框架,单纯的看数据,其实我们要解决的问题很简单
a) 当DO对象属性放生变化时候,通知View更新
b) 当View上表单值放生变化时,通知DO更新,并异步通知队列同步到数据源
先来看问题a,这个最简单,DO是一个基本的Javascript Object,我们在View上的模板显示是这个Object.property,
改变一个Object对象的方式无非几种,一种是
a) 显示Object.property = ‘你好’
b) xxxx.methodName(Object, ‘property’, ‘你好’)
c) xxxx.merge(Object, {‘property’: ‘你好’})
如果是a的情况,ES5+,可以通过设置Object.defefineProperty(‘property’,{set: functiono(){},get:function(){}}),来做赋值和取值的监控触发
对于IE8一下,因为js不支持运算符重载,所以暂时没有好的办法,所以如果只考虑移动端的话,直接defineProperty就全部搞定,如果是要考虑PC的话,就不建议开发者使用直接赋值的方式,参考java的开发模式,也是推荐OOP时候,使用set方式赋值,而不是直接=赋值。
当然了,如果你非要兼容IE8一下的话,用定时器做轮训,配合for in 反射,通过脏数据与原始备份对比的方法也是一种办法,不过这种办法在当前页面非常耗性能,由于IE8一下不支持多线程,HTML5 worker,如果未来flash 插件支持多线程的话,倒是可以用js和flash插件做线程交互的方式做脏数据检测。
如果是b的情况,那就太简单了,在methodName里面触发对于该属性修改的回调即可,如何注册回调呢,首先我们要实现一个类似Dom Event的自定义对象的Event模型,然后通过类似Dom Event的注册事件方式,注册观察者,订阅事件,当执行了methodName时候,发送消息,通知所有订阅者执行回调
如果是c的情况,类似b一样处理
这样一看,双向数据绑定的问题就非常简单的解决了。
我们再来看另外一个MVVM的问题,非简单数据模型,复合数据模型(DO的属性值不是一个string,而是一个Object,且这个Object可能还嵌套多层Obejct的时候)的处理办法,这个一般的MVVM框架直接不考虑,或者通过长字段名的方式绕过这个问题
这个问题是这样的,早在10几年前,java structs框架流行的时候就出现了,当一个表单,出现需要对两个Java Bean做update操作时候,一个bean是user,一个bean是成绩
对应的表单字段名,就是 user表.name,user表.id,score表.point,
在struct2里面,处理逻辑是把 “点”作为特殊符号,在做form序列化时候,非包含点的字段的值都是string,包含点的字段是一个Object,比如刚才的form序列化之后结果就是 { user: {id :’’ , name: ‘’}, score: {id: ‘’, point: ‘’}}
同理在MVVM实现时,也是一样,认为点是分割对象的关键字,这样我们就可以实现把多个对象嵌套到View模板里面,实现复合Object的双向映射。