• 7.15 watch
    • 7.15.1 依赖收集
    • 7.15.2 派发更新

    7.15 watch

    到这里,关于响应式系统的分析大部分内容已经分析完毕,我们上一节还遗留着一个问题,Vue对用户手动添加的watch如何进行数据拦截。我们先看看两种基本的使用形式。

    1. // watch选项
    2. var vm = new Vue({
    3. el: '#app',
    4. data() {
    5. return {
    6. num: 12
    7. }
    8. },
    9. watch: {
    10. num() {}
    11. }
    12. })
    13. vm.num = 111
    14. // $watch api方式
    15. vm.$watch('num', function() {}, {
    16. deep: ,
    17. immediate: ,
    18. })

    7.15.1 依赖收集

    我们以watch选项的方式来分析watch的细节,同样从初始化说起,初始化数据会执行initWatch,initWatch的核心是createWatcher

    1. function initWatch (vm, watch) {
    2. for (var key in watch) {
    3. var handler = watch[key];
    4. // handler可以是数组的形式,执行多个回调
    5. if (Array.isArray(handler)) {
    6. for (var i = 0; i < handler.length; i++) {
    7. createWatcher(vm, key, handler[i]);
    8. }
    9. } else {
    10. createWatcher(vm, key, handler);
    11. }
    12. }
    13. }
    14. function createWatcher (vm,expOrFn,handler,options) {
    15. // 针对watch是对象的形式,此时回调回选项中的handler
    16. if (isPlainObject(handler)) {
    17. options = handler;
    18. handler = handler.handler;
    19. }
    20. if (typeof handler === 'string') {
    21. handler = vm[handler];
    22. }
    23. return vm.$watch(expOrFn, handler, options)
    24. }

    无论是选项的形式,还是api的形式,最终都会调用实例的$watch方法,其中expOrFn是监听的字符串,handler是监听的回调函数,options是相关配置。我们重点看看$watch的实现。

    1. Vue.prototype.$watch = function (expOrFn,cb,options) {
    2. var vm = this;
    3. if (isPlainObject(cb)) {
    4. return createWatcher(vm, expOrFn, cb, options)
    5. }
    6. options = options || {};
    7. options.user = true;
    8. var watcher = new Watcher(vm, expOrFn, cb, options);
    9. // 当watch有immediate选项时,立即执行cb方法,即不需要等待属性变化,立刻执行回调。
    10. if (options.immediate) {
    11. try {
    12. cb.call(vm, watcher.value);
    13. } catch (error) {
    14. handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
    15. }
    16. }
    17. return function unwatchFn () {
    18. watcher.teardown();
    19. }
    20. };
    21. }

    $watch的核心是创建一个user watcher,options.user是当前用户定义watcher的标志。如果有immediate属性,则立即执行回调函数。而实例化watcher时会执行一次getter求值,这时,user watcher会作为依赖被数据所收集。这个过程可以参考data的分析。

    1. var Watcher = function Watcher() {
    2. ···
    3. this.value = this.lazy
    4. ? undefined
    5. : this.get();
    6. }
    7. Watcher.prototype.get = function get() {
    8. ···
    9. try {
    10. // getter回调函数,触发依赖收集
    11. value = this.getter.call(vm, vm);
    12. }
    13. }

    7.15.2 派发更新

    watch派发更新的过程很好理解,数据发生改变时,setter拦截对依赖进行更新,而此前user watcher已经被当成依赖收集了。这个时候依赖的更新就是回调函数的执行。