vue3.0 双向绑定原理,VUE双向绑定原理
本文主要介绍Vue2.x Vue利用Object.defineProperty()方法劫持数据的双向绑定原理,利用set和get检测数据读写。有需要的朋友可以参考下面这篇文章的具体内容。
目录
1、实现流程2、显示一个Observer3、实现Watcher4、实现Compile5、添加解析事件6。myVueVue完整版是通过使用Object.defineProperty()方法,使用set和get来检测数据读写的数据劫持。
https://jsrun.net/RMIKp/embedded/all/light
MVVM框架主要包括两个方面:数据变化更新视图,视图变化更新数据。
查看更改更新数据。如果是类似输入的标签,可以使用oninput事件。
数据更改可用于更新视图。Object.definProperty()的set方法可以检测数据变化。当数据发生变化时,该功能将被触发,然后视图将被更新。
1、实现过程
我们知道如何实现双向绑定。首先,我们需要劫持和监控数据,所以我们需要设置一个观察者函数来监控所有属性的变化。
如果属性发生了变化,应该通知订阅者观察器查看数据是否需要更新。如果有多个订阅者,则需要一个Dep来收集这些订阅者,然后以统一的方式管理监听器观察器和观察器。
您还需要一个指令解析器来扫描和解析需要监控的节点和属性。
因此,流程大概是这样的:
实现一个监听器观察器,用于劫持和监听所有属性,并在有变化时通知订阅者。
实现一个订阅者观察器,当收到属性改变的通知时,执行相应的函数,然后更新视图,使用Dep收集这些观察器。
实现一个解析器编译,用于扫描和解析节点的相关指令,并根据初始化模板初始化相应的订阅者。
2、显示一个 Observer
Observer是一个数据监听器,其核心方法是使用Object.defineProperty()递归添加setter和getter方法到所有属性中进行监听。
var库={
第一册:{
名称: ,
},
第二本书: ,
};
观察(图书馆);
Library.book1.name=vue权威指南;//属性名已被监控,现在其值为:“vue权威指南”
Library.book2=没有这样的书;//属性book2已经被监控,现在它的值是:“没有这个书”
//添加数据检测
函数定义有效(数据,键,值){
观察(val);//递归遍历所有子属性
let Dep=new Dep();//创建新的dep
Object.defineProperty(数据,键,{
可枚举:真,
可配置:真,
get: function() {
if (Dep.target) {
//确定是否需要添加订户。只需要第一次添加,之后就不需要了。详情请参见观察器功能。
dep . add sub(dep . target);//添加订户
}
返回val
},
set: function(newVal) {
if (val==newVal)返回;//如果值没有改变,则返回
val=newVal
console.log(
属性“key”已被监视,现在它的值是:“ newVal.toString()”
);
dep . notify();//如果数据发生变化,通知所有订阅服务器。
},
});
}
//侦听对象的所有属性
功能观察(数据){
如果(!数据数据类型!==object) {
返回;//如果不是对象,则返回
}
Object.keys(数据)。forEach(函数(键){
defineReactive(data,key,data[key]);
});
}
//Dep负责收集订阅者,并在属性发生变化时触发更新功能。
函数Dep() {
this . subs={ };
}
部门原型={
addSub: function(sub) {
this . subs . push(sub);
},
通知:函数(){
this . subs . foreach((sub)=sub . update());
},
};
在分析中,需要有一个可以容纳订阅者的消息订阅者Dep,用于收集订阅者,并在属性发生变化时执行相应的更新功能。
从代码的角度来看,订阅者Dep被添加到getter中,以便在观察器初始化时触发。因此,有必要确定是否需要该订户。
在setter中,如果任何数据发生变化,所有订阅者都会得到通知,然后订阅者会更新相应的函数。
至此,一个相对完整的观测器已经完成。接下来,我们开始设计观察器。
3、实现 Watcher
订户观察器需要在初始化期间将其自身添加到订户Dep中。我们已经知道listener Observer在get期间执行Watcher操作,所以我们只需要在Watcher初始化时触发相应的get函数来添加相应的subscriber操作。
在这里,如何触发get?因为我们已经设置了Object.defineProperty(),所以只需要获取相应的属性值就可以触发它了。
我们只需要在初始化订阅者观察器时在Dep.target上缓存订阅者,然后在成功添加后删除它们。
函数观察器(vm,exp,cb) {
this.cb=cb
this.vm=vm
this.exp=exp
this . value=this . get();//将您自己添加到订阅者的操作
}
Watcher.prototype={
更新:函数(){
this . run();
},
run: function() {
var value=this . VM . data[this . exp];
var oldVal=this.value
如果(值!==oldVal) {
this.value=value
this.cb.call(this.vm,value,old val);
}
},
get: function() {
Dep.target=this//缓存自己,用来决定是否添加watcher。
var value=this . VM . data[this . exp];//在侦听器中强制执行get函数
Dep.target=null//释放自己
返回值;
},
};
到目前为止,已经设计了简单的观察器,然后通过将观察器与观察器相关联,可以实现简单的双向绑定。
因为解析器编译还没设计好,可以先把模板数据写死。
将代码写入ES6构造函数,预览一下。
https://jsrun.net/8SIKp/embed.
这段代码直接传入绑定变量,因为它没有实现编译器。我们只在一个节点上设置一个数据(名称)进行绑定,然后在页面上做new MyVue,就可以实现双向绑定了。
而两秒后,就值得换了。如您所见,页面也发生了变化。
//MyVue
proxyKeys(键){
var self=this
Object.defineProperty(this,key,{
可枚举:false,
可配置:真,
get:函数proxyGetter() {
return self . data[key];
},
set:函数proxySetter(newVal) {
self . data[key]=new val;
}
});
}
上面代码的作用是将this.data的键代理到this,这样我就可以很方便的用this.xx得到this.data.xx
4、实现 Compile
虽然上面实现了双向数据绑定,但是整个过程并不解析DOM section store,而是固定和替换的,所以下一步要实现一个解析器来解析和绑定数据。
解析器 compile 的实现步骤:
解析模板指令,替换模板数据,并初始化视图。
将相应的更新函数绑定到模板的指定节点,并初始化相应的订阅者。
为了解析模板,我们需要首先解析DOM数据,然后在DOM元素上处理相应的指令。所以整个DOM操作比较频繁,我们可以创建一个新的片段,把要解析的DOM存储到片段中进行处理。
函数节点到分段(el) {
var fragment=document . createdocumentfragment();
var child=el.firstChild
while (child) {
//将Dom元素移动到片段中
fragment.appendChild(子);
child=el.firstChild
}
返回片段;
}
接下来,我们需要遍历每个节点,对包含相关指令和模板语法的节点进行特殊处理。首先,我们需要执行最简单的模板语法处理,并使用“{{variable}}”语法的常规解析。
函数编译元素(el) {
var child nodes=El . child nodes;
var self=this
[].slice.call(childNodes)。forEach(函数(节点){
var reg=/\{\{(。*)\}\}/;//匹配{{xx}}
var text=node.textContent
如果(自我。istext节点(节点)寄存器。test(text)){//确定它是否是符合此形式的指令{{}}
self.compileText(node,reg . exec(text)[1]);
}
if(node . child nodes node . child nodes . length){
self.compileElement(节点);//继续递归遍历子节点
}
});
},
函数compileText (node,exp) {
var self=this
var init text=this . VM[exp];
updateText(node,initText);//初始化视图中的初始化数据
Newwatcher (this.vm,exp,function(value){//生成订阅者并绑定更新函数。
self.updateText(节点,值);
});
},
函数updateText(节点,值){
node.textContent=typeof value==未定义?“”:值;
}
获得最外层节点后,调用compileElement函数判断所有子节点。如果节点是文本节点切匹配{{}}形式的指令,编译并初始化相应的参数。
然后需要为当前参数生成对应的更新函数订阅者,当数据发生变化时更新对应的DOM。
这样就完成了解析、初始化、编译三个过程。
接下来,可以修改myVue,使用模板变量进行双向数据绑定。
https://jsrun.net/K4IKp/embed.
5、添加解析事件
添加Compile后,基本完成了一个数据的双向绑定。下一步是添加更多的指令进行编译,比如v-model、v-on、v-bind等。
添加一个 v-model 和 v-on 解析:
函数编译(节点){
var nodeAttrs=node.attributes
var self=this
array . prototype . foreach . call(nodeAttrs,function(attr) {
var attrName=attr.name
if(is direct(attrName)){
var exp=属性值;
var dir=attrname . substring(2);
if(iseventdirection(dir)){
//事件指令
self.compileEvent(node,self.vm,exp,dir);
}否则{
//垂直模型指令
self.compileModel(node,self.vm,exp,dir);
}
node.removeAttribute(属性名);//解析后,移除该属性。
}
});
}
//垂直指令解析
函数是直接的(属性){
return attr . index of( v-)==0;
}
//on:指令解析
函数iseventdirection(dir){
return dir . index of( on:)===0;
}
上面的编译函数是用来遍历当前dom的所有节点属性,然后判断属性是否是指令属性,如果是则做相应的处理(事件会听事件,数据会听数据.)
6、完整版 myVue
在MyVue中添加mounted方法,该方法将在所有操作完成后执行。
MyVue类{
构造函数(选项){
var self=this
this . data=options . data;
this . methods=options . methods;
Object.keys(this.data)。forEach(函数(键){
self.proxyKeys(键);
});
观察(this . data);
新编译(options.el,this);
options . mounted . call(this);//一切处理完毕后执行挂载的函数。
}
proxyKeys(键){
//将this.data属性代理到此
var self=this
Object.defineProperty(this,key,{
可枚举:false,
可配置:真,
get:函数getter() {
return self . data[key];
},
set:函数setter(newVal) {
self . data[key]=new val;
},
});
}
}
然后就可以测试使用了。
https://jsrun.net/Y4IKp/embed.
总结一下过程,回头看看这张图。是不是清楚多了?
可以查看的代码地址:Vue2.x双向绑定的原理与实现
关于Vue2.x双向绑定的原理和实现的文章到此为止,更多关于Vue数据双向绑定的原理,请搜索我们之前的文章或者继续浏览下面的相关文章。希望大家以后能多多支持我们!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。