首先要明确 MVVM 是什么,它是 MVC 的衍生架构。无论是 MVC 还是 MVVM 都不是只针对于前端或后端开发的,它们是针对于所有软件开发的架构。
MVC:
<------------- Model <-------------
|更新 |控制
View Constoller
| |
<---sees-- User --updates-->
Model(模型):是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
View(视图):是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。
Controller(控制器):是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
原理:1.View传送用户操作给Controller。
2.Controller完成业务逻辑后,要求Model改变状态。
3.Model将新数据发送到View,用户得到反馈。
缺点:数据解析。将数据解析的部分放到了Controller里面,那么Controller就将变得相当臃肿。所以MVVM才诞生,将数据解析放到VM里去做。
Controller的存在感被完全的降低了。MVVM中的持有关系就是:C持有VM,VM持有M。
MVC和MVVM的区别:
1.数据解析。
2.双向数据绑定。MVC是单向(View -> Model)
MVVM:
数据绑定
View <--------> ViewModel <--------- Model
ViewModel ---------> Model
事件监听
M: 数据层Model,数据对象data:data: {msg: "world!"},data数据通过[数据绑定]给模板页面显示
V: 视图层View,模板页面:<div id="test"></div>,对DOM负责。通过 [事件监听]更新数据。
[
指令:自定义标签属性
大括号表达式:显示数据
]
VM:视图模型层ViewModel,VUE实例,里面进行DOM事件监听和数据绑定(v-model),取名叫vm: const vm = new Vue({el: "选择器", data: {msg: "world!"}})
将 数据层Model 和 视图层View 通过 [事件监听] 和 [数据绑定(v-model)] 连接起来。
[MVVM实现原理图]
劫持监听data里所有属性--数据代理 Dep与data中的属性一一对应
|-------> Observer(监听器) --给每个属性创建Dep并通知变化--> Dep(消息订阅器,管理监听器和订阅者) --通知变化-->
new MVVM() ^<--添加订阅者,把Watcher添加到Dep里的subs数组里--| |
|-------> Compile(指令解析器) --为表达式创建对应的Watcher,绑定更新函数--> Watcher(订阅者)
|--解析指令和{{}}表达式,[初始化]视图 通过绑定的更新函数[更新]视图--|
|--> Updater(里面有对应的更新的方法) <----|
初始化显示:通过[模板解析],页面(表达式和一般指令)能从data读取数据显示(编译和解析)。
更新显示:通过[数据代理]和[数据绑定],更新data中的属性的数据,通过Object.defineProperty()里的set(data里的set)函数更新界面。
[初始化完整流程]
1. 先new一个MVVM实例,内部先进行[数据代理],通过方法Object.defineProperty()[给vm添加与data对象的属性对应的属性描述符],get方
法实现代理读操作,set方法实现代理写操作。
2. 创建【Observer监听器】,通过方法Object.defineProperty()里的set方法来[劫持监听]data里的所有属性实现[数据绑定]。
同时创建给每个属性创建【Dep消息订阅器】,[Dep对象与data中的属性一一对应]。
同时创建【Compile指令解析器】,
解析器一方面调用Updater方法解析指令和双大括号表达式,来初始化视图[模板解析];
解析器另一方面会创建[和表达式对应的【Watcher订阅者】],在Watcher里指定了更新数据的回调函数,并且把Watcher添加到Dep对象里的用来
保存订阅者的subs数组里,用来监听Dep对象里属性的变化,如果属性改变,就调用这些回调函数更新数据到界面,就这样建立了Dep与Watcher之
间的关系,是在监听器里的get方法完成的,此get方法的另一个作用是获得当前属性值。
[更新显示完整流程]
1. this.name='Lucy';修改了vm里面的属性值,被vm里的set函数监视到,vm里的set函数就会去修改data里的name属性值[数据代理],又被data里
的set函数监视到[数据绑定],然后data里的set函数去更新界面。
2. set怎么更新界面?data里的set是Observer的原型对象里的Object.defineProperty()里的set,set函数通过Observer监听到数据改变,通知
Dep消息管理器(对应data中的属性),在Dep对象里通知所有相关的Watcher订阅者(对应非事件指令),Watcher对象会先取到最新的属性值,然后
去调用更新的回调函数(赋值操作)实现了更新数据。再修改其他的属性值,从set开始循环即可。
[举例:vm.name = 'abc'-->data中的name属性值变化 -->name的set()调用 -->Dep -->相关的所有的Watcher -->cb() -->Updater更新]
创建这些对象有先后顺序:
首先创建Observer构造函数,
然后创建Observe功能函数,通过new一个Observer实例,对data中所有层次的属性通过数据劫持实现数据绑定。
然后创建Dep构造函数,在Observer的原型对象里new一个Dep实例,
然后创建Compile构造函数,在MVVM(Vue)里new一个Compile实例来解析模板(解析大括号表达式和一般指令)。
然后创建Watcher构造函数,在Compile初始化编译模板时创建Watcher,并在Watcher的原型对象里建立Dep和Watcher之间的关系。
什么时候建立?初始化的解析模板中的表达式创建Watcher对象时。在watcher的原型对象里建立Dep和Watcher的关系:
addDep: function (dep) {
if (!this.depIds.hasOwnProperty(dep.id)) { // 判断如果已存在关系,则不再建立
// [建立dep到watcher,把watcher添加到dep里的subs数组里。]this是watcher。【这个关系是关键】
dep.addSub(this);
// [建立watcher到dep的关系,把dep添加到watcher里的depIds对象里。]this是watcher。【这个关系只是为了判断是否已存在关系】
this.depIds[dep.id] = dep;
}
},
1. 什么时候一个dep中关联多个watcher?
多个指令或表达式用到了当前同一个属性 {{name}} {{name}}
2. 什么时候一个watcher中关联多个dep?
多层表达式的watcher对应多个dep {{a.b.c}}
最后创建MVVM(Vue)构造函数。在MVVM(Vue)的原型对象里实现数据代理。
v-model指令是怎么实现双向数据绑定的?
[1] 双向数据绑定是建立在单向数据绑定(数据层绑定视图层)[model=>view]的基础之上的。
[初始化完整流程]
(1) 先new一个MVVM实例,内部先进行[数据代理],通过方法Object.defineProperty()[给vm添加与data对象的属性对应的属性描述符],get方
法实现代理读操作,set方法实现代理写操作。
(2) 创建Observer监听器,通过方法Object.defineProperty()里的set方法来劫持监听data里的所有属性实现[数据绑定]。
同时创建Dep消息订阅器,[Dep对象与data中的属性一一对应]。
同时创建Compile指令解析器,
解析器一方面调用Updater方法解析指令和双大括号表达式,来初始化视图[模板解析];
解析器另一方面会创建[和表达式对应的Watcher]订阅者,在Watcher里指定了更新数据的回调函数,并且把Watcher添加到Dep对象里的用来保存订
阅者的subs数组里,用来监听Dep对象里属性的变化。如果属性改变,就调用这些回调函数更新数据到界面,就这样建立了Dep与Watcher之间的关
系,是在监听器里的get方法完成的,此get方法的另一个作用是获得当前属性值。
[更新显示完整流程]
(1) this.name='Lucy';修改了vm里面的属性值,被vm里的set函数监视到,vm里的set函数就会去修改data里的name属性值[数据代理],又被data里
的set函数监视到[数据绑定],然后data里的set函数去更新界面。
(2) set怎么更新界面?data里的set是Observer的原型对象里的Object.defineProperty()里的set,set函数通过Observer监听到数据改变,
通知Dep消息管理器(对应data中的属性),在Dep对象里通知所有相关的Watcher订阅者(对应非事件指令),Watcher对象会先取到最新的属性值,
然后去调用更新的回调函数(赋值操作)实现了更新数据。再修改其他的属性值,从set开始循环即可。
[举例:vm.name = 'abc'-->data中的name属性值变化 -->name的set()调用 -->Dep -->相关的所有的Watcher -->cb() -->Updater更新]
[2] 视图层绑定数据层的实现流程:通过v-model指令实现[view=>model]
[初始化完整流程] 在解析v-model指令时,给当前元素添加input监听。【input输入内容的过程中触发,change是失去焦点才触发】
(1) 解析v-model时,在Compile原型对象里先执行compile方法里解析普通指令的语句:
compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
然后会进入到[指令处理集合compileUtil对象]里找到[解析v-model的方法model],该方法里调用[真正用于解析指令的方法bind],在bind里根据
指令名(model)进入到对应的更新节点函数updater里modelUpdater方法,实现初始化,同时,bind里会给表达式model创建一个Watcher。
modelUpdater: function (node, value, oldValue) { // node是input当前元素,value是msg的初始值"haha",oldValue是undefined
node.value = typeof value == 'undefined' ? '' : value; // 把初始值"haha"赋值给input输入框,初始显示
}
(2) 然后执行方法model里bind后面的代码,获取表达式model的值msg,添加input事件监听(方法addEventListener),用于处理视图层变
化更新数据层数据。
概括:解析v-model时,进入解析指令的方法bind里,一方面[根据指令名model初始化当前元素],另一个方面[给表达式model创建一个Watcher。]
执行完bind之后,会获取表达式model的值msg,然后[添加input事件监听(方法addEventListener)],用于处理视图层变化更新数据层数据。
[更新显示完整流程] 当input的value发生改变时,将最新的值赋值给当前表达式model所对应的data属性msg。
(1) [手动改变输入框的值,即value发生变化,input事件监听到后调用model函数(v-model绑定的函数)的set方法将最新的值赋值给当前表达式所
对应的数据层data]里的msg属性,
(2) 此时,[data里的set函数监视到]属性msg改变了,再走一遍[数据绑定]的流程,实现把更新后的内容显示到界面上。