推荐博客:
https://blog.csdn.net/jia12216/article/details/55520426
https://www.cnblogs.com/sunny_z/p/7093663.html
一、MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。当然这些事 ViewModel 已经帮我们做了,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。微软的WPF带来了新的技术体验,如Silverlight、音频、视频、3D、动画……,这导致了软件UI层更加细节化、可定制化。同时,在技术层面,WPF也带来了 诸如Binding、Dependency Property、Routed Events、Command、DataTemplate、ControlTemplate等新特性。MVVM(Model-View-ViewModel)框架的由来便是MVP(Model-View-Presenter)模式与WPF结合的应用方式时发展演变过来的一种新型架构框架。它立足于原有MVP框架并且把WPF的新特性糅合进去,以应对客户日益复杂的需求变化。
MVVM优点编辑
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大优点
1.
低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
2.
可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
3.
独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用Expression Blending可以很容易设计界面并生成xaml代码。
4.
可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
MVVM设计模式的缺点
第一点:数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。
第二点:一个大的模块中,model也会很大,虽然使用方便了也很容易保证了数据的一致性,当时长期持有,不释放内存,就造成了花费更多的内存。
第三点:数据双向绑定不利于代码重用。客户端开发最常用的重用是View,但是数据双向绑定技术,让你在一个View都绑定了一个model,不同模块的model都不同。那就不能简单重用View了。
MVVM框架的主要应用场景
1)针对具有复杂交互逻辑的前端应用
2)提供基础的架构抽象
3)通过Ajax数据持久化,保证前端用户体验
好处就是当前后端进行一些数据交互的时候,前端可以通过Ajax请求对后端做数据持久化,不需要刷新整个页面,只需要改动DOM里需要改动的那部分数据和内容,特别是对于移动端应用场景,刷新页面的代价太昂贵,会重新加载很多资源,虽然有些资源会被缓存,但是页面的DOM、JS、CSS都会被浏览器重新解析一遍,因此,移动端页面经常会做成SPA单页应用,在这个基础上就诞生了很多MVVM框架,如
Angular、React、Vue
二、MVC(Model View Controller)架构开发,它是苹果推荐的开发模式,它把页面分成三部分:数据模型,页面视图,页面控制器。一个页面被分成多个小视图,一个页面共享一个数据模型,只是这个数据模型只被控制器操作,不被各个子视图处理。这个架构看起来整洁多了,控制器的复杂度降低的很多,页面的显示单元被分配到各个视图去显示,控制器专注与数据的加工与处理,部分解放了控制器,使它的功能更集中,更紧凑,更小。数据模型,页面视图,页面控制器各个独立,各司其职。这就是重量级视图控制的特点。它看起来简单,更容易理解,所有的逻辑在视图控制器里处理。
一个页面一般分三个文件夹:页面文件夹(存放页面控制器和其子文件夹),页面文件夹下的model文件夹,页面文件夹下的view文件夹。想找页面间的逻辑就去页面控制器里找,想看页面显示元素就在view文件夹下寻找。你发现数据文件很小,只是数据存储和转换,这符合数据单一性原则;视图部分也很小,只是数据的显示,完全不自主;控制器部分仍然摆脱不了过于庞大的弊病。他们就像一个人一样头重而且庞大,脚轻,身子小。看到这些你就知道这种开发架构需要进化的方向:强化控制器的重要性,减轻它的体积,把不属于它的非核心部分迁移到类似视图的那一部分去,在保证数据的定义性上增加它的功能和作用。
取消使用单例组装http请求的做法(单例像类一样常驻内存,无形中增加类内存的开销。虽然它做到了统一管理http请求的作用,但是不符合那个页面的请求那个页面管理请求的原则)。让从控制器分化出来的这一部分功能给View,http请求封装为一个新操作类。
优点:
1. 可定制性
2. 代码清晰,便于维护
3. 测试友好性
4. 轻量级
5. 开源
主要缺点有两个:
1. View对Model的依赖,会导致View也包含了业务逻辑;
2. Controller会变得很厚很复杂。
前端mvc框架,如angularjs,backbone:
三、MVP:Model-View-Presenter,MVC的一个演变模式,将Controller换成了Presenter,主要为了解决上述第一个缺点,将View和Model解耦
四、了解一下Vue双数据绑定原理
vue.js
是采用
数据劫持
结合
发布者-订阅者模式
的方式,通过
Object.defineProperty()
来劫持各个属性的
setter
,
getter
,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体步骤:
第一步:需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter
这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
五、什么是数据劫持:
首先我们应该搞清楚什么是数据劫持,说白了就是通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时做你想要做的事情,举个栗子:
var
data = {
name:
'lhl'
}
Object.
keys(
data).
forEach(
function(
key){
Object.
defineProperty(
data,
key,{
enumerable:
true,
configurable:
true,
get
:
function(){
console.
log(
'get');
},
set
:
function(){
console.
log(
'监听到数据发生了变化');
}
})
});
data.
name
//控制台会打印出 “get”
data.
name =
'hxx'
//控制台会打印出 "监听到数据发生了变化"
上面的这个栗子可以看出,我们完全可以控制对象属性的设置和读取。在Vue中,作者在很多地方都非常巧妙的运用了defineProperty这个方法,具体用在哪里并且它又解决了哪些问题,下面做详细的介绍:
监听对象属性的变化
这个应该是Vue非常重要的一块,其主要思想是observer每个对象的属性,添加到订阅器dep中,当数据发生变化的时候发出notice通知。 相关源代码如下:(作者采用的是ES6+flow写的,代码在src/core/observer/index.js模块里面)
export
function
defineReactive (
obj:
Object,
key:
string,
val:
any,
customSetter?:
Function
) {
const
dep =
new
Dep()
//创建订阅对象
const
property =
Object.
getOwnPropertyDescriptor(
obj,
key)
if (
property &&
property.
configurable ===
false) {
return
}
// cater for pre-defined getter/setters
const
getter =
property &&
property.
get
const
setter =
property &&
property.
set
let
childOb =
observe(
val)
//创建一个观察者对象
Object.
defineProperty(
obj,
key, {
enumerable:
true,
configurable:
true,
get
:
function
reactiveGetter () {
const
value =
getter ?
getter.
call(
obj) :
val
//这里也是作者一个巧妙设计,在创建watcher实例的时候,通过调用对象的get方法往订阅器 dep上添加这个创建的watcher实例
if (
Dep.
target) {
dep.
depend()
if (
childOb) {
childOb.
dep.
depend()
}
if (
Array.
isArray(
value)) {
dependArray(
value)
}
}
return
value
},
set
:
function
reactiveSetter (
newVal) {
const
value =
getter ?
getter.
call(
obj) :
val
if (
newVal ===
value) {
return
}
if (
process.
env.
NODE_ENV !==
'production' &&
customSetter) {
customSetter()
}
if (
setter) {
setter.
call(
obj,
newVal)
}
else {
val =
newVal
}
childOb =
observe(
newVal)
//继续监听新的属性值
dep.
notify()
//这个是真正劫持的目的,要对订阅者发通知了
}
})
}
以上是监听对象属性的变化,那么下面再看看如何监听数组的变化:
监听数组的变化
const
arrayProto =
Array.
prototype
export
const
arrayMethods =
Object.
create(
arrayProto)
;[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
.
forEach(
function (
method) {
// cache original method
const
original =
arrayProto[
method]
def(
arrayMethods,
method,
function
mutator () {
// avoid leaking arguments:
// http://jsperf.com/closure-with-arguments
let
i =
arguments.
length
const
args =
new
Array(
i)
while (
i--) {
args[
i] =
arguments[
i]
}
const
result =
original.
apply(
this,
args)
const
ob =
this.
__ob__
let
inserted
switch (
method) {
case
'push':
inserted =
args
break
case
'unshift':
inserted =
args
break
case
'splice':
inserted =
args.
slice(
2)
break
}
if (
inserted)
ob.
observeArray(
inserted)
// notify change
ob.
dep.
notify()
return
result
})
})
...
/**
* Define a property.
*/
function
def (
obj,
key,
val,
enumerable) {
Object.
defineProperty(
obj,
key, {
value:
val,
enumerable: !!
enumerable,
writable:
true,
configurable:
true
});
}
通过上面的代码可以看出Vue是通过修改了数组的几个操作的原型来实现的。
原文自: https://www.cnblogs.com/haohaoday/p/6375149.html
Vue框架很好的利用了Object.defineProperty()这个方法来实现了数据的监听和修改,同时也达到了很好的模块间解耦,在日常开发用好这个方法说不定会达到令人意想不到的结果。
发布订阅者模式:
https://www.sohu.com/a/207062452_464084