我的需求
:
-
晚上练完车之后,之前参考我毕设的一个小伙伴要答辩,问了我一个问题,结果问的一下不知道怎么回答....
以下是我回答他问题的答案
:所以在回答完他之后,赶快整理一波..
我需要解决的问题
:
-
MVVM到底是个什么东东,和前后端有没有关系,它和MVC区别是啥,有啥优势。
我是这样做的
:
-
百度
寻找,找了一些关于MVVM论文,博客,梳理出自己的答案。
-
嗯,资源比较零散,准确性有待考量,
所以不对的地方请小伙伴指出来
。
爱自己,是终生浪漫的开始 ------王尔德
对于MVC想来小伙伴是不陌生的,但是网上的资源各抒己见....我也整的晕头转向的,可能有
前(后)端
,有
胖(瘦)客户端
框架应用,具体还有细微的差异。
If you put ten software architects into a room and have them discuss what the Model-View-Controller pattern is, you will end up with twelve different opinions. --Josh Smith
如果你把10个软件架构师放在一个房间里,让他们讨论模型-视图-控制器模式是什么,你最终会得到12种不同的观点。
我们这里讨论的
MVC
和
MVVM
是以
BS架构
为基础的
java Web
中的应用,因为博主只接触了这方面的,关于网上提到的
IOS
和一些
客户端
框架,没有接触过。本博客也不涉及。所以如果听都没听过
java Web
的,或者没了解过
Web框架
的小伙伴个人感觉这篇博客不太适合,不太建议继续读下去。
我们先看看MVVM吧!嘻嘻 ^ _ ^
MVVM 名词解释:
MVVM
是
Model-View-ViewModel
的简写。它本质上就是
MVC
的改进版。MVVM 就是将其中的
View的状态和行为抽象化
,让我们将
视图 UI
和
业务逻辑
分开。当然这些事
ViewModel
已经帮我们做了,它可以取出
Model
的数据同时帮忙处理
View
中由于需要展示内容而涉及的业务逻辑。
MVVM(Model-View-ViewModel)框架
的由来便是
MVP(Model-View-Presenter)模式
与
WPF
结合的应用方式时发展演变过来的一种
新型架构框架
。它立足于原有
MVP
框架并且把
WPF
的新特性糅合进去,以应对客户日益复杂的需求变化。
MVVM
upright=1.5
MVVM(Model–view–viewmodel)
是一种
软件架构模式
。
MVVM
有助于将
图形用户界面的开发
与
business logic
(业务逻辑)或
后端逻辑
(数据模型)的开发
分离
开来,这是通过置标语言或GUI代码实现的。MVVM的视图模型是一个
值转换器
, 这意味着
视图模型
负责从
模型
中
暴露(转换)数据对象
,以便轻
松管理和呈现对象
。在这方面,
视图模型
比
视图
做得更多,并且处理大部分
视图
的
显示逻辑
。 视图模型可以实现
中介者模式
,组织对视图所支持的用例集(Model)的后端逻辑的访问。
MVVM 的发展历程
MVVM
是
马丁·福勒
的
PM(Presentation Model)设计模式
的变体。
MVVM
以相同的方式
抽象
出
视图的状态和行为
, 但
PM
以
不依赖于特定用户界面平台
的方式抽象出
视图
(建立了视图模型)。
MVVM
和
PM
都来自
MVC模式
。
MVVM
由微软架构师
Ken Cooper
和
Ted Peters
开发,通过利用
WPF(微软.NET图形系统)
和
Silverlight(WPF的互联网应用衍生品)
的特性来
简化用户界面
的
事件驱动程式设计
。 微软的WPF和Silverlight架构师之一
John Gossman
于
2005
年在他的博客上发表了
MVVM
。 MVVM也被称为
model-view-binder
,特别是在不涉及
.NET平台
的实现中。ZK(Java写的一个Web应用框架)和KnockoutJS(一个JavaScript库)使用
model-view-binder
。
MVC到MVVM 的发展历程
二十世纪八十年代
施乐帕克实验室
提出了
MVC
的概念,MVC的全称即
Model-View-Controller
,是
模型(model)
一
视图(view)
一
控制器(controller)
的缩写“…,它是一种
客户端软件开发框架
,个人认为,其实最初的
Java Web
来讲,
Model2
即
Servlet+JSP
也是用的这个结构,所以说
Model2(MVC)
它相对已
Model1(Javabean+JSP)
来讲,已经实现了
View
和
Model
的部分解耦,但是不彻底,如图
view
负责显示,
Model
负责提供数据,
Controller
负责逻辑的处理,其实现的流程大概是:
-
(1)当用户需要发送请求时,首先是在View发送请求,由View将指令传送到Controller里。
-
(2)Controller接收到指令之后,先完成所需要的业务逻辑,然后要求Model根据业务逻辑改变状态;
-
(3)Model将新的数据发送给View,View则根据新的数据更新视图,从而用户的请求得到反馈。
在
MVC
框架中,
View
是可以直接访问
Model
的(JSP里直接使用
JavaBean
),这样不可避免的使
View
里面也需要包括一些
业务逻辑
,同时还需要
Model
保持不变,而
Model
又对应着多个不同的显示(View),所以总体说来就是,在MVC模型里面,
Model
不依赖
View
,但是
View
是依赖于
Model
的。这样就导致更改
View
比较困难,且
业务无法重用
。从而
MVC框架的弊端就显现出来
,这也是使用
Servlet+JSP
的弊端。前后端没有解耦,
Model
与
View
没有彻底解耦。
为了解决
MVC
框架中
View
和
Model
联系紧密的问题,开发者研究开发了
MVP
模式,
MVP
即
Model-View-Presenter
,即把MVC中的
Controller
换成了
Presenter
,目的就是为了完全切断
View
跟
Model
之间的联系,在MVP模式中,
View
负责
视图的显示
,
Model
负责
提供数据
,
Presenter
则主要负责
逻辑业务
的处理。
有些
SSM+JSP
的开发方式也是基于这种,我之前的公司就这样写,前后端不分离使用的
JSP
,但是交互全是
Ajax
,传递的全是
JSON
,也没有返回
ModelAndView
,个人感觉这里其实是使用了
MVP
的模式。以前后端
不分离
的方式
丢弃模板引擎的服务端渲染
,追求前后端
分离
中
彻底解耦了View和Model
。看上去怪怪的,其实有时候项目开发更多的是和
业务
、
体量
、
成本
、
效益
等有关系,综合考虑,选最合适,不一定要按照
常规
的
构建方式
考虑,比如正常思考可能
不分离
是为了
服务端渲染
,
首屏快载
,
SEO
等,分离是为了
降低服务器压力
,
接口复用
,前后端
工作职责解耦
.
对于
SSM
+
模板引擎
的开发方式
-
如何是返回
Modelandview
的话,那
缺点
就是
后端路由
,前后端没有
彻底解耦
,优点就是
服务端渲染
,返回的是整个
构建好的页面
.
-
如果返回
JSON
的话,那优点就是
前后端彻底解耦
,
接口复用
,但是没有利用模板引擎的
服务端渲染
。
-
如果体量很大,那前后端是两个人写,那使用
Modelandview
的方式就很麻烦,需要
接口协调
,而且工作
职责不清晰
。会浪费好多时间。JSON就方便很多。
-
如果体量不是他大,前端的东西也不是特别多,考虑成本问题,前后端一个人写,那
Modelandview
就很合适,节省了接口协调,对接等时间成本问题。
在
MVP
框架中,
View
无法直接再与
Model
交互,
View
和
Model
之间的通信都是通过
Presenter
进行完成的,所有的交互都在
Presenter
内部发生,即由
Presenter
充当了
View
和
Model
的桥梁,做到
View-Model
之间通信的
完全隔离
。
Presenter
完全把
Model
和
View
进行分离,将主要的程序逻辑放在Presenter里实现。
Presenter
与
View
也是没有直接相关联的,而是通过已定义的
接口进行交互
,从而使得在变更
View
的时候可以保持
Presenter
的不变,即保证了
Presenter
的可重用性(接口的复用性),同时也解决了
MVC
框架中的
View
和
Model
关联紧密的问题。
这样之后,对于
Web
项目来讲,前后端都是通过数据进行交互,那
路由
怎么处理,前端只能实现简单一部分跳转,涉及到复杂的需要
通过Controller(Presenter)来处理的路由
怎么处理,或者带状态的路由如何跳转,即
Controller无法控制使用那个View
。个人感觉,
Web
系统来讲这个时候
完全的前后端分离
可能不是适合所有项目,而且分离之后留给
前端要解决的问题
可能也不是能很好的解决。所以这个时候....
有个叫
Rod Johnson
带领一帮人搞出的
SpringMVC
,不像桌面应用的
MVC
, 这里的Model没法给View 发通知。
也不像
MVP
, 这里的
Controller
可以控制
View
来实现路由。即前后后端没有分离,但是将原来的
View的构建解耦
了。由
模板
和
数据
构成:
public class MyGlobalException {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ModelAndView customException(MaxUploadSizeExceededException e) {
ModelAndView mv = new ModelAndView("javaboy");
mv.addObject("error", e.getMessage());
return mv;
}
即降低了
View
和
Model
耦合,同时又实现了
后端路由
。
对于大型项目而言,
前端
的东西原来越多,造成
服务端
的压力越来越大,而且由于
MVP
的出现,逐渐向
前后端分离
靠拢,分离之后,View分担
服务端
的压力,或者说是
浏览器
分担了
服务器
压力,包括页面渲染,路由等问题,这时侯MVVM出现了..(
这里是自己猜的,没找到相关资料
)
MVVM
框架便是
前后端分离框架发展史
上的一次思想的完全变革。它是将
数据模型双向绑定
的思想作为变革的
核心
,即
View
的变动,自动反映在
ViewModel
上面,而
ViewModel
的变动也会随即反映在
View
上面,从而实现
数据与模型的双向绑定
。
在
MVVM
框架中,
View
用于发送用户的
交互请求
,之后将用户请求转交给
ViewModel
,
ViewModel
即可根据用户请求操作
Model
数据更新,待
Model
数据更新完毕,便会通知
ViewModel
数据发生了变化,然后
ViewModel
就会即刻更新
View
数据,完成视图的更新,从而完成用户的请求。
虽然
MVVM
框架和之前的
MVC
、
MVP
模式的目的相同,即完成
视图(View)和模型(Model)的分离
,但它却有着明显的优势。
-
首先,MVVM框架中的
View
完全可以独立于
Model
发生
变化和修改
,彻底解耦,View发生变化时Model可以不变,同样,当Model发生变化时View也可以不变化,并且一个
ViewModel
可以绑定到多个不同的
View
上面,这就体现了
MVVM框架的低耦合性
。
-
其次,绑定在一个
ViewModel
上面的多个
View
都可以使用
ViewModel
里面的视图逻辑,完成了框架可重用性的特性。除此之外,
MVVM框架
还具有
可独立开发
、
可测试
等特性,把框架作用发挥到最大化,也因此成为了开发者们青睐的框架。。
对于
MVVM
这种模式主要用于构建
基于事件驱动的 UI 平台
,对于前端开发领域中
数据与界面相混合
的情况特别适用
,其中
-
Model
仅仅只是代表应用程序所需的数据信息,它
不关注
任何行为;
-
View
是软件中与用户进行直接交互的部分,它需要响应
ViewModel
的事件并格式化数据,
不负责
控制应用的状态;
-
ViewModel
用于
封装业务逻辑层
,这点类似于 MVC 模式中的控制器,它
控制View
的很多显示逻辑,它可以把
数据模型
的变化
传递
给
视图
,也可以把视图中数据的变化传递给数据模型,即在 Model 和View 之间建立了双向绑定。
Vue与MVVM
我第一次看到
MVVM
是因为
Vue
,相信好多小伙伴也是
Vue
认识
MVVM
架构模式。Vue官网中讲到:
虽然没有完全遵循
MVVM
模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示组件实例
通过
双向数据绑定
连接
视图
层和
数据
,而实际的
界面 UI 操作
(
DOM 操作
)被封装成
对应的指令
(Directives)和
过滤器
(Filters)
MVVM原理:
实现
数据绑定
的做法有大致如下几种:
-
脏值检查(angular.js)
:
angular.js
是通过
脏值检测
的方式比对数据是否有
变更
,来决定是否
更新视图
,最简单的方式就是通过
setInterval()
定时
轮询检测
数据变动,angular只有在指定的
事件触发时
进入
脏值检测
.
-
DOM
事件,譬如用户输入文本,点击按钮等。(
ng-click
)
-
XHR
响应事件 (
$http
)
-
浏览器
Location
变更事件 (
$location
)
-
Timer
事件
( $timeout , $interval )
-
执行
$digest()
或
$apply()
-
数据劫持(vue.js)
:
数据劫持
,指的是在
访问
或者
修改
对象的某个属性时,通过一段代码
拦截
这个行为,进行
额外的操作
或者
修改返回结果
。简单地说,就是当我们
触发函数
的时候 动一些手脚做点我们自己想做的事情,也就是所谓的
"劫持"操作
-
在Vue中其实就是通过
Object.defineProperty
来劫持对象属性的
setter和getter
操作,并“种下”一个
监听器
,当数据发生变化的时候发出通知:
Object.defineProperty(obj,prop,descriptor)
参数:
obj:目标对象
prop:需要定义的属性或方法的名称
descriptor:目标属性所拥有的特性
可供定义的特性列表:
value:属性的值
writable:如果为false,属性的值就不能被重写。
get: 一旦目标属性被访问就会调回此方法,并将此方法的运算结果返回用户。
set:一旦目标属性被赋值,就会调回此方法。
configurable:如果为false,则任何尝试删除目标属性或修改属性性以下特性(writable, configurable, enumerable)的行为将被无效化。
enumerable:是否能在for...in循环中遍历出来或在Object.keys中列举出来。
+ >`Proxy数据代理`:Proxy 可以被认为是` Object.defineProperty() 的升级版`。外界对`某个对象的访问`,都必须经过这层`拦截`。因此它是针对 整个`对象`,而不是 对象的某个`属性`。
var data = {name:'test'}
Object.keys(data).forEach(function(key){
Object.defineProperty(data,key,{
enumerable:true,
configurable:true,
get:function(){
console.log('get');
set:function(newValue){
console.log('监听到数据发生了变化');
document.getElementById(‘myText’).value=newValue;
document.getElementById(‘myText’).addEventListener(‘keyup’,function(e){
data.name=e.target.value; // 监听 View 的变化,同步更新 Model
data.name //控制台会打印出 “get”
data.name = 'hxx' //控制台会打印出 "监听到数据发生了变化"
var arr = [1,2,3]
var handle = {
//target目标对象 key属性名 receiver实际接受的对象
get(target,key,receiver) {
console.log(`get ${key}`)
// Reflect相当于映射到目标对象上
return Reflect.get(target,key,receiver)
set(target,key,value,receiver) {
console.log(`set ${key}`)
return Reflect.set(target,key,value,receiver)
//arr要拦截的对象,handle定义拦截行为
var proxy = new Proxy(arr,handle)
proxy.push(4) //可以翻到控制台测试一下会打印出什么
上述介绍了简单的
一对一双向绑定
的实现,即一个
数据模型
只与一个
视图
进行绑定。当多个
View
与一个
Model
进行绑定时,每次更新
Model
时需要在
Model
的
set
访问器属性中更新多个
View
,这样硬编码的方式不利于
后期的维护
。为了解决
硬编码带来的耦合性过强
的问题,在在实际实现中,需要使用到设计模式中的
发布 - 订阅模式
。
发布 - 订阅模式
(又称
观察者
模式)是一种常用的设计模式,该模式包含
发布者
和
订阅者
两种角色。可以让
多个订阅者
订阅同
一个发布者
发布的主题,当发布者的主题发生变化时,对外发送一个通知,所有订阅了该主题的订阅者都会接收到更新的消息。因此,观察者模式定义的是
一种一对多的关系
。发布 - 订阅模式非常适合于 MVVM 双向绑定中多个视图绑定到同一个数据模型的情形。
实现双向数据绑定步骤
要实现
mvvm
的双向绑定,就必须要实现以下几点:
-
实现一个
指令解析器Compile
,对每个元素节点的指令进行扫描和解析,根据
指令模板
替换数据,以及
绑定
相应的
更新函数
-
实现一个
数据监听器Observer
,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并
通知订阅者(Dep)
-
实现一个
Watcher
,Watcher是订阅 - 发布模式中订阅者的实现,作为连接
Observer
和
Compile
的桥梁,能够订阅并收到每个属性变动的通知,
执行指令
绑定的相应回函数 (发布),从而
更新视图
-
MVVM入口函数,整合以上三者
当
新建
一个
Vue 对象
时,框架进入
初始化
阶段。Vue 在初始化阶段主要执行两个操作:
-
第一个是
遍历系统中数据
的所有属性,来对各个属性的
变化添加监听
;
-
第二个操作是利用
指令编译器 Compile
对视图中绑定的指令进行扫描
进行视图的初始化
,然后订阅
Watcher
来
更新视图
,此时
Watcher
会将自己添加到
消息订阅器Dep
中。至此,Vue的初始化过程结束。
在系统运行过程中,一旦系统中的数据模型发生了变化,
观察者 Observer
的 setter 访问器属性就会被触发,此时消息
订阅中心
Dep 会遍历它所维护的所有
订阅者
,对于每一个订阅了该数据的
对象
,向它发出一个
更新通知
,订阅者收到通知后就会对
视图
进行相应的更新。以上过程不断往复循环,这就是 MVVM 模式在 Vue.js 中的运行原理。
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>Two-way data-binding</title>
</head>
<div id="app">
<input type="text" v-model="text">
{{ text }}
<script>
function observe (obj, vm) {
Object.keys(obj).forEach(function (key) {
defineReactive(vm, key, obj[key]);
function defineReactive (obj, key, val) {
var dep = new Dep();
Object.defineProperty(obj, key, {
get: function () {
if (Dep.target) dep.addSub(Dep.target);
return val
set: function (newVal) {
if (newVal === val) return
val = newVal;
dep.notify();
function nodeToFragment (node, vm) {
var flag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
compile(child, vm);
flag.appendChild(child);
return flag;
function compile (node, vm) {
var reg = /\{\{(.*)\}\}/;
// 节点类型为元素
if (node.nodeType === 1) {
var attr = node.attributes;
// 解析属性
for (var i = 0; i < attr.length; i++) {
if (attr[i].nodeName == 'v-model') {
var name = attr[i].nodeValue; // 获取v-model绑定的属性名
node.addEventListener('input', function (e) {
// 给相应的data属性赋值,进而触发该属性的set方法
vm[name] = e.target.value;
node.value = vm[name]; // 将data的值赋给该node
node.removeAttribute('v-model');
new Watcher(vm, node, name, 'input');
// 节点类型为text
if (node.nodeType === 3) {
if (reg.test(node.nodeValue)) {
var name = RegExp.$1; // 获取匹配到的字符串
name = name.trim();
new Watcher(vm, node, name, 'text');
function Watcher (vm, node, name, nodeType) {
// this为watcher函数
Dep.target = this;
// console.log(this);
this.name = name;
this.node = node;
this.vm = vm;
this.nodeType = nodeType;
this.update();
Dep.target = null;
Watcher.prototype = {
update: function () {
this.get();
if (this.nodeType == 'text') {
this.node.nodeValue = this.value;
if (this.nodeType == 'input') {
this.node.value = this.value;
// 获取daa中的属性值
get: function () {
this.value = this.vm[this.name]; // 触发相应属性的get
function Dep () {
this.subs = []
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
function Vue (options) {
this.data = options.data;
var data = this.data;
observe(data, this);
var id = options.el;
var dom = nodeToFragment(document.getElementById(id), this);
// 编译完成后,将dom返回到app中
document.getElementById(id).appendChild(dom);
var vm = new Vue({
el: 'app',
data: {
text: 'hello world'
</script>
</body>
</html>
我的理解
-
架构意义角度(Web端的角度)
:
MVC
和
MVVM
在本质上都是为了
实现View和Model的解耦
,
MVC
是通过
Controller
实现了
View
和
Model
的
解耦
,一般用与客户端,或者
Web
端的整个架构过程;而
MVVM
是在
MVC
发展到
MVP
后(为了彻底解决View和Model的耦合问题),在提出
前后端分离
的基础上(考虑Coltroller的复用性,接口复用性),对
View层
进行了
增强(Vue.js)
,或者说细化了View层的表现手法,提出了通过
ViewModel
对视图层的
View
和
Model
解耦。
个人感觉
MVVM
和
MVP
的整体
架构
是有
相似
的地方的,不同的是面对的
问题域
不同,
MVP
是
Web
架构整体的解决方案,
MVVM
主要用于构建
基于事件驱动的 UI 平台(界面)
,适用于
前端
开发领域中
数据与界面相混合
的情况,所以它只专注于
视图层
,
抽象
出
视图
的状态和行为,实现了
用户界面的UI(View)
和
数据(Model)
的
解耦
。这个
View
和
Model
虽然和
MVC
中描述的一样,但是不相同的,可以理解为
MVC
中
View
中包含了
MVVM
的架构方式。
一般
前后端分离
的
Web
开发中会结合
MVC
和
MVVM
两种架构模式。使用
MVC
构建整体的
Web
架构,使用
MVVM
解决
View
层
DOM
和
data
的耦合问题。
-
设计模式角度考虑
:
MVC
是基于
观察者
设计模式的,
Model
作为一个主题,
View
作为观察者,当一个
Model
变化时,会通知更新一个或多个依赖的
View
,反之;
MVVM可以看做是基于
中介者
设计模式和
观察者
设计模式,
View
和
Model
通过
ViewModel
这个
中介者对象
进行交互,解耦了
View
和
Mode
l的同时实现
数据双向绑定
。
同时
ViewModel
作为一个
主题对象
,
View
和
Model
为两个观察者(或者可以理解为
View
为主题时,
Model
为观察者,反之。这里的
Model View
起到一个
注册
,
通知
的作用,对于
观察者
模式的定义,
ModelView
是主题的行为,但实际变化的是
View
或者
Model
,
个人觉得两种理解都没问题,理解不对的请小伙伴指出来
),当
Model
变化时,
ViewModel
由
数据绑定
通知并更新与之相关的多个
View
,反之,当
View
变化时,
ViewModel
由
DOM监听
通知更新相关的多个
Model
。
引用文献资料
在MVVM的框架下视图和模型是不能直接通信的。
它们通过ViewModel来通信,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化
然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel也能监听到视图的变化
然后通知数据做改动,这实际上就实现了数据的双向绑定。并且MVVM中的View 和 ViewModel可以互相通信。
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构.