# Vue异步更新机制

  • 核心是利用浏览器的异步任务队列实现的

  • 当响应式数据更新后,会触发 dep.notify 通知所有的 watcher 执行 update 方法

notify () {
  // stabilize the subscriber list first
  // 获取所有 watcher 
  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
    subs.sort((a, b) => a.id - b.id)
  }

  // 遍历执行 update
  for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • watcher 类的 update 方法
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
 /* istanbul ignore else */
 if (this.lazy) {
   // computed 缓存复用逻辑 lazy 
   this.dirty = true
 } else if (this.sync) {
   // 同步执行 watch 选项参数传 sync 
   // 不塞入异步更新队列
   this.run()
 } else {
   // 正常更新走当前分支
   queueWatcher(this)
 }
}

/**
* Push a watcher into the watcher queue.
* Jobs with duplicate IDs will be skipped unless it's
* pushed when the queue is being flushed.
*/
export function queueWatcher (watcher: Watcher) {
 const id = watcher.id
 // 根据 watcher id 判断是否在队列中,不重复入队
 if (has[id] == null) {
   has[id] = true
   // 全局 queue 队列未处于刷新状态 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.
     // 全局 queue 处于刷新状态
     // 在单调递增序列找到当前id 的位置并进行插入操作
     let i = queue.length - 1
     while (i > index && queue[i].id > watcher.id) {
       i--
     }
     queue.splice(i + 1, 0, watcher)
   }
   // queue the flush
   if (!waiting) {
     waiting = true

     // 同步执行逻辑
     if (process.env.NODE_ENV !== 'production' && !config.async) {
       flushSchedulerQueue()
       return
     }
     // 将回调函数 flushSchedulerQueue 放入 callbacks 数组
     nextTick(flushSchedulerQueue)
   }
 }
}

/**
* Flush both queues and run the watchers.
*/
// 更新 flushing 为 true 表示正在刷新队列 在此期间加入的 watcher 必须有序插入队列 保证单调递增
// 按照队列的 watcher.id 从小到大排序 保证先创建的先执行
// 遍历 watcher 队列 按序执行 watcher.before watcher.run 最后清除缓存 watcher
function flushSchedulerQueue () {
 currentFlushTimestamp = getNow()
 // 标识正在刷新队列
 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)
 // 3. If a component is destroyed during a parent component's watcher run,
 //    its watchers can be skipped.
 queue.sort((a, b) => a.id - b.id)

 // do not cache length because more watchers might be pushed
 // as we run existing watchers
 // 未缓存长度是因为可能在执行 watcher 时加入 watcher
 for (index = 0; index < queue.length; index++) {
   watcher = queue[index]
   if (watcher.before) {
     watcher.before()
   }
   id = watcher.id
   // 清楚缓存的 watcher 
   has[id] = null
   // 触发更新函数 如 updateComponent 或 执行用户的 watch 回调
   watcher.run()
   // in dev build, check and stop circular updates.
   if (process.env.NODE_ENV !== 'production' && 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
 const activatedQueue = activatedChildren.slice()
 const updatedQueue = queue.slice()

 // waiting = flushing = false 标识刷新队列结束 可以向浏览器的任务队列加入下一个 flushCallbacks
 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
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
117
118
119
120
121
122
123
124
125
126
127
  • watcher.run
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
// 执行实例化 watcher 传递的第二个参数 
// 更新旧值为新值
// 执行实例化 watcher 时传递第三个参数 用户传递的 watcher 回调
run () {
 if (this.active) {
   // 获取值 
   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
     // 如果是项目传入的 watcher 则执行实例化传递的回调函数
     if (this.user) {
       const info = `callback for watcher "${this.expression}"`
       invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
     } else {
       this.cb.call(this.vm, value, oldValue)
     }
   }
 }
}

/**
* Evaluate the getter, and re-collect dependencies.
*/
// 执行 this.getter 重新收集依赖
// 重新收集依赖是因为触发更新  setter 中只做了响应观测 但没有依赖收集的操作 
// 所以 在更新页面时 会重新执行一次 render 函数 执行期间会触发读取操作 这时进行依赖收集
get () {
 pushTarget(this)
 let value
 const vm = this.vm
 try {
   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)
   }
   popTarget()
   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