vue 数据双向绑定的实现方法
1.前言
本文适合于学习Vue源码的初级学者,阅读后,你将对Vue的数据双向绑定原理有一个大致的了解,认识Observer、Compile、Wathcer三大角色(如下图所示)以及它们所发挥的功能。
本文将一步步带你实现简易版的数据双向绑定,每一步都会详细分析这一步要解决的问题以及代码为何如此写,因此,在阅读完本文后,希望你能自己动手实现一个简易版数据双向绑定。
2.代码实现
2.1目的分析
本文要实现的效果如下图所示:
本文用到的HTML和JS主体代码如下:
letvm=newVue({
el:"#app",
data:{
msg:"helloworld",
msg2:"helloxiaofei"
}
})
我们将按照下面三个步骤来实现:
- 第一步:将data中的数据同步到页面上,实现M==>V的初始化;
- 第二步:当input框中输入值时,将新值同步到data中,实现V==>M的绑定;
- 第三步:当data数据发生更新的时候,触发页面发生变化,实现M==>V的绑定。
2.2实现过程
2.2.1入口代码
首先,我们要创造一个Vue类,这个类接收一个options对象,同时,我们要对options对象中的有效信息进行保存;
然后,我们有三个主要模块:Observer、Compile、Wathcer,其中,Observer用来数据劫持的,Compile用来解析元素,Wathcer是观察者。可以写出如下代码:(Observer、Compile、Wathcer这三个概念,不用细究,后面会详解讲解)。
classVue{
//接收传进来的对象
constructor(options){
//保存有效信息
this.$el=document.querySelector(options.el);
this.$data=options.data;
//容器:{属性1:[wathcer1,wathcer2...],属性2:[...]},用来存放每个属性观察者
this.$watcher={};
//解析元素:实现Compile
this.compile(this.$el);//要解析元素,就得把元素传进去
//劫持数据:实现Observer
this.observe(this.$data);//要劫持数据,就得把数据传入
}
compile(){}
observe(){}
}
2.2.2页面初始化
在这一步,我们要实现页面的初始化,即解析出v-text和v-model指令,并将data中的数据渲染到页面中。
这一步的关键在于实现compile方法,那么该如何解析el元素呢?思路如下:
- 首先要获取到el下面的所有子节点,然后遍历这些子节点,如果子节点还有子节点,那我们就需要用到递归的思想;
- 遍历子节点找到所有有指令的元素,并将对应的数据渲染到页面中。
代码如下:(主要看compile那部分)
classVue{
//接收传进来的对象
constructor(options){
//获取有用信息
this.$el=document.querySelector(options.el);
this.$data=options.data;
//容器:{属性1:[wathcer1,wathcer2...],属性2:[...]}
this.$watcher={};
//2.解析元素:实现Compile
this.compile(this.$el);//要解析元素,就得把元素传进去
//3.劫持数据:实现Observer
this.observe(this.$data);//要劫持数据,就得把数据传入
}
compile(el){
//解析元素下的每一个子节点,所以要获取el.children
//备注:children返回元素集合,childNodes返回节点集合
letnodes=el.children;
//解析每个子节点的指令
for(vari=0,length=nodes.length;i
这样,我们就实现页面的初始化了。
2.2.3视图影响数据
因为input带有v-model指令,因此我们要实现这样一个功能:在input框中输入字符,data中绑定的数据发生相应的改变。
我们可以在input这个元素上绑定一个input事件,事件的效果就是:将data中的相应数据修改为input中的值。
这一部分的实现代码比较简单,只要看标注那个地方就明白了,代码如下:
classVue{
constructor(options){
this.$el=document.querySelector(options.el);
this.$data=options.data;
this.$watcher={};
this.compile(this.$el);
this.observe(this.$data);
}
compile(el){
letnodes=el.children;
for(vari=0,length=nodes.length;i{
this.$data[attrVal]=ev.target.value;
//可以试着在这里执行:console.log(this.$data),
//就可以看到每次在输入框输入文字的时候,data中的msg值也发生了变化
})
}
}
}
observe(data){}
}
2.2.4数据影响视图
至此,我们已经实现了:当我们在input框中输入字符的时候,data中的数据会自动发生更新;
本小节的主要任务是:当data中的数据发生更新的时候,绑定了该数据的元素会在页面上自动更新视图。具体思路如下:
1)我们将要实现一个Wathcer类,它有一个update方法,用来更新页面。观察者的代码如下:
classWatcher{
constructor(node,updatedAttr,vm,expression){
//将传进来的值保存起来,这些数据都是渲染页面时要用到的数据
this.node=node;
this.updatedAttr=updatedAttr;
this.vm=vm;
this.expression=expression;
this.update();
}
update(){
this.node[this.updatedAttr]=this.vm.$data[this.expression];
}
}
2)试想,我们该给哪些数据添加观察者?何时给数据添加观察者?
在解析元素的时候,当解析到v-text和v-model指令的时候,说明这个元素是需要和数据双向绑定的,因此我们在这时往容器中添加观察者。我们需用到这样一个数据结构:{属性1:[wathcer1,wathcer2...],属性2:[...]},如果不是很清晰,可以看下图:
可以看到:vue实例中有一个$wathcer对象,$wathcer的每个属性对应每个需要绑定的数据,值是一个数组,用来存放观察了该数据的观察者。(备注:Vue源码中专门创造了Dep这么一个类,对应这里所说的数组,本文属于简易版本,就不过多介绍了)
3)劫持数据:利用对象的访问器属性getter和setter做到当数据更新的时候,触发一个动作,这个动作的主要目的就是让所有观察了该数据的观察者执行update方法。
总结一下,在本小节我们需要做的工作:
- 实现一个Wathcer类;
- 在解析指令的时候(即在compile方法中)添加观察者;
- 实现数据劫持(实现observe方法)。
完整代码如下:
classVue{
//接收传进来的对象
constructor(options){
//获取有用信息
this.$el=document.querySelector(options.el);
this.$data=options.data;
//容器:{属性1:[wathcer1,wathcer2...],属性2:[...]}
this.$watcher={};
//解析元素:实现Compile
this.compile(this.$el);//要解析元素,就得把元素传进去
//劫持数据:实现Observer
this.observe(this.$data);//要劫持数据,就得把数据传入
}
compile(el){
//解析元素下的每一个子节点,所以要获取el.children
//拓展:children返回元素集合,childNodes返回节点集合
letnodes=el.children;
//解析每个子节点的指令
for(vari=0,length=nodes.length;i{
this.$data[attrVal]=ev.target.value;
})
if(!this.$watcher[attrVal]){
this.$watcher[attrVal]=[];
}
//不同于上处用的innerHTML,这里input用的是vaule属性
this.$watcher[attrVal].push(newWatcher(node,"value",this,attrVal))
}
}
}
observe(data){
Object.keys(data).forEach((key)=>{
letval=data[key];//这个val将一直保存在内存中,每次访问data[key],都是在访问这个val
Object.defineProperty(data,key,{
get(){
returnval;//这里不能直接返回data[key],不然会陷入无限死循环
},
set(newVal){
if(val!==newVal){
val=newVal;//同理,这里不能直接对data[key]进行设置,会陷入死循环
this.$watcher[key].forEach((w)=>{
w.update();
})
}
}
})
})
}
}
classWatcher{
constructor(node,updatedAttr,vm,expression){
//将传进来的值保存起来
this.node=node;
this.updatedAttr=updatedAttr;
this.vm=vm;
this.expression=expression;
this.update();
}
update(){
this.node[this.updatedAttr]=this.vm.$data[this.expression];
}
}
letvm=newVue({
el:"#app",
data:{
msg:"helloworld",
msg2:"helloxiaofei"
}
})
至此,代码就完成了。
3.未来的计划
用设计模式的知识,分析上面这份源码存在的问题,并和Vue源码进行比对,算是对Vue源码的解析
以上就是vue数据双向绑定的实现方法的详细内容,更多关于vue数据双向绑定的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。