赞
踩
MVC,MVP,MVVM是三种常见的前端架构模式,通过分离关注点来改进代码组织方式。MVC模式是MVP,MVVM模式的基础,这两种模式更像是MVC模式的优化改良版,他们三个的MV即Model,view相同,不同的是MV之间的纽带部分。
Model:模型】指的是后端传递的数据。注意这里的M指的是Domain Model,这个应用程序对需要解决的问题的数据抽象,不包含应用的状态,可以简单理解为对象,只保存数据结构和提供数据操作的接口。
View:【视图】指的是所看到的页面。
Controller:控制器指的是页面业务逻辑。
Mvvm定义:Model-View-ViewModel的简写。即模型-视图-视图模型。
【视图模型】mvvm模式的核心,它是连接view和model的桥梁。两个方向称之为数据的双向绑定:
一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。
二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。
总结:在MVVM的框架下视图和模型是不能直接通信的。它们通过ViewModel来通信,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel也能监听到视图的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。并且MVVM中的View 和 ViewModel可以互相通信。MVVM流程图如下:
MVC的定义:Model-View- Controller的简写。即模型-视图-控制器。使用MVC的目的就是将M和V的代码分离。MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下。
MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。
如果前端没有框架,只使用原生的html+js,MVC模式可以这样理解:将html看成view;js看成controller,将js的ajax当做Model,也就是数据层,通过ajax从服务器获取数据。
简单来说:
1、MVC:(非服务端的MVC)
注意:上面流程是单向数据流动的,controller只是接收View的指令,更新后的数据不是通过controller通知view更新,而是model通知的=》单向的
流程描述:用户对View操作以后,View捕获到这个操作,会把处理的权利交移给Controller(Pass calls);Controller接着会执行相关的业务逻辑,这些业务逻辑可能需要对Model进行相应的操作;当Model变更了以后,会通过观察者模式(Observer Pattern)通知View;View通过观察者模式收到Model变更的消息以后,会向Model请求最新的数据,然后重新更新界面。
1、View是把控制权交移给Controller,自己不执行业务逻辑。
2、Controller执行业务逻辑并且操作Model,但不会直接操作View,可以说它是对View无知的。
3、View和Model的同步消息是通过观察者模式进行,而同步操作是由View自己请求Model的数据然后对视图进行更新。
优点:
1、把业务逻辑全部分离到Controller中,模块化程度高。当业务逻辑变更的时候,不需要变更View和Model,只需要Controller换成另外一个Controller就行了(Swappable Controller)。
2、观察者模式可以做到多视图同时更新。
缺点:
1、Controller测试困难。因为视图同步操作是由View自己执行,而View只能在有UI的环境下运行。在没有UI环境下对Controller进行单元测试的时候,Controller业务逻辑的正确性是无法验证的:Controller更新Model的时候,无法对View的更新操作进行断言。
2、View无法组件化。View是强依赖特定的Model的,如果需要把这个View抽出来作为一个另外一个应用程序可复用的组件就困难了。因为不同程序的的Domain Model是不一样的
简单案例:https://github.com/livoras/MVW-demos/tree/master/src/scripts/mvc
- models:todos.js
- var EventEmitter = require("eventemitter2").EventEmitter2
- function TodosModel() {
- EventEmitter.call(this)
- this._todos = [{content: "Make PPT!", done: false}]
- }
- var pro = TodosModel.prototype = Object.create(EventEmitter.prototype)
- pro.getTodos = function() {
- return this._todos
- }
- pro.setTodos = function(todos) {
- this._todos = todos
- this.emit("change", todos) //3、和view配合,通知view数据更新了
- }
- module.exports = TodosModel
-
-
-
- views:todos-list-view.js
- var Handlebars = require("handlebars")
- function TodosListView(controller, model) {
- this.controller = controller
- this.model = model
- this.template = Handlebars.compile($("#todos-list-tpl").html())
- this.$el = $("<div></div>")
- }
- TodosListView.prototype.build = function() {
- this.render()
- this.listenCheck()
- this.listenModel()
- }
- TodosListView.prototype.render = function() {
- var todos = this.model.getTodos()
- this.$el.html(this.template({todos: todos}))
- }
- TodosListView.prototype.listenCheck = function() {
- var self = this
- self.$el.on("click", "li", null, function(event) {
- var $li = $(event.currentTarget)
- self.controller.onCheck(+$li.attr("data-index")) //1、将请求转发给controller
- })
- }
- TodosListView.prototype.listenModel = function() {
- this.model.on("change", this.render.bind(this)) //4、监控model变化主动获取model更新后的数据
- }
- module.exports =TodosListView
-
-
-
- controllers:todos-list-controller.js
- function TodosListController(model) {
- this.model = model
- }
- TodosListController.prototype.onCheck = function(index) {
- var todos = this.model.getTodos()
- var todo = todos[index]
- todo.done = !todo.done
- this.model.setTodos(todos) //2、Controller操作model去更新数据
- }
- module.exports = TodosListController
2、MVP:
和MVC相比,MVP打破了View原来对于Model的依赖。
流程描述:用户对View的操作都会从View交移给Presenter。Presenter同样的会执行相应的业务逻辑,并且对Model进行相应的操作;而这时候Model也是通过观察者模式把自己变更的消息传递出去,但是是传给Presenter而不是View。Presenter获取到Model变更的消息以后,通过View提供的接口更新界面。
Presenter将Model的变化返回给View。和MVC不同的是,Presenter会反作用于View,不像Controller只会被动的接受View的指挥。抽象View暴露其属性和事件,然后Presenter引用View的抽象。这样可以很容易的构造View的Mock对象,提高可单元测试性。Presenter不仅要操作数据,而且要更新View。
1、View不再负责同步的逻辑,而是由Presenter负责。Presenter中既有业务逻辑也有同步逻辑。
2、View需要提供操作界面的接口给Presenter进行调用。(关键)
优点:
1、便于测试。Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter业务逻辑的正确性。这里根据上面的例子给出了Presenter的单元测试样例。
2、View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务逻辑完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做高度可复用的View组件。
缺点:
1、Presenter中除了业务逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。
MVP的实现会根据View的充、贫血而有一些不同,一部分倾向于在View中放置简单的逻辑,在Presenter放置复杂的逻辑;另一部分倾向于在presenter中放置全部的逻辑。这两种分别被称为:Passive View(常讨论的是PV)和Superivising Controller。
Passive View模式,该模式下View非常Passive,它几乎什么都不知道,Presenter让它干什么它就干什么=》在Passive View中,为了减少UI组件的行为,使用Controller不仅控制用户事件的响应,而且将结果更新到View上。可以集中测试Controller,减小View出问题的风险。
Supervising Controller模式中,Presenter会把一部分简单的同步逻辑交给View自己去做,Presenter只负责比较复杂的、高层次的UI操作。
简单案例:https://github.com/livoras/MVW-demos/tree/master/src/scripts/mvp
- models:todos.js
- var EventEmitter = require("eventemitter2").EventEmitter2
- function TodosModel() {
- EventEmitter.call(this)
- this._todos = [{content: "Make PPT!", done: false}]
- }
- var pro = TodosModel.prototype = Object.create(EventEmitter.prototype)
- pro.getTodos = function() {
- return this._todos
- }
- pro.setTodos = function(todos) {
- this._todos = todos
- this.emit("change", todos) //通知presenter而不是view数据更新了
- }
- module.exports = TodosModel
-
-
- presenter:todos-list-presenter.js
- function TodosListPresenter(view, model) {
- this.view = view
- this.model = model
- this.init()
- }
- TodosListPresenter.prototype.init = function() {
- this.view.setPresenter(this)
- this.view.build(this.model.getTodos())
- this.listenModelChange()
- }
- TodosListPresenter.prototype.onCheck = function(index) {
- this.toggleTodoModel(index) //2、
- this.toggleTodoView(index) //
- }
- TodosListPresenter.prototype.toggleTodoModel = function(index) {
- var todos = this.model.getTodos() //2-1、更新model数据
- var todo = todos[index]
- todo.done = !todo.done
- }
- TodosListPresenter.prototype.toggleTodoView = function(index) {
- if (this.model.getTodos()[index].done) { //根据model数据情况,调用view暴露的相应方法更新view
- this.view.checkItem(index)
- } else {
- this.view.uncheckItem(index)
- }
- }
- TodosListPresenter.prototype.listenModelChange = function() {
- var self = this
- this.model.on("change", function() { //获取model数据的更新,通过调用view暴露的方法同步更新view
- self.view.render(self.model.getTodos())
- })
- }
- module.exports = TodosListPresenter
-
-
- views:todos-list-view.js
- var Handlebars = require("handlebars")
- function TodosListView(model) {
- this.presenter = null
- this.model = model
- this.template = Handlebars.compile($("#todos-list-tpl").html())
- this.$el = $("<div></div>")
- }
- TodosListView.prototype.build = function(todos) {
- this.render(todos)
- this.listen()
- }
- TodosListView.prototype.render = function(todos) {
- this.$el.html(this.template({todos: todos}))
- }
- TodosListView.prototype.setPresenter = function(presenter) {
- this.presenter = presenter
- }
- TodosListView.prototype.listen = function() {
- var self = this
- self.$el.on("click", "li", null, function(event) {
- var $li = $(event.currentTarget)
- self.presenter.onCheck(+$li.attr("data-index")) //1、转发给Presenter
- })
- }
- TodosListView.prototype.checkItem = function(i) {
- return this.$el.find("li").eq(i).addClass("done")
- }
- TodosListView.prototype.uncheckItem = function(i) {
- return this.$el.find("li").eq(i).removeClass("done")
- }
- module.exports = TodosListView
3、MVVM:
MVVM可以看作是一种特殊的MVP(Passive View)模式,或者说是对MVP模式的一种改良。
MVVM与MVC最大的区别就是:它实现了View和Model的自动同步,也就是当Model的属性改变时,我们不用再自己手动操作Dom元素,来改变View的显示,而是改变属性后该属性对应View层显示会自动改变。
MVVM是在原有领域Model的基础上添加一个ViewModel,ViewModel除了正常的属性以外,还包括一些供View显示用的属性。例如在经典的MVP中,View有一个属性IsCheck,需要在Presenter中设置View的IsCheck值。但是在MVVM中的Presenter也会有一个IsCheck属性来同步View的IsCheck属性,可能会用到Observer模式同步IsCheck的值。
MVVM的调用关系和MVP一样。但是,在ViewModel当中会有一个叫Binder,或者是Data-binding engine的东西。以前全部由Presenter负责的View和Model之间数据同步操作交由给Binder处理(如上面的IsCheck)。你只需要在View的模版语法当中,指令式地声明View上的显示的内容是和Model的哪一块数据绑定的。当ViewModel对进行Model更新的时候,Binder会自动把数据更新到View上去,当用户对View进行操作(例如表单输入),Binder也会自动把数据更新到Model上去。这种方式称为:Two-way data-binding双向数据绑定。可以简单而不恰当地理解为一个模版引擎,但是会根据数据变更实时渲染。
优点:
1、提高可维护性。解决了MVP大量的手动View和Model同步的问题,提供双向绑定机制。提高了代码的可维护性。
2、简化测试。因为同步逻辑是交由Binder做的,View跟着Model同时变更,所以只需要保证Model的正确性,View就正确。大大减少了对View同步更新的测试。
缺点:
1、过于简单的图形界面不适用,或说牛刀杀鸡。
2、对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高。
3、数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的。
MVVM双向数据绑定典型代表VUE
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。