派发更新

10/18/2021 vue

上一小节分析了响应式数据依赖收集的过程,这一节接着分析当数据发生改变通知更新的过程,首先看下defineReactiveSetter部分,该方法定义在src/core/observer/index.js

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
		
      // ......
			
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}
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

当响应式对象的属性值发生变化时会触发对应的响应式Setter方法,在方法内部会先调用预先在该属性定义的Getter方法获取属性值value,并将修改的值newValvalue做一个比对,如果valuenewVal为相同的值或者valuenewVal都为NaN,则不会触发更新,接着会判断是否存在自定义customSetter,如果存在则调用该方法,customSetter后续小节会讲解到,如果只存在预先定义的Getter而没有Setter,相当于任何修改操作都无效,该属性为只读属性,始终返回的都是固定值,所以直接return返回,如果存在预先定义的Setter则调用,否则将修改的newVal赋值给val,接着是根据修改的值newVal生成新的childOb,这里其实用到了闭包的思想,就不细说了(只可意会不可言传了),最后调用响应式对象属性的dep.notify方法实现派发更新

继续回到src/core/observer/dep.js文件中,查看Dep类中对notify方法的实现

export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  // ......

  notify () {
    // stabilize the subscriber list first
    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)
    }
    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
17
18
19
20
21
22
23
24
25
26

notify方法中,会循环遍历响应式对象属性对应的dep.subs中订阅的watcher实例,并调用每个watcherupdate方法

继续回到Watcher中,可在src/core/observer/watcher.js文件中查看对update部分的定义

export default class Watcher {
	
  // ......
	
  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update () {
    /* istanbul ignore else */
    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
12
13
14
15
16
17
18
19
20
21

watcher实例的update方法中,做了两次判断,首先判断this.lazy是否为true,当只有watcher实例为computed watcher时,该属性才为true,后面到计算属性小节的时候再做分析,接着判断当前watcher实例是否为同步watcher,如果为同步watcher则直接执行run方法,到监听器小节时再做分析,接着会调用queueWatcher方法,将watcher实例作为参数传递

src/core/observer/scheduler.js中可以查看对queueWatcher方法的定义


const queue: Array<Watcher> = []
const activatedChildren: Array<Component> = []
let has: { [key: number]: ?true } = {}
let circular: { [key: number]: number } = {}
let waiting = false
let flushing = false
let index = 0

// ......

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    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
      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
      }
      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

queueWatcher中,首先做了一层has[id] == null的判断,保证没有重复的watcher,接着对flushing做了层判断,为false的情况将watcher添加到数组queue中,else的部分后面再分析,接着就是判断waiting,保证只调用了一次nextTick方法,异步的调用flushSchedulerQueue方法,对于nextTick方法的分析会专门写一个小节

flushSchedulerQueuequeueWatcher方法定义在同一文件中

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
  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 (process.env.NODE_ENV !== 'production' && has[id] != null) {

      // ......
	
    }
  }

  // keep copies of post queues before resetting state
  const activatedQueue = activatedChildren.slice()
  const 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

该方法首先对数组queue中的watcher根据其id做了从小到大的排序,源码中主要做了三点解释:

  1. 组件的更新是从父到子(因为父组件总是先于子组件创建)
  2. 组件的用户观察程序(监听器watcher)在其渲染观察程序(渲染watcher)之前运行(因为监听器watcher会优先于渲染watcher创建)
  3. 如果某个组件在父组件的观察程序运行期间被销毁,它的观察者可以跳过

接着就是循环遍历queue中的每一项watcher,需要注意的点是每次循环都会重新计算queue.length,如果存在watcher.before方法,则调用,然后调用每一个watcher实例的run方法,接着调用resetSchedulerState方法,重置相关状态,和对activatedupdate钩子函数的调用

接着继续分析watcher.run()的逻辑,定义在src/core/observer/watcher.js中。

class Watcher {
	
  // ......
	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
	}

  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
        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)
        }
      }
    }
  }
}
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

run方法内部先对this.get方法进行了调用,如果当前watcher为渲染watcher,则会重新生成新的vnode,利用DOM Diff算法,做局部更新渲染,如果为监听器watcher,则会根据返回的新值value和旧值this.value做比对,如果不相等,或者新值value为一个不为null的对象,或者watcher为一个深度监听器,则将新值value赋值给this.value,然后执行监听器回调this.cb

重置状态

function resetSchedulerState () {
  index = queue.length = activatedChildren.length = 0
  has = {}
  if (process.env.NODE_ENV !== 'production') {
    circular = {}
  }
  waiting = flushing = false
}
1
2
3
4
5
6
7
8

resetSchedulerState方法中对index、queue、activatedChildren、has、circular、waiting、flushing都进行了重置

回到queueWatcher方法中,继续对else逻辑进行分析

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    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
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    if (!waiting) {
      waiting = true
      // ......
      nextTick(flushSchedulerQueue)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

flushingtrue时会进入else分支逻辑,意味着flushSchedulerQueue在执行中有新的watcher被添加进来,源码中使用while循环根据watcher.id在队列queue中从后往前进行比对(在flushSchedulerQueue方法中对queue中的watcher进行了排序,所以肯定是有序的watcher队列),然后找到合适的位置插入到queue队列中

最后更新时间: 12/4/2022, 1:44:46 PM