1.依赖收集器Dep:
constructor中有个subs的数组,用于收集观察者addSub方法:用于将所有的观察者添加到subs数组中- 看上图的
Dep → Watcher这一步,还需要通知变化,所以需要一个notify方法,内部遍历每一个watcher观察者,去更新发生变化的部分
//依赖收集器
class Dep {
constructor() {
this.subs = []
}
//收集观察者
addSub(watcher){
this.subs.push(watcher)
}
//通知观察者去更新
notify(){
// 每个观察者有个自己对应的update方法,然后去更新视图
this.subs.forEach(watcher=>watcher.update())
}
}
2.观察者Watcher:
- 观察者主要是通过比较
新值和旧值的变化,然后决定是否调用回调函数callback更新视图 - getOldVal方法中通过之前文章中写过的
compileUtil对象的getValue方法来获取值
//观察者
class Watcher {
constructor(vm,attrValue,callback) {
//获取旧值
this.oldVal = this.getOldVal()
this.vm = vm;
this.attrValue = attrValue;
this.callback = callback;
}
//更新视图的方法,判断新值和旧值是否一样,从而判断是否更新视图
update(){
const newVal = compileUtil.getValue(this.attrValue,this.vm)
if(newVal !==this.oldVal){
this.callback(newVal)
}
}
// 通过compileUtil对象的getValue方法获取到旧值
getOldVal(){
return compileUtil.getValue(this.attrValue,this.vm);
}
}
3.Dep和Observer关联:
首先是在初始化一开始Observer类中截取数据的defineReactive的get方法,添加观察者wathcer
-
通过
const dep = new Dep();生成了收集器Dep的一个实例 -
然后通过
dep.addSub方法去添加观察者watcher,但是问题来了,如果获得观察者watcher并添加进去呢?或者说,如何在实例化Dep的时候,能后拿到watcher观察者呢?解决办法如下:-
为了让
Watcher和Dep建立联系,在初始化调用this.getOldeVal()方法的时候,让Dep类的target属性等于this,也就是当前wathcer实例,获取完了oldVal之后将Dep类的target清空() -
//Watcher类(观察者) class Watcher { constructor(vm,attrValue,callback) { //获取旧值 this.vm = vm; this.attrValue = attrValue; this.callback = callback; this.oldVal = this.getOldVal() } //更新视图的方法,判断新值和旧值是否一样,从而判断是否更新视图 update(){ const newVal = compileUtil.getValue(this.attrValue,this.vm) if(newVal !==this.oldVal){ this.callback(newVal) } } // 通过compileUtil对象的getValue方法获取到旧值 getOldVal(){ Dep.target = this; const oldVal = compileUtil.getValue(this.attrValue,this.vm); Dep.target = null; return oldVal; } }
-
-
然后在
Observer中,初始化劫持对象属性的时候--Dep.target && dep.addSub(Dep.target),就说如果Dep的target有属性,那就通过dep实例方法addSub添加进收集器中。 -
set方法中设置新值的时候,需要通过dep实例的notify方法通知变化。
//数据劫持类Observer
class Observer {
constructor(data) {
this.observe(data);
}
observe(data){
if(data && typeof data ==="object"){
console.log("我是data数据中的所有key键",Object.keys(data))
Object.keys(data).forEach(key=>{
this.defineReactive(data,key,data[key])
})
}
}
defineReactive(obj,key,value){
//递归遍历
this.observe(value);
//在初始化劫持数据的时候就创建dep实例,对每个属性进行关联
const dep = new Dep();
Object.defineProperty(obj,key,{
enumerable:true, //可遍历枚举的
configurable:false,
get(){
//订阅数据变化,往dep中添加观察者
Dep.target && dep.addSub(Dep.target)
return value
},
set:(newVal)=>{
//这里调用observe让新设置的值newVal也支持监听
this.observe(newVal)
if(newVal !== value){
value = newVal;
}
//通知变化
dep.notify()
}
})
}
}
4.Watcher观察者添加绑定时机
Watcher是在解析指令渲染页面的时候进行的,也就是compileUtil对象中,先来看下html方法中的处理
-
html方法中直接通过new Watcher实例化,回调函数中通过this.updater.htmlUpdater(node,newVal);来更新值。new Watcher(vm,attrValue,(newVal)=>{ this.updater.htmlUpdater(node,newVal); })
//compileUtil方法
const compileUtil = {
text(node,attrValue,vm){
let value ;
if(attrValue.indexOf('{{') !== -1)
{
value = attrValue.replace(/\{\{(.+?)\}\}/g,(...args)=>{
console.log("我是args",args)
return this.getValue(args[1],vm)
})
}else
{
value = this.getValue(attrValue,vm);
}
this.updater.textUpdater(node,value)
},
html(node,attrValue,vm){
const value = this.getValue(attrValue,vm);
new Watcher(vm,attrValue,(newVal)=>{
this.updater.htmlUpdater(node,newVal);
})
this.updater.htmlUpdater(node,value);
},
model(node,attrValue,vm){
const value = this.getValue(attrValue,vm);
this.updater.modelUpdater(node,value);
},
on(node,attrValue,vm,eventName){
//获取到options中data的方法
const handler = vm.$options.methods && vm.$options.methods[attrValue];
node.addEventListener(eventName,handler.bind(vm),false);
},
// 更新函数对象
updater:{
textUpdater(node, value) {
node.textContent = value;
},
htmlUpdater(node, value) {
node.innerHTML = value;
},
modelUpdater(node, value) {
node.value = value;
}
},
// 获取<div v-text='person.name'></div>中person.name以及其他形式的值
// 这里将vm.$data传入,第一次的随后prevValue的值就是vm.$data
//然后return 回去的prevValue将作为新的prevValue,之道获取到person.name的值为止。
getValue(attrValue,vm){
return attrValue.split(".").reduce((prevValue,currValue)=>{
console.log("我是currentValue",currValue)
return prevValue[currValue]
},vm.$data)
}
}
测试:
-
刷新inex.html页面,打开控制台,输入
vm.$data.htmlStr="<h1>我是测试compileUtile中html方法的</h1>"✅正确结果如下图:
-
再来看下如果Watcher类中,如果不将
Dep.target = null;会出现如下图结果:
getOldVal(){
Dep.target = this;
const oldVal = compileUtil.getValue(this.attrValue,this.vm);
//Dep.target = null;
return oldVal;
}
❎打印了多个Watcher,就是说,多次设置vm.$data.htmlStr时,上一次的Wathcer没有被清空。
流程梳理:
修改数据后 → Observer的set方法中 → 调用dep.notify方法通知观察者更新 → this.subs.forEach(watcher=>watcher.update())调用对应的watcher方法 → wathcer的update中有自己的回调函数,更新页面渲染。
5.compileUtil中其他方法的处理:
1.text方法:
- 主要是更新调用
this.updater.textUpdater时,需要通过this.getContent(attrValue,vm)重新获取值。
const compileUtil = {
text(node,attrValue,vm){
let value ;
//这里需要对{{这种形式进行处理 <h2>{{person.name}} -- {{person.age}}</h2>
if(attrValue.indexOf('{{') !== -1)
{
value = attrValue.replace(/\{\{(.+?)\}\}/g,(...args)=>{
//可能会是 {{person.name}} -- {{person.age}} 这种形式
new Watcher(vm,args[1],()=>{
this.updater.textUpdater(node,this.getContent(attrValue,vm));
})
return this.getValue(args[1],vm)
})
}else
{
value = this.getValue(attrValue,vm);
}
this.updater.textUpdater(node,value)
},
// 更新函数对象
updater:{
textUpdater(node, value) {
node.textContent = value;
},
htmlUpdater(node, value) {
node.innerHTML = value;
},
modelUpdater(node, value) {
node.value = value;
}
},
// 获取<div v-text='person.name'></div>中person.name以及其他形式的值
// 这里将vm.$data传入,第一次的随后prevValue的值就是vm.$data
//然后return 回去的prevValue将作为新的prevValue,之道获取到person.name的值为止。
getValue(attrValue,vm){
return attrValue.split(".").reduce((prevValue,currValue)=>{
console.log("我是currentValue",currValue)
return prevValue[currValue]
},vm.$data)
},
// 重新处理getContent方法
getContent(attrValue,vm){
return attrValue.replace(/\{\{(.+?)\}\}/g,(...args)=>{
return this.getValue(args[1],vm)
})
}
}
测试:
控制台输入vm.$data.msg="测试compileUtil的text方法"会得到