Vue双向绑定的实现原理系列(三):监听器Observer和订阅者Watcher
监听器Observer和订阅者Watcher
实现简单版Vue的过程,主要实现{{}}、v-model和事件指令的功能
主要分为三个部分
1.数据监听器Observer,能够对数据对象的所有属性进行监听;
实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性
2.Watcher将数据监听器和指令解析器连接起来,数据的属性变动时,执行指令绑定的相应回调函数,
1.如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。
3.指令解析器Compile,
对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher
因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。=
监听器Observer
Observer是一个数据监听器,核心是前面一直谈论的Object.defineProperty(),
对所有属性监听,利用递归来遍历所有的属性值,对其进行Object.defineProperty()操作:
function definReactive(data,key,val){
observers(val);//递归所有子属性
Object.defineProperty(data,key,{
enumerable:true,
configurable:true,
get:function(){
console.log('属性'+key+'执行get');
return val;
},
set:function(newVal){
val = newVal;
console.log('属性:'+key+'以及被监听,现在值为:'+newVal.toString());
}
})
}
function observers(data){
if(!data || typeof data!='object'){
return;
}
Object.keys(data).forEach(function(key){
definReactive(data,key,data[key]);
})
}
var library = {
book1:{name:''},
book2:''
}
observers(library);
library.book1.name = 'vue书籍';
library.book2 = '没有书';
//属性book1执行get
//属性:name以及被监听,现在值为:vue书籍
//属性:book2以及被监听,现在值为:没有书
接下来创建一个收集所有订阅者的订阅器Dep,阅器Dep主要负责收集订阅者,然后再属性变化的时候执行对应订阅者的更新函数,
再改写一下订阅器Observer,创建一个observer.js:
function Observe(data){
this.data = data;
this.walk(data);
}
Observe.prototype = {
walk:function(data){
var self = this;
Object.keys(data).forEach(function(key) {
self.defineReactive(data, key, data[key]);
});
},
defineReactive:function(data,key,val){
observers(val);//递归所有子属性
var dep = new Dep();
Object.defineProperty(data,key,{
enumerable:true,
configurable:true,
get:function(){
if(是否需要添加订阅者){
dep.addSub(Watcher);//在这里添加一个订阅者
}
console.log('属性'+key+'执行get');
return val;
},
set:function(newVal){
if(val === newVal){
return;
}
val = newVal;
dep.notify();//如果数据变化,通知所有订阅者
console.log('属性:'+key+'以及被监听,现在值为:'+newVal.toString());
}
})
}
}
function observers(data){
if(!data || typeof data!='object'){
return;
}
return new Observe(data);
}
/**Dep:创建一个可以容纳订阅者的消息订阅器
* **/
function Dep(){
this.subs = [];
}
Dep.prototype = {
addSub:function(sub){//添加订阅者
this.subs.push(sub);
},
notify:function(){//通知订阅者
this.subs.forEach(function(sub){
sub.update();
})
}
}
可以看出,订阅器Dep,添加一个订阅者是在Object.defineProperty()的get里面,这是为了让Watcher初始化进行触发,
因此要判断是不是需要添加订阅者,后面解释。在set里面,如果数据变化,就会通知所有的订阅者,订阅者就会去执行对应的更新的函数
以上,一个完整的订阅器完成。
订阅者Watcher
Watcher在初始化的时候要将自己添加进订阅者Dep中,如何做到:
已经知道监听器Observer是在get函数执行了添加订阅者Wather的操作的,
所以我们只要在订阅者Watcher初始化的时候触发对应的get函数,去执行添加订阅者操作即可,
那要如何触发get的函数:
只要获取对应的属性值就可以触发了,核心原因就是因为我们使用了Object.defineProperty()进行数据监听。
注意:
我们只要在订阅者Watcher初始化的时候才需要添加订阅者,所以需要做一个判断操作,
因此可以在订阅器上做一下手脚:在Dep.target上缓存下订阅者,添加成功后再将其去掉就可以了。
创建一个watcher.js
function Watcher(vm,exp,cb){
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.value = this.get();//将自己添加到订阅器的操作
}
Watcher.prototype = {
update:function () {
this.run();
},
run:function () {
var value = this.vm.data[this.exp];
var oldVal = this.value;
if(value != oldVal){
this.value = value;
this.cb.call(this.vm,value,oldVal);
}
},
get:function () {
Dep.target = this;//缓存自己
var value = this.vm.data[this.exp];//强制执行监听器observer里的Object.defineProperty()里的get函数
Dep.target = null;//释放自己
return value;
}
}
再调整下observer.js的defineReactive函数里的get操作:
defineReactive:function(data,key,val){
observers(val);//递归所有子属性
var dep = new Dep();
Object.defineProperty(data,key,{
enumerable:true,
configurable:true,
get:function(){
if(Dep.target){
dep.addSub(Dep.target);//在这里添加一个订阅者
}
console.log('属性'+key+'执行get');
return val;
},
set:function(newVal){
if(val === newVal){
return;
}
val = newVal;
dep.notify();//如果数据变化,通知所有订阅者
console.log('属性:'+key+'以及被监听,现在值为:'+newVal.toString());
}
})
}
//Dep加个target属性
function Dep(){
this.subs = [];
this.target = null;
}
简单版的Watcher设计好了,
只要将Observer和Watcher关联起来,就可以实现一个简单的双向绑定数据了。
这里没有还没有设计解析器Compile,所以对于模板数据我们都进行写死处理:
模板有个节点,id为name,双向数据绑定的变量name,这里大框号暂时没有用:
<body>
<h1 id="name">{{name}}</h1>
</body>
selVue.js 关联Observer和Watcher
function SelfVue(data,el,exp){
this.data = data;
observers(data);
el.innerHTML = this.data[exp];//初始化模板数据的值
new Watcher(this,exp,function(value){
el.innerHTML = value;
});
return this;
}
页面上实现双向数据绑定:
<h1 id="name">{{name}}</h1>
<script src="js/observer.js"></script>
<script src="js/watcher.js"></script>
<script src="js/selfVue.js"></script>
<script>
var ele = document.querySelector('#name');
var self_Vue = new SelfVue({
name:'第一次显示数据'
},ele,'name');
window.setTimeout(function(){
console.log('值变了');
self_Vue.data.name = '重新赋值了';
},2000);
</script>
打开页面,可以看到页面刚开始显示了是“第一次显示数据”,过了2s后就变成“重新赋值了”了。到这里,完成了一部分
注意:赋值的时候是 self_Vue.data.name = '重新赋值了',但是希望是 self_Vue.name = '重新赋值了',
需要在new SelVue的时候做个代理,让访问self_Vue的属性代理为访问self_Vue.data的属性,
实现原理还是使用Object.defineProperty()对属性再包一层,
修改selVue.js:
function SelfVue(data,el,exp){
var self = this;
this.data = data;
Object.keys(data).forEach(function (key) {
self.proxyKeys(key);//绑定代理属性
});
observers(data);
el.innerHTML = this.data[exp];//初始化模板数据的值
new Watcher(this,exp,function(value){
el.innerHTML = value;
});
return this;
}
SelfVue.prototype = {
proxyKeys:function(key){
var self = this;
Object.defineProperty(this,key,{
enumerable:false,
configurable:true,
get:function proxyGetter(){
return self.data[key];
},
set:function proxySetter(newVal){
self.data[key] = newVal;
}
})
}
}
//这下我们就可以直接通过self_Vue.name = '重新赋值了'的形式来进行改变模板数据。
至此监听器Observer和订阅者Watcher功能基本完成,后面再加上指令解析器compile的功能!
系列文章的目录:
Vue双向绑定的实现原理系列(一):Object.defineproperty
Vue双向绑定的实现原理系列(二):设计模式
Vue双向绑定的实现原理系列(三):监听器Observer和订阅者Watcher
Vue双向绑定的实现原理系列(四):补充指令解析器compile
Vue双向绑定的实现原理系列(三):监听器Observer和订阅者Watcher的更多相关文章
- Vue双向绑定的实现原理系列(四):补充指令解析器compile
补充指令解析器compile github源码 补充下HTML节点类型的知识: 元素节点 Node.ELEMENT_NODE(1) 属性节点 Node.ATTRIBUTE_NODE(2) 文本节点 N ...
- Vue双向绑定的实现原理系列(一):Object.defineproperty
了解Object.defineProperty() github源码 Object.defineProperty()方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象. ...
- 梳理vue双向绑定的实现原理
Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过Object.defineProperty来劫持数据的setter,getter,在数据变动时发布消息给订阅者,订阅者收到消息后 ...
- Vue双向绑定的实现原理及简单实现
vue数据双向绑定原理 vue数据双向绑定是通过(数据劫持)+(发布者-订阅者模式)的方式来实现的,而所谓的数据劫持就是通过Object.defineProperty() 来实现的,所谓的Obje ...
- vue双向绑定原理及实现
vue双向绑定原理及实现 一.总结 一句话总结:vue中的双向绑定主要是通过发布者-订阅者模式来实现的 发布 订阅 1.单向绑定和双向绑定的区别是什么? model view 更新 单向绑定:mode ...
- 通俗易懂了解Vue双向绑定原理及实现
看到一篇文章,觉得写得挺好的,拿过来给大家分享一下,刚好解答了一些困扰我的一些疑惑!!! 1. 前言 每当被问到Vue数据双向绑定原理的时候,大家可能都会脱口而出:Vue内部通过Object.defi ...
- vue双向绑定(数据劫持+发布者-订阅者模式)
参考文献:https://www.cnblogs.com/libin-1/p/6893712.html 实现mvvm主要包含两个方面,数据变化更新视图,视图变化更新数据. 关键点在于data如何更新v ...
- Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)
Vue.js双向绑定的实现原理 解析 神奇的 Object.defineProperty 这个方法了不起啊..vue.js和avalon.js 都是通过它实现双向绑定的..而且Object.obser ...
- vue双向绑定的原理及实现双向绑定MVVM源码分析
vue双向绑定的原理及实现双向绑定MVVM源码分析 双向数据绑定的原理是:可以将对象的属性绑定到UI,具体的说,我们有一个对象,该对象有一个name属性,当我们给这个对象name属性赋新值的时候,新值 ...
随机推荐
- A. Coffee Break(思维题,类似于邻接表的head数组用法)
题:https://codeforces.com/gym/101911/problem/A 题意:每天工作m分钟,每次喝coffee得间隔d分钟,然后给出n个数,每个数表示想在一天中的a[i]的时刻喝 ...
- 头次接触wamp服务器、xampp,初次单独使用tomcat部署
刚刚经过了近两天的接触wamp.xampp.tomcat的时光,真的爽 导师有个网站打不开了,就让我去弄,还有一个网站的后台密码忘了,让我帮忙找回来.我第一感觉就是第一个活不简单,第二个还不简单吗?打 ...
- TCP与UDP 笔记
本文整理自:<图解TCP/IP 第5版>作者:[日] 竹下隆史,[日] 村山公保,[日] 荒井透,[日] 苅田幸雄 著译者:乌尼日其其格出版时间:2013-07 TCP提供可靠的通信传输, ...
- [LC] 287. Find the Duplicate Number
Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), pro ...
- 密码子演化假说|凝固事件假说|立体化学假说|共进化假说|代谢途径相关性假说|四重兼并|假四重兼并|最小损伤原则|AU-rich|GC-rich|逐步进化假说|分子机制进化假说
生命组学 将密码子表重排后发现,嘌呤嘧啶含量不同,密码子的氨基酸种类由第一二位决定,同时第三位变化大却没有蛋白质层面上实质性的改变,这说明第三位氨基酸是用于维持氨基酸组成不发生变化同时保证蛋白质稳定性 ...
- pycharm全局搜索快捷键无反应
原因:和搜狗输入法的快捷键冲突
- spring学习笔记三:Component注解(把POJO类实例化到spring的IOC容器中)
Component注解:把普通的POJO 类实例化到spring的IOC容器中,就是定义成<bean id="" class=""> 项目目录树: ...
- 吴裕雄--天生自然 oracle学习笔记:oracle理论学习详解及各种简单操作例子
1. 数据库的发展过程 层次模型 -->网状模型 -->关系模型 -->对象关系模型 2. 关于数据库的概念 DB:数据库(存储信息的仓库) DBMS:数据库管理系统(用于管理数据库 ...
- npm安装依赖太慢问题
执行 npm install 会发现很慢,可以在安装时手动指定从哪个镜像服务器获取资源,我使用的是阿里巴巴在国内的镜像服务器. 命令如下: npm install --registry=https:/ ...
- Luogu_1280_尼克的任务
题目描述 尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成. 尼克的一个工作日为N分钟,从第一分钟开始 ...