四、Watcher类

Vue

首次渲染的时候数据的执行过程,数据改变的时候数据的执行过程.

# 三种类型

  • Computed Watcher
  • 用户 Watcher (侦听器)
  • 渲染 Watcher

渲染Watcher是在instance/lifecycle.js的mountComponent中创建的,创建了Watcher对象

// 创建了一个Watcher对象,并把updateComponent传入,所以其执行是在Watcher中调用的
// 第三个参数是noop空函数,第三个参数对用户和计算属性watcher是有用的,渲染watcher没有用
// 第四个参数是对象,里面定义了before函数,触发生命周期钩子函数
// 最后一个参数是true,标记这个watcher是渲染watcher
new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
}, true /* isRenderWatcher */)
1
2
3
4
5
6
7
8
9
10
11

进入watcher构造函数中了解其执行过程

export default class Watcher {
  ...
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    // 是否是渲染Watcher,有三种Watcher
    /**
     * watcher有三种
     * - 第一种是渲染Watcher,当前的Watcher
     * - 计算属性的Watcher
     * - 侦听器的Watcher
     */
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    // 判断是不是渲染watcher,如果是就把当前watcher记录到实例的_watcher中
    if (isRenderWatcher) {
      vm._watcher = this
    }
    // 记录到_watchers中,这里面的watcher不仅仅是渲染watcher,还有计算属性watcher和侦听器的watcher
    vm._watchers.push(this)
    // options
    // 这些选项都与渲染watcher无关,默认这些值都是false,非渲染的watcher会传入一些选项
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      // lazy 延迟执行,watcher要更新视图,那lazy就是是否延迟更新视图,当前是首次渲染要立即更新所以值是false,如果是计算属性的话是true,当数据发生变化之后才去更新视图
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      // 传入的before函数,会触发生命周期的beforeUpdate
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    // cb是构造函数的第三个参数,渲染参数是noop空函数,当用watcher的时候会传入一个回调,会对比新旧两个值
    this.cb = cb
    // watcher唯一标识
    this.id = ++uid // uid for batching
    // 标识当前watcher是否是活动的
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    // 记录与watcher相关的dep对象
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    // 第二个参数如果是function就直接把变量赋值给getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      // 如果是字符串的话要进一步处理,如何处理先不关注,如果是侦听器的话第二个参数传入的就是字符串
      // 例如:watch:{ 'person': function ...}
      // parsePath这个函数的作用是生成一个函数来获取属性的值,将这个函数返回的新函数记录到getter中
      // 此时的getter是一个函数,这个函数的作用是返回属性结果,获取属性 person 的值,触发了这个属性的getter,触发 getter 的时候会去收集依赖
      // 此时并没有执行而是记录下来了
      this.getter = parsePath(expOrFn)
      // 做了一些错误的处理,开发环境getter不存在就会有警告
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    // 给this.value赋值,先判断this.lazy,如果当前不要求延迟执行就立即执行get方法
    // this.lazy如果是计算属性的watcher是true,延迟执行,其他watcher是false立即执行
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    // 调用pushTarget,将当前的Watcher对象放入栈中
    // 每个组件对应一个Watcher,Watcher会去渲染视图,如果组件有嵌套的话会先渲染内部的组件,所以要将父组件的Watcher先保存起来,这是这个pushTarget的作用
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // 最关键的一句话
      // 这句话调用了getter,getter存储的是传入的第二个参数,且是函数,首次渲染是updateComponent,所以在get方法的内部调用了updateComponent,并且改变了函数内部的this指向到Vue实例vm,并且传入了vm
      // 这里将虚拟DOM转化成了真实DOM并更新到页面中
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
      // 执行完毕之后会进行清理工作
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      // 将watcher从栈中弹出
      popTarget()
      // 会把当前watcher会从dep.subs数组中移除,把watcher里面的dep也移除
      this.cleanupDeps()
    }
    return value
  }
  ...
}
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116

下面看当数据更新的时候watcher是如何工作的?

# notify

当数据更新的时候,会调用dep.notify方法,进入observer/dep.js

// 发布通知
  notify () {
    // stabilize the subscriber list first
    // subs数组是watcher对象数组,这里要进行克隆,下面要进行排序,按照id从小到大进行排序
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      // 按照watcher的创建顺序进行排序,保证执行watcher的顺序是正确的
      subs.sort((a, b) => a.id - b.id)
    }
    // 循环subs数组,
    for (let i = 0, l = subs.length; i < l; i++) {
      // 会调用watcher中的update方法
      subs[i].update()
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

进入update方法,返回到了watcher类

# update

update () {
    /* istanbul ignore else */
    // 渲染watcher中,lazy和sync是false,会执行queueWatcher
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
}
1
2
3
4
5
6
7
8
9
10
11

渲染watcher要执行的是queueWatcher

# queueWatcher

// 接收一个参数,watcher对象
export function queueWatcher (watcher: Watcher) {
  // 获取watcher对象的id
  const id = watcher.id
  // 判断has[id],has是个对象,如果是null说明watcher没有被处理,防止重复处理
  if (has[id] == null) {
    // 标识已经处理过了
    has[id] = true
    // 这段代码的功能是将watcher对象添加到队列中

    // flushing是正在刷新的意思,如果其为true说明队列正在被处理,队列queue就是watcher对象,是watcher对象正在被处理
    // 判断队列没有被处理的时候,将watcher直接放到队列的末尾中
    if (!flushing) {
      queue.push(watcher)
    } else {
      // 如果这个队列正在被处理
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      // 获取队列的长度
      let i = queue.length - 1
      // index是处理到了队列的第几个元素,i > index 表示该队列还没有被处理完,就获取队列中的watcher对象,判断id是否大于当前正在处理的id,如果大于就将i--
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      // 这样就把处理的watcher放到了合适的位置中
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    // 判断当前队列是否被执行,如果没有被执行就进入
    if (!waiting) {
      waiting = true
      // 如果是开发环境的话,就直接执行flushSchedulerQueue函数,如果是生产环境,将flushSchedulerQueue传给nextTick
      if (process.env.NODE_ENV !== 'production' && !config.async) {
        // 这个函数会遍历所有的watcher,并调用watcher的run方法
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)
    }
  }
}
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

下面看一下flushSchedulerQueue是怎么执行的

# flushSchedulerQueue

// 标记true,表示正在处理队列
  flushing = true
  let 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)
  // 组建的用户watcher要在其对应的渲染watcher之前运行,因为用户watcher是在渲染watcher之前创建的
  // 3. If a component is destroyed during a parent component's watcher run,
  //    its watchers can be skipped.
  // 如果一个组件在父组件执行之前被销毁了,那应该跳过

  // 先对queue进行id的从小到大排序,即watcher的创建顺序
  queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  // 遍历queue中的所有watcher,不要缓存lenth,因为watcher在执行的过程中在队列中可能会加入新的watcher
  for (index = 0; index < queue.length; index++) {
    // 获取watcher,判断其是否有before函数,有before函数是在渲染watcher中才有的,触发钩子函数beforeUpdate
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    // 获取watcher的id,将其处理为null,下次调用的时候还能正常被运行
    id = watcher.id
    has[id] = null
    // 调用watcher的run方法
    watcher.run()
    ...
  }
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

点击watcher的run方法,进入

# run

run () {
    // 标记当前watcher是否是存活的状态,默认为true,可处理
    if (this.active) {
      // 调用其get方法,如果是渲染watcher会调用getter,执行updateComponent方法渲染DOM更新页面
      // 之后用value记录返回结果,如果是渲染watcher没有返回结果,value是undefined,渲染函数的cb是noop空函数.
      // 如果是用户watcher,继续执行,获取旧值记录新值,调用cb回调函数,侦听器的function就是回调函数,
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          // 如果是用户watcher,添加异常处理
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          // 如果是其他watcher,直接调用
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
}
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

# 总结

  • 当数据变化之后调用dep.notify方法去通知watcher
  • 先把watcher放到一个队列中,遍历队列,调用队列中元素的run方法
  • run方法中最终调用了updateComponent函数
更新时间: 2022-01-13 21:13