前语
在上一节把数据劫持简略的完成了一下,可是现在咱们只能在操控台中测试看到数据的改变,这节会在原来的基础上逐渐丰厚。这节咱们要完成的是Watcher
观察者,即数据改变后视图也会进行更新,此外还会运用Dep
订阅器来对观察者进行搜集。
完成Dep订阅器
效果
- 增加观察者
- 告诉观察者去更新视图
完成
既然要对观察者进行增加,那么咱们能够在Observer.js
文件中写出以下代码:
class Dep {
constructor() {
this.subs = [];
}
// 搜集观察者
addSub(watcher) {
this.subs.push(watcher);
}
// 告诉观察者
notify() {
this.subs.forEach(w => w.update());
}
}
经过addSub
办法完成第一个对观察者的增加,notify
办法会告诉每一个观察者去更新视图,所以咱们还需求在观察者中定义一个update
办法。
完成Watcher观察者
效果
- 检查旧值和新值有没有改变
- 有改变则调用更新视图的办法
完成
接着咱们需求继续在Observe.js
文件中新增Watcher
类,因为咱们首先需求在里面获取到旧值和新值,然后在进行比较,所以咱们需求传递一些能够获取旧值和新值的参数。
class Watcher {
constructor(vm, expr, cb) {
this.vm = vm;
this.expr = expr;
this.cb = cb;
this.oldValue=this.getOldValue()
}
getOldValue() {
const oldValue=compileUtil.getVal(this.expr,this.vm);
return oldValue
}
}
上述代码中在getOldValue
办法中经过再次调用compileUtil
中的getVal
办法来获取当时的值,其间cb
参数是为了把新值回调到模版编译里即更新视图,也便是说会形成一个闭环,别着急接着往下看。
update() {
const newValue=compileUtil.getVal(this.expr,this.vm);
if(newValue!==this.oldValue){
this.cb(newValue);
}
}
声明update
办法来比较旧值和新值,如果有改变就把新的值回调回去。
再来看这张图,现在Dep
和Watcher
已经创立好了,它们之间怎样进行相关呢?由图可知,需求把Observer
和Dep
、Dep
和Watcher
、Compile
和Watcher
进行相关。
也便是说在订阅数据改变时要在往Dep
中增加订阅者,所以这一步能够放在监听数据中的getter
的时分进行,可是订阅者Watcher
从哪里来呢?这儿就用到了一奇妙的方式,便是在进行模版编译时拿v-html
举例,在获取v-html
指令对应的值的时分咱们就给它创立一个Watcher
观察者并绑定更新函数,这也便是为什么在创立Watcher
类中传递cb
参数的原因。
html(node, expr, vm) {
const value = this.getVal(expr, vm);
// 绑定对应的watcher 订阅数据改变 绑定更新函数
new Watcher(vm, expr, (newVal) => {
this.updater.htmlUpdater(node, newVal)
})
this.updater.htmlUpdater(node, value);
}
绑定了Watcher
类之后咱们能够在Watcher
类中的getOldValue
办法中直接把当时Watcher
实例目标的this
赋值给Dep
,这样一来getter
中的Dep
就能够顺畅增加观察者了。
// Watcher
getOldValue() {
Dep.target = this;
const oldValue = compileUtil.getVal(this.expr, this.vm);
// 确保只增加一次观察者
Dep.target = null;
return oldValue
}
// Observer
defineReactive(obj, key, value) {
this.observer(value)
let dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 订阅数据改变时往Dep中增加观察者
Dep.target && dep.addSub(Dep.target)
return value
},
})
}
在数据发生改变时需求调用Dep
中的notify
办法,notify
办法会根据当时的值与旧值进行比较,有改变就会把新的值回调到更新页面的办法中去,这样就形成了一个闭环!
defineReactive(obj, key, value) {
this.observer(value)
let dep=new Dep();
Object.defineProperty(obj, key, {
set: (newVal) => {
this.observer(newVal)
if (newVal != value) {
value = newVal
dep.notify();
}
}
})
}
这样整个流程基本上就差不多完成了,最后再总结一下吧。
总结
标题:vue是怎么完成呼应式的/vue中的双向数据绑定原理?
vue的双向数据绑定首要是由
Compile
、Observer
、Dep
、Watcher
四部分组成,效果分别是Compile
用来初始化视图,对页面中的一些指令或特点进行解析(便是根据书写的一些vue语法在data中找到对应定义的值并烘托到页面中的过程),在这个过程中Compile
还会对页面用到的每个值进行观察者创当即绑定更新函数,用于更新视图。
Dep
中首要经过addSub
办法来增加订阅者以及运用notify
办法告诉watcher去更新视图。
Watcher
首要用来更新视图,经过getOldValue
获取到当时的值与旧值进行比较如果有改变会当即执行在模版编译阶段传递的回调函数进行数据的替换。
Observe
的效果便是运用Object.defineProperty
办法对所有数据进行劫持监听,在get
办法中进行依赖搜集并往Dep
中增加订阅者,在set
的时分会告诉Dep
中的观察者更新视图。
结束撒花!