4.1 vm.$watch
用法:
用于观察一个表达式或者computed函数在Vue.js实例上的变化。回调函数调用时,会从参数得到新数据和旧数据。表达式只接受以点分隔的路径(如a.b.c)。如果是一个比较复杂的表达式,可以用函数代替表达式。
vm.$watch( expOrFn, callback, [options] )
- expOrFn是被watch的东西,可以是String也可以是Function。
- 参数deep可以用来发现对象内部值的变化,监听数组的变动时用不到。
- 参数immediate表示是否立即以表达式的当前值触发回调。
原理:
vm.$watch是对Watcher的一种封装
Vue.prototype.$watch=function(expOrFn, cb, options){
const vm=this
options=options || {}
const watcher =new Watcher(vm, expOrFn, cb, options)
if(options.immediate){
cb.call(vm, watcher.value)
}
return function unwatchFn(){
watcher.teardown()
}
}
代码解析:
- 通过Watcher完全可以实现vm.$watch的功能,但是vm.$watch中的参数deep和immediate是Watcher中所没有的。
修改:新增判断expOrFn类型的逻辑。如果expOrFn是函数,则直接将它赋值给getter;如果不是函数,再使用parsePath函数来读取keypath中的数据。
export default class Watcher{
constructor (vm, expOrFn, cb){
this.vm=vm
// expOrFn参数支持函数
if(typeof expOrFn==='function'){
this.getter=expOrFn
}else{
this.getter=parsePath(expOrFn)
}
this.cb=cb
this.value=this.get()
}
......
}
代码解析:
- 当expOrFn是函数时,不只可以动态的返回数据,其中读取的所有数据也都被Watcher观察。当expOrFn时字符串类型的keypath时,Watcher会读取这歌keypath所指向的数据并观察这个数据的变化。而当expOrFn是函数时,Watcher会同时观察expOrFn函数中读取的所有Vue.js实例上的响应式数据。即如果函数从Vue.js实例上读取了两个数据,那么Watcher会同时观察这两个数据的变化,当其中任意一个发生变化时,Watcher都会得到通知。
- 执行new Watcher后,代码会判断用户是否使用了immediate函数,若是,则立即执行一次cb。
- 返回函数unwatchFn表示取消观察数据。执行的watcher.teardown()本质是把watcher实例从当前正在观察的状态的依赖列表中移除。
teardown()函数的实现:首先在WAtcher中记录自己都订阅了谁,即watcher实例被收集进了哪些Dep中。若不想继续订阅,循环自己记录的订阅列表来通知Dep将自己从Dep的依赖列表中移除。
export default class Watcher{
constructor (vm, expOrFn, cb){
this.vm=vm
this.deps=[] //新增
this.depIds=new Set()
this.getter=parsePath(expOrFn)
this.cb=cb
this.value=this.get()
}
......
addDep(dep){
const id=dep.id
if(!this.depIds.has(id)){
this.depIds.add(id)
this.depIds.push(dep)
dep.addSub(this)
}
}
...
}
实现思路:
- 先在Watcher中添加addDep方法,该方法的作用是在Watcher中记录自己订阅过的Dep。
- depIds用来判断如果当前Watcher已经订阅了该Dep,则不会重复订阅。(因为Watcher每次读取value都会触发收集依赖的逻辑。当依赖发生变化时,会通知Watcher重新读取最新的数据,如果诶一判断,就会导致每当数据发生变化,Watcher都会读取最新的数据,就导致Dep中的依赖有重复。这样当数据发生变化时,会通知多个Watcher。为避免这个问题,只有第一次触发getter的时候才会收集依赖。)
- 执行this.depIds.add来记录当前Watcher已经订阅了这个Dep。
- 执行this.deps.push(dep)记录自己都订阅了哪些Dep。
- 触发dep.addSub(this)来将自己订阅到Dep中。
在Watcher中新增addDep方法后,Dep中收集依赖的逻辑也需要有所改变:
let uid=0
export default class Dep{
constructor(){
this.id=uid++ //新增
this.subs=[]
}
......
depend(){
if(window.target){
// this.addSub(window.target) //废弃
window.target.addDep(this) //新增
}
}
}
Dep会记录数据发生变化时,需要通知哪些Watcher,而Watcher中也同样记录了自己会被哪些Dep通知。
现在正式介绍teardown()函数
teardown(){
let i=this.deps.length
while(i--){
this.deps[i].removeSub(this)
}
}
export default class Dep{
......
removeSub(sub){
const index=this.subs.indexOf(sub)
if(index>-1){
return this.subs.splice(index,1)
}
}
......
}
网友评论