在上篇文章当中,我们实现了单向数据绑定,那么接下来,咱们一步一步来实现双向数据绑定。
首先最大的问题就是我们如何去知道data里面的数据变化了,在Vue的实例化对象的data属性当中,可以查看到该属性上存在着一个get和set方法,而这两个方法实际上是通过Object.defineProperty()来实现数据劫持的。
先来实现一个简单的需求,创建一个简单的对象。
var Book={
name:"明朝那些事"
}
但是现在我想要在获取到Book.name的时候有个书名号包裹着该怎么办呢?这就需要用到上文说到的Object.defineProperty()了。
var Book={};
var value="";
Object.defineProperty(Book,"name",{
get:function(){
return value;
},
set:function(newValue){
value="《"+newValue+"》";
}
});
通过Object.defineProperty( )设置了对象Book的name属性,对其get和set进行重写操作,get就是在读取name属性这个值触发的函数,set就是在设置name属性这个值触发的函数。而在控制台输出的Book对象,乍一看,和Vue的数据长得有点类似,说明Vue确实是通过这种方法来实现数据的劫持的,那么我们就可以实现一个监听器observer了。
/*
* Description:监听器
* obj:监听的对象
*/
function observer(obj){
if(!obj || typeof obj != "object"){
return;
}
for(var key in obj){
defineReactive(obj,key,obj[key]);
}
}
function defineReactive(obj,key,value){
//递归调用,监听该对象下面所有子对象的属性
observer(value);
Object.defineProperty(obj,key,{
get:function(){
return value;
},
set:function(newValue){
value=newValue;
}
});
}
有了observer方法,我们就能够去实现监听Vue实例化对象的data属性值是否发生了更改,也就是说在Vue类当中调用observer方法即可,但是现在只是知道了哪个属性发生了变化,却无法实时的更新,要想实现这一步,我们先来考虑一个问题,当某一个属性值发生变化的时候,是需要将所有绑定该属性值的节点的值改为该属性值,所以这就用到了发布和订阅模式,也就是说,绑定同一个属性的每一个订阅者都存放在同一个订阅器内,如果该属性值变化了,订阅器会逐个的发布告知每一个订阅者。(更多详细信息请自行百度或参考lk儒家博客)
根据理解,很容易的清楚的知道需要一个订阅者容器类,该类的实例化对象必须含有一个数组,然后有一个向该数组存放订阅者的方法,以及循环该数组通知所有订阅者更新的方法。
//负责收集订阅者的容器
function Dep(){
this.subs=[];
}
Dep.prototype={
//添加订阅者的方法
addSub:function(sub){
this.subs.push(sub);
},
//通知订阅者的方法
notify:function(){
this.subs.forEach(function(sub){
//调用订阅者的update方法让其进行更新
sub.update();
});
}
}
而对于订阅者,上文也进行分析了,也很清楚的知道每一个订阅者的实例上都存在着一个update方法,即更新其内容,而再仔细想一想,这些订阅者到底是什么,更新的是什么内容呢?是不是每一个绑定Vue的属性的节点都是一个订阅者,改变的是该节点的内容,也就是说,这些订阅者是在上篇文章当中compile的时候去创建,需要的参数值是Vue的实例化对象,绑定的该节点以及绑定的Vue的属性值。
function Watcher(vm,node,name){
this.vm=vm;
this.node=node;
this.name=name;
//初始化
this.update();
}
Watcher.prototype={
//更新的方法,需要节点node,vue的实例化对象,属性名称
update:function(){
if(this.node.nodeType==1 && this.node.nodeName === 'INPUT'){
this.node.value=this.vm.data[this.name];
return;
}
this.node.nodeValue=this.vm.data[this.name];
}
}
而现在我们剩下的问题就是observer和Watcher并没有关联到一起,细心的小伙伴可能已经发现了在Watcher类当中进行了一句初始化操作,而执行update方法为什么属于初始化呢?可以发现,在update方法当中是将this.vm.data[this.name]赋值给节点内容,而this.vm.data[this.name]早已经被监听了,即其会在赋值之前先执行该Vue实例化对象data属性的get方法,所以向订阅者容器当中添加订阅者的操作就是在监听的get方法当中,那么又该如何区分是否是需要添加的呢,这里可以在Watcher类当中添加一个标识,例如Dep.target,因为这个是全局的,所以在每一次操作完后就要对该值释放。
修改后的Watcher类 修改后的defineReactive方法到此为止,整个Vue的双向绑定原理基本上说完了,但是运行发现,当修改输入框内容的时候,绑定同样属性值的内容根本就没有变化,这是由于没有给输入框绑定keyup事件,在该事件当中,只需要将输入框的内容赋值给Vue实例化对象的data中的绑定属性即可。
给input输入框绑定keyup事件最后,奉上一张逻辑图,加深理解,biubiu!
网友评论