Vue源码之watch

244 阅读3分钟

watch

抛出问题

  1. watch的几种写法
  1. watch何时创建,创建做了啥
  1. immediate如何立即监听
  1. deep如何深度监听
  1. 监听值改变的流程是怎样的
  1. sync如何同步调用监听

watch的几种写法

简言之就是直接调用和写在watch配置中,一种函数的写法一种对象的写法。

watch何时创建,创建做了啥

  1. initState时,会去处理各种选项,其中包括处理 watch

if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) }

  1. initWatch

这⾥就是对 watch 对象做遍历,拿到每⼀个 handler ,因为 Vue 是⽀持 watch 的同⼀个 key对应多个 handler ,所以如果 handler 是⼀个数组,则遍历这个数组,调⽤ createWatcher ⽅法,否则直接调⽤ createWatcher。

  1. createWatcher

对 hanlder 的类型做判断,拿到它的回调函数,最后调⽤vm.watch(keyOrFn,handler,options)函数,watch(keyOrFn, handler, options) 函数, watch 是 Vue 原型上的⽅法,它是在执⾏stateMixin 的时候定义的:

  1. $watch

最终会调⽤ watch⽅法,这个⽅法⾸先判断cb如果是⼀个对象,则调⽤createWatcher⽅法,这是因为watch ⽅法,这个⽅法⾸先判断 cb 如果是⼀个对象,则调⽤ createWatcher ⽅法,这是因为 watch ⽅法是⽤户可以直接调⽤的,它可以传递⼀个对象,也可以传递函数。接着执⾏第一步 const watcher = new Watcher(vm, expOrFn, cb, options) 实例化了⼀个user watcher ,因为 options.user = true 。

  1. Watcher

我们传过来的expOrFn不是函数而是字符串key,此时的getter调用parsePath方法,他最终会返回一个函数。

  1. 紧接着执行第二步this.get()

第一步就是丢入watcher队列和设置Dep.target为user Watcher,第二步就是执行上面返回的函数getter,注意此时传入了vm,然后此函数最终调用vm[key],也就是对key进行访问返回,这是就会触发key的getter,最后就是这个key收集了user Watcher这个依赖。然后执行第三步,处理深度监听不断递归深入读取对象因为读取,就可以让这个属性收集到 这User watcher 的原则这样不管对象内多深的属性变化,都会通知到User watcher这样就完成了深度监听。然后执行第四步推出watcher和清理Deps。然后依赖收集就结束啦。

immediate如何立即监听

通过实例化 watcher 的⽅式,⼀旦我们 watch 的数据发送变化,它最终会执⾏ watcher 的run ⽅法,执⾏回调函数 cb ,并且如果我们设置了 immediate 为 true,则直接会执⾏回调函数cb 。最后返回了⼀个 unwatchFn ⽅法,它会调⽤ teardown ⽅法去移除这个 watcher 。所以本质上侦听属性也是基于 Watcher 实现的,它是⼀个 user watcher。

执行完依赖收集,我们就跳出了new Watcher,执行第二步,这里一看就明白了啦,如果设置了immediate就会立即执行回调。最后返回一个unwatchFn函数。

deep如何深度监听

由上面第三步可知,处理深度监听不断递归深入读取对象因为读取,就可以让这个属性收集到 这User watcher 的原则,这样不管对象内多深的属性变化,都会通知到User watcher这样就完成了深度监听。

监听值改变的流程是怎样的

当此key发生变化时会触发set,然后触发dep.notify,然后触发User Watcher的Update,如果用户设置了sync属性为true这里就会立即同步执行run,而不是加入微任务中,如果sync为false就会执行queueWatcher(),就是加入微任务队列,在下一次执行,最终执行User Watcher.run()。

首先会执行this.get(),然后就是执行this.getter,也就是执行parsePath返回的那个函数,然后就是获取key的值,然后就会判断新旧值等不等或者新值是对象模式和deep为true,然后是user Watcher就会执行回调函数回调新值和旧值。

sync如何同步调用监听

由上可得,在执行update时sync为true时直接调用run,就可以在当前 Tick 中同步执⾏ watcher 的回调函数

总结

1.在初始化数据InitState如果定义了watch就会有个initWatch的操作,initWatch首先遍历watch,如果key的值为数组那么遍历数组调用createWatcher传入vm实例,name以及value,否则直接调用createWatcher。如果传入value为对象,那么把对象赋值给options,接着把对象的handler的值赋值给handler。也就是尝试把对象的形式的写法赋值为函数。如果value类型为string那么直接在vm实例上取此key的值函数赋值给handler。最后调用vm.$watch传入name handler函数以及options。

2.vm.watch首先判断传入的handler是否为对象,对于写在watch属性里,由上面可知传进来的是函数,但是对于直接使用vm.watch首先判断传入的handler是否为对象,对于写在watch属性里,由上面可知传进来的是函数,但是对于直接使用vm.watch创建的就要进行判断了,这是这个判断存在的原因。如果是对象那么自然而然去调用createWatcher尝试弄成函数再回来。接着将传入的options赋值给options没有则设为空对象,定义options的user属性为true。其次定义了watcher创建new Watcher实例传入vm,name,handler,options参数。

3.new Watcher首先设置user属性为true,设置deep属性为!!options.deep,设置sync属性为!!options.sync。然后犹如传入的expOrFn不为函数执行parsePath(expOrFn)将返回的函数赋值给this.getter。接着就是执行this.get(),首先执行pushTarget(this)把Dep.Watcher设置为user Watcher,然后执行this.getter执行parsePath(expOrFn)返回的函数传入vm实例。函数执行的内容就是访问vm实例上的此key的值返回。由于因为访问了key所以就会触发此key的依赖收集触发get最终此key收集了这个User Watcher。this.getter执行完毕后会判断用户是否设置了deep属性,如果设置了会调用traverse不断递归深入读取对象因为读取,就可以让这个属性收集到 这User watcher 的原则,这样不管对象内多深的属性变化,都会通知到User watcher这样就完成了深度监听。最后就是执行popTarget和cleanupDeps。

4.回到vm.$watch,new Watcher执行完毕后,会去判断用户是否设置了immediate属性,如果设置了就会立刻执行handler回调函数。最后返回了⼀个 unwatchFn ⽅法,它会调⽤ teardown ⽅法去移除这个 watcher 。所以本质上侦听属性也是基于 Watcher 实现的,它是⼀个 user watcher。

5.当数据发生变化时会触发set,然后触发dep.notify,然后触发User Watcher的update,此时会判断是否设置了sync,如果设置了sync那么就会立即同步执行watch.run,而不是加入微任务中。否则调用queueWatcher。watch.run先会执行this.get(),其次判断新旧值是否相等或者新值是否为对象或者是否是deep模式,接着判断是否为user watcher,是user watcher执行回调传入旧值与新值。