一、Dep
- Dep的作用是收集观察者以及当数据发生变动时通知观察者去更新
- 每一个属性都有自身的dep,接着添加watcher,在每次数据变动时(即set),通知自身的dep,dep通知其中watcher去完成视图更新
class Dep {
constructor () {
this.subs = []
}
// 收集观察者
addSub (watcher) {
this.subs.push(watcher)
}
// 通知观察者去更新
notify () {
this.subs.forEach(w => w.update())
}
}
二、Watcher
- Watcher观察者主要作用是接收自身Dep的通知后执行update()函数去更新视图
class Watcher {
constructor (vm, expr, cb) {
this.vm = vm
this.expr = expr
this.cb = cb
// 先把旧值保存起来
this.oldVal = this.getOldVal()
}
getOldVal () {
Dep.target = this // 在定义watcher时在Dep类上增加属性target指向自身watcher
const oldVal = compileUtil.getVal(this.expr, this.vm) // 获取数据时就在属性自身的dep上把该watcher添加上去
Dep.target = null // 不设置为null的话每次获取数据就会一直往对应的dep中加入watcher(这时的watcher看此时Dep的target的指向),数据更改后就会执行该dep中的所有watcher的update()
return oldVal
}
update () {
const newVal = compileUtil.getVal(this.expr, this.vm)
if(newVal !== this.oldVal) { // 值改变才去更新
this.cb(newVal) // 调用cb函数处理视图
this.oldVal = newVal // 把watcher的旧值改为新值
}
}
}
三、Observer和Dep的关联
- 顺序是这样的,先
new Vue()
然后new Observer()
然后new Compile()
- 在
new Observer()
时创建属性各自的dep
- 在
new Compile()
中new Watcher()
,执行new Watcher()
时:会先绑定Dep
类上的target
为这个new Watcher()
,接着执行,需要获取数据getOldVal()
,就会执行这里的get()
,这样子就顺利把每个属性的watcher
添加到各自的dep
中了 - 在数据发生变动时,
dep.notify()
即可
class Observer {
...
defineReactive (obj, key, value) {
this.observe(value) // 如果是对象的话就会继续递归监听
const dep = new Dep() // 定义该属性自身的dep
Object.defineProperty(obj, key, {
enumerable: true,
configurable: false,
get () {
Dep.target && dep.addSub(Dep.target) // 顺序是这样的,先new Vue()然后new Observer()然后new Compile()
// 在new Compile()中new Watcher(),执行new Watcher()时:会先绑定Dep类上的target为这个new Watcher(),接着执行,需要获取数据getOldVal(),就会执行这里的get(),这样子就顺利把每个属性的watcher添加到各自的dep中了
return value
},
set: (newVal) => { // 使用普通函数的话this指向obj
if(newVal !== value) {
this.observe(newVal) // 对新值也要进行监听
value = newVal
}
dep.notify() // 当数据变动时通知自身的dep
}
})
}
}
四、创建watcher的时机
- 在初始化
new Compile()
的过程中 - 在
new Watcher()
中把watcher
添加到属性自身的dep
- 仅当初始化完成后,在用户交互过程中数据变动才会触发
dep
去执行watcher.update()
五、数据变动修改视图
修改compileUtil
:
-
v-html
中增加watcher
const compileUtil = {
html (node, expr, vm) {
const value = this.getVal(expr, vm)
new Watcher(vm, expr, (newVal) => { // 当数据变化时执行该回调函数修改对应的视图
this.updater.htmlUpdater(node, newVal)
})
this.updater.htmlUpdater(node, value)
},
}
-
v-model
中增加watcher
:
const compileUtil = {
model (node, expr, vm) {
const value = this.getVal(expr, vm)
// 绑定更新函数,数据=>视图
new Watcher(vm, expr, (newVal) => { // 当数据变化时执行该回调函数修改对应的视图
this.updater.modelUpdater(node, newVal)
})
this.updater.modelUpdater(node, value)
},
}
-
v-text
以及普通文本的修改:
const compileUtil = {
text (node, expr, vm) {
let value
let reg = /\{\{(.+?)\}\}/g
if(reg.test(expr)) { // 处理文本
value = expr.replace(reg, (...args) => { // 普通文本中可能有多个{{}}插值,因此要匹配出来逐一增加`watcher`
new Watcher(vm, args[1], () => {
this.updater.textUpdater(node, this.getContentVal(expr, vm))
})
return this.getVal(args[1], vm)
})
} else {
value = this.getVal(expr, vm) // 处理v-text
new Watcher(vm, expr, (newVal) => {
this.updater.textUpdater(node, newVal)
})
}
this.updater.textUpdater(node, value)
},
...
getContentVal (expr, vm) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getVal(args[1], vm)
})
},
}
至此,我们就实现了数据=>视图的功能了