github源码vue
1.数据监听器Observer,可以对数据对象的全部属性进行监听; 实现数据的双向绑定,首先要对数据进行劫持监听,因此咱们须要设置一个监听器Observer,用来监听全部属性 2.Watcher将数据监听器和指令解析器链接起来,数据的属性变更时,执行指令绑定的相应回调函数, 1.若是属性发上变化了,就须要告诉订阅者Watcher看是否须要更新。 3.指令解析器Compile, 对每一个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher 由于订阅者是有不少个,因此咱们须要有一个消息订阅器Dep来专门收集这些订阅者,而后在监听器Observer和订阅者Watcher之间进行统一管理的。=
监听器Observergit
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:github
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里面,若是数据变化,就会通知全部的订阅者,订阅者就会去执行对应的更新的函数 以上,一个完整的订阅器完成。
订阅者Watchersegmentfault
Watcher在初始化的时候要将本身添加进订阅者Dep中,如何作到:已经知道监听器Observer是在get函数执行了添加订阅者Wather的操做的,设计模式
因此咱们只要在订阅者Watcher初始化的时候触发对应的get函数,去执行添加订阅者操做便可,缓存
那要如何触发get的函数:bash
只要获取对应的属性值就能够触发了,核心缘由就是由于咱们使用了Object.defineProperty()进行数据监听。函数
注意:this
咱们只要在订阅者Watcher初始化的时候才须要添加订阅者,因此须要作一个判断操做,prototype
所以能够在订阅器上作一下手脚:在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