Vue原理解析
##Vue原理
采用Object.defineProperty() 数据劫持,和发布-订阅者模式,实现双向绑定
- 通过建立虚拟dom树document.createDocumentFragment(),方法创建虚拟dom树。
- 一旦被监测的数据改变,会通过Object.defineProperty定义的数据拦截,截取到数据的变化。
- 截取到的数据变化,从而通过订阅——发布者模式,触发Watcher(观察者),从而改变虚拟dom的中的具体数据。
- 最后,通过更新虚拟dom的元素值,从而改变最后渲染dom树的值,完成双向绑定
##数据拦截简单实现
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
语法:Object.defineProperty(obj, prop, descriptor)
$(function (param) {
var obj = {};
Object.defineProperty(obj,'hello',{
get: function () {
//执行obj.hello 时,拦截到了数据
console.log("调用了getter方法");
},
set: function (newValue) {
// 执行obj.hello = 'hello world',拦截到数据
console.log("调用setter方法");
}
});
obj.hello;
obj.hello = 'hello world';
})
对其更底层对象属性的修改或获取的阶段进行了拦截(对象属性更改的钩子函数)。
在数据拦截的基础上,可以做到数据的双向绑定
1 | <body> |
##了解了基本组成原理了,现在开始实现一个Vue
1:创建Vue的虚拟节点容器
1 | function nodeContainer(node, vm, flag){ |
##Vue 初始方法编译
1 | var demo = new Vue({ |
##编译
1 | function compile(node, vm){ |
代码解释:当nodeType为1的时候,表示是个元素。同时我们进行判断,如果节点中的指令含有v-model这个指令,那么我们就初始化,进行对节点的值的赋值。
如果nodeType为3的时候,也就是text节点属性。表示你的节点到了终点,一般都是节点的前后末端。我们常常在这里定义我们的双绑值。此时一旦匹配到了双绑(双大括号),即进行值的初始化。
至此,我们的Vue初始化已经完成。
##Vue响应式声明
定义Vue中data属性的响应式声明
1 | function defineReactive(obj,key,value){ |
用observer 方法,循环调用响应式方法,为data中的每个值增加响应
1 | function observer(obj,vm){ |
初始化Vue
1 | function Vue(options){ |
编译时找到v-module时去监听
1 | function compile(node, vm){ |
##订阅–发布者模式
在发布者发布消息时,所有的订阅者可以收到消息
1 | //定义三个订阅者 |
###重点要实现的是:如何去更新视图,同时把订阅——发布者模式进去watcher观察者模式?
##观察者模式
定义订阅者
1 | function Dep(){ |
定义发布者(观察者)
1 | function Watcher(vm,node,name){ |
以上需要注意的点:
在Watcher函数对象的原型方法update里面更新视图的值(实现watcher到视图层的改变)。
Watcher函数对象的原型方法get,是为了触发defineProperty方法中的get方法!
在new一个Watcher的对象的时候,记得把Dep函数对象赋值一个全局变量,而且及时清空。至于为什么这么做,我们接下来看。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21function defineReactive (obj, key, value){
var dep = new Dep();//这里每一个vm的data属性值声明一个新的订阅者
Object.defineProperty(obj,key,{
get:function(){
console.log(Dep.global);
-----------------------
if(Dep.global){//这里是第一次new对象Watcher的时候,初始化数据的时候,往订阅者对象里面添加对象。第二次后,就不需要再添加了
dep.add(Dep.global);
}
-----------------------
return value;
},
set:function(newValue){
if(newValue === value){
return;
}
value = newValue;
dep.notify();//触发了update()方法
}
})
}这里有一点需要注意: 在上述圈起来的地方:if(Dep.global)是在第一次new Watcher()的时候,进入update()方法,触发这里的get方法。这里非常的重要的一点!在此时new Watcher()只走到了this.update();方法,此刻没有触发Dep.global = null函数,所以值并没有清空,所以可以进到dep.add(Dep.global);方法里面去。 而第二次后,由于清空了Dep的全局变量,所以不会触发add()方法。
紧接着在text节点new Watcher的方法来触发以上的内容:
1 | //如果节点类型为text |