watcher

RenderWatcher

在我看来watcher对象对于Vue来说非常重要。之前总是想不明白Vue是怎么更新视图的,以及beforeUpdate、updated这两个钩子函数的由来。 秘密其实藏在$mount()中,仔细看$mount()函数中的mountcomponent函数中的代码片段,如下。

  updateComponent = function () {
    vm._update(vm._render(), hydrating);
  };
  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before: function before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate');
      }
    }
  }, true /* isRenderWatcher */);
  hydrating = false;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

在这段代码中有创建一个watcher,这个watcher实际上就是renderwatcher对象。Vue给它传递了一个函数updateComponent。而且watcher在new的过程中,已然把自己赋值给了Dep.target,在后续依赖收集时,该renderwatcher就会被收集到data的dep中。具体收集过程就是当watcher调用这个updateComponent函数时,先调用了render函数(render函数是用户手写模版后编译成的,也可能是用户用JSX手写的,render函数类似于下方代码)

  data () {
    return {
      title: 'renderwatcher'
    }
  },
  render (h) {
    return h('div', null, [this.title])
  }
1
2
3
4
5
6
7
8

当执行render函数时,renderwatcher就被收入了this.title的dep中。所以当title更新时,renderwatcher就会触发更新事件。

update钩子

在我们之前new watcher的过程中,还传递了一个before参数。before参数里实际上就是调用了beforeupdate的钩子函数,而调用它的时机,可以仔细看这个flushSchedulerQueue函数。

  /**
   * Flush both queues and run the watchers.
   */
  function flushSchedulerQueue () {
    currentFlushTimestamp = getNow();
    flushing = true;
    var watcher, id;

    // Sort queue before flush.
    // This ensures that:
    // 1. Components are updated from parent to child. (because parent is always
    //    created before the child)
    // 2. A component's user watchers are run before its render watcher (because
    //    user watchers are created before the render watcher)
    // 3. If a component is destroyed during a parent component's watcher run,
    //    its watchers can be skipped.
    queue.sort(function (a, b) { return a.id - b.id; });

    // do not cache length because more watchers might be pushed
    // as we run existing watchers
    for (index = 0; index < queue.length; index++) {
      watcher = queue[index];
      if (watcher.before) {
        watcher.before();
      }
      id = watcher.id;
      has[id] = null;
      watcher.run();
      // in dev build, check and stop circular updates.
      if (has[id] != null) {
        circular[id] = (circular[id] || 0) + 1;
        if (circular[id] > MAX_UPDATE_COUNT) {
          warn(
            'You may have an infinite update loop ' + (
              watcher.user
                ? ("in watcher with expression \"" + (watcher.expression) + "\"")
                : "in a component render function."
            ),
            watcher.vm
          );
          break
        }
      }
    }

    // keep copies of post queues before resetting state
    var activatedQueue = activatedChildren.slice();
    var updatedQueue = queue.slice();

    resetSchedulerState();

    // call component updated and activated hooks
    callActivatedHooks(activatedQueue);
    callUpdatedHooks(updatedQueue);

    // devtool hook
    /* istanbul ignore if */
    if (devtools && config.devtools) {
      devtools.emit('flush');
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

当watcher存在before函数时,先执行before函数即调用beforeUpdate钩子函数。待所有watcher更新完成后后,执行updated钩子函数。