前言
在上一节把数据劫持简单的实现了一下,但是目前我们只能在控制台中测试看到数据的变化,这节会在原来的基础上逐渐丰富。这节我们要实现的是Watcher
观察者,即数据变化后视图也会进行更新,此外还会使用Dep
订阅器来对观察者进行收集。
实现Dep订阅器
作用
- 添加观察者
- 通知观察者去更新视图
实现
既然要对观察者进行添加,那么我们可以在Observer.js
文件中写出以下代码:
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
类,因为我们首先需要在里面获取到旧值和新值,然后在进行比较,所以我们需要传递一些可以获取旧值和新值的参数。
js
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
参数是为了把新值回调到模版编译里即更新视图,也就是说会形成一个闭环,别着急接着往下看。
js
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
参数的原因。
js
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
就可以顺利添加观察者了。
js
// 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
方法会根据当前的值与旧值进行比较,有变化就会把新的值回调到更新页面的方法中去,这样就形成了一个闭环!
js
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
中的观察者更新视图。
完结撒花!