当前位置:   article > 正文

VUE源码学习第十一篇--响应式原理(订阅)_watcher only accepts simple dot-delimited paths. f

watcher only accepts simple dot-delimited paths. for full control, use a fun

一、总述

     前一章节,通过对prop,data所定义的属性建立观察类和发布器,但此时的发布类Dep中sub空空如也,如何实现watcher的注册,并在属性发生变化时,实现更新。本章节将继续介绍订阅Watcher。

在介绍前,我们先思考下,用户定义的prop,data在哪些方法或者表达式是需要实现响应式变化的?

首先用户自定义的watch,computed是需要的,另外我们的视图也会用到大量的属性表达式(如前面的实例{{item.id}}),也是需要的。method中虽然用到了这些数据,但是都是及时调用,所以不需要的。

事实上,源码中也是在这三个场景创建watcher,如果阅读过我前面的博客,在initWatcher,initComputer(第五篇),以及mount(第六篇)简单的介绍过,做过一些知识的铺垫。

这三种watcher分别为user watcher,compute watcher,以及render watcher,本篇就结合前面的知识,详细介绍这几种watcher实现原理。

二、user watcher

我们以下面的的watch为运行实例:

  1. data:{
  2. msg:'this is msg',
  3. items:[
  4. {id:1},
  5. {id:2},
  6. {id:3}
  7. ]
  8. }
  9. watch: {
  10. msg: function (val, oldVal) {
  11. console.log('new: %s, old: %s', val, oldVal)
  12. },
  13. ....
  14. }

回顾下第五篇initWatcher的创建watcher过程,如下:

  1. Vue.prototype.$watch = function (
  2. expOrFn: string | Function,
  3. cb: any,
  4. options?: Object
  5. ): Function {
  6. ...
  7. //创建watcher对象,进行监听
  8. const watcher = new Watcher(vm, expOrFn, cb, options)
  9. ...
  10. }

 

其中主要入参,vm,即vue对象;expOrFn,待观察的表达式,即本例中属性msg对象;cb,回调函数,即本例中msg属性的对应的方法。watcher的定义位于src/core/observer/watcher.js。

  1. export default class Watcher {
  2. vm: Component;
  3. expression: string;
  4. cb: Function;
  5. id: number;
  6. deep: boolean;
  7. user: boolean;
  8. computed: boolean;
  9. sync: boolean;
  10. dirty: boolean;
  11. active: boolean;
  12. dep: Dep;
  13. deps: Array<Dep>;
  14. newDeps: Array<Dep>;
  15. depIds: SimpleSet;
  16. newDepIds: SimpleSet;
  17. before: ?Function;
  18. getter: Function;
  19. value: any;
  20. constructor (
  21. vm: Component,//组件对象
  22. expOrFn: string | Function,//待观察的表达式
  23. cb: Function,//回调函数,更新的时候调用
  24. options?: ?Object,
  25. isRenderWatcher?: boolean
  26. ) {
  27. //1、初始化变量
  28. this.vm = vm
  29. if (isRenderWatcher) {
  30. vm._watcher = this
  31. }
  32. vm._watchers.push(this)
  33. // options
  34. if (options) {
  35. this.deep = !!options.deep
  36. this.user = !!options.user
  37. this.computed = !!options.computed
  38. this.sync = !!options.sync
  39. this.before = options.before
  40. } else {
  41. this.deep = this.user = this.computed = this.sync = false
  42. }
  43. this.cb = cb
  44. this.id = ++uid // uid for batching
  45. this.active = true
  46. this.dirty = this.computed // for computed watchers
  47. this.deps = []
  48. this.newDeps = []
  49. this.depIds = new Set()
  50. this.newDepIds = new Set()
  51. this.expression = process.env.NODE_ENV !== 'production'
  52. ? expOrFn.toString()
  53. : ''
  54. // parse expression for getter
  55. //2、解析表达式,获取getter方法
  56. if (typeof expOrFn === 'function') {
  57. this.getter = expOrFn
  58. } else {
  59. this.getter = parsePath(expOrFn)
  60. if (!this.getter) {
  61. this.getter = function () {}
  62. process.env.NODE_ENV !== 'production' && warn(
  63. `Failed watching path: "${expOrFn}" ` +
  64. 'Watcher only accepts simple dot-delimited paths. ' +
  65. 'For full control, use a function instead.',
  66. vm
  67. )
  68. }
  69. }
  70. //3、依赖收集
  71. if (this.computed) {
  72. //对于计算属性watcher,此时没有立即进行依赖收集,在执行render函数时收集
  73. this.value = undefined
  74. this.dep = new Dep()
  75. } else {
  76. //对于普通watcher,调用get方法,进行依赖收集
  77. this.value = this.get()
  78. }
  79. }
  80. ...
  81. }

1、构造方法

我们先看下其构造方法。主要过程包括:

(1)、初始化变量。主要变量如下:

deep,表示是否要进行深度监听,即属性嵌套的变量进行监听。

computed,表示是否是计算属性。

sync,表示更新时,是否需要同步执行。

deps,newDeps,该watcher对应的维护的发布器数组。

(2)、解析表达式,获取getter方法,如果expOrFn是function类型,则直接设置为getter方法,否则调用parsePath方法返回属性对象作为getter方法,本例中则使用后一种,执行结果为vm[msg]。

  1. export function parsePath (path: string): any {
  2. ...
  3. return function (obj) {
  4. for (let i = 0; i < segments.length; i++) {
  5. if (!obj) return
  6. obj = obj[segments[i]]
  7. }
  8. return obj
  9. }
  10. }

(3)、依赖收集,这部分处理的很巧妙,对于非计算属性,直接调用了get方法(对于计算属性的watcher我们等会在表)。继续看下get方法。

2、get方法

  1. get () {
  2. //1、将当前的watcher压栈
  3. pushTarget(this)
  4. let value
  5. const vm = this.vm
  6. try {
  7. //2、核心代码,依赖收集
  8. value = this.getter.call(vm, vm)
  9. } catch (e) {
  10. if (this.user) {
  11. handleError(e, vm, `getter for watcher "${this.expression}"`)
  12. } else {
  13. throw e
  14. }
  15. } finally {
  16. // "touch" every property so they are all tracked as
  17. // dependencies for deep watching
  18. //3、后置处理
  19. if (this.deep) {
  20. traverse(value)
  21. }
  22. popTarget()
  23. this.cleanupDeps()
  24. }
  25. return value
  26. }

(1)将自身的watcher对象压入栈,设置全局的变量Dep.target为当前的watcher对象。

  1. export function pushTarget (_target: ?Watcher) {
  2. if (Dep.target) targetStack.push(Dep.target)
  3. Dep.target = _target
  4. }

(2)执行getter方法,触发该属性的get劫持,我们回顾前一章节定义的劫持方法。

  1. Object.defineProperty(obj, key, {
  2. enumerable: true,
  3. configurable: true,
  4. get: function reactiveGetter () {
  5. const value = getter ? getter.call(obj) : val
  6. //依赖收集
  7. if (Dep.target) {
  8. dep.depend()
  9. if (childOb) {
  10. childOb.dep.depend()
  11. if (Array.isArray(value)) {
  12. dependArray(value)
  13. }
  14. }
  15. }
  16. return value
  17. }
  18. ...

全局变量Dep.target表示的就是当前的watcher对象,非空,继续调用Dep类的depend方法

  1. depend () {
  2. if (Dep.target) {
  3. Dep.target.addDep(this)
  4. }
  5. }

最终又回调了watcher中的addDep方法,

  1. addDep (dep: Dep) {
  2. const id = dep.id
  3. if (!this.newDepIds.has(id)) {
  4. //加入到watcher的dep数组
  5. this.newDepIds.add(id)
  6. this.newDeps.push(dep)
  7. if (!this.depIds.has(id)) {
  8. //加入到dep的sub数组
  9. dep.addSub(this)
  10. }
  11. }
  12. }

该方法,首先将dep保存到watcher的newDeps数组中,然后调用Dep的addSub,将watcher对象加入到sub数组中。

  1. addSub (sub: Watcher) {
  2. this.subs.push(sub)
  3. }

整个调用过程比较绕,这样做的目的,就是为了建立dep和watcher间的双向链表。

(3)后置处理,包括deep处理,清除当前的watcher对象等。

整个过程完成后,msg属性的Dep对象的sub中就添加了watcher对象。msg的依赖模型如下:

三、compute watcher

以下面的计算属性computeMsg为运行实例:

  1. <div id="app">
  2. {{computeMsg}}
  3. ...
  4. </div>
  5. var vm = new Vue({
  6. data:{
  7. msg:'this is msg',
  8. items:[
  9. {id:1},
  10. {id:2},
  11. {id:3}
  12. ]
  13. }
  14. watch: {
  15. msg: function (val, oldVal) {
  16. console.log('new: %s, old: %s', val, oldVal)
  17. },
  18. computed:{
  19. computeMsg:function(){
  20. return "this is computed msg:"+this.msg
  21. }
  22. }
  23. ....
  24. }

   该实例中,创建computeMsg计算属性,并在模板(template)中使用。computeMsg计算属性表达式中调用了msg属性。

我们来回顾下第五章节的initComputed方法。

  1. function initComputed (vm: Component, computed: Object) {
  2. // $flow-disable-line
  3. const watchers = vm._computedWatchers = Object.create(null)
  4. // computed properties are just getters during SSR
  5. const isSSR = isServerRendering()
  6. //1、循环计算属性
  7. for (const key in computed) {
  8. const userDef = computed[key]
  9. const getter = typeof userDef === 'function' ? userDef : userDef.get
  10. ...
  11. if (!isSSR) {
  12. // create internal watcher for the computed property.
  13. //2、为每个属性创建watcher
  14. watchers[key] = new Watcher(
  15. vm,
  16. getter || noop,
  17. noop,
  18. computedWatcherOptions
  19. )
  20. }
  21. ...
  22. //3、劫持数据变化,创建监听方法
  23. defineComputed(vm, key, userDef)
  24. }
  25. }
  26. export function defineComputed (
  27. target: any,
  28. key: string,
  29. userDef: Object | Function
  30. ) {
  31. const shouldCache = !isServerRendering()
  32. if (typeof userDef === 'function') {
  33. sharedPropertyDefinition.get = shouldCache
  34. ? createComputedGetter(key)
  35. : userDef
  36. sharedPropertyDefinition.set = noop
  37. } else {
  38. ....
  39. }
  40. //4、并对计算属性的getter和setter进行劫持
  41. Object.defineProperty(target, key, sharedPropertyDefinition)
  42. }
  43. }
  44. //getter方法劫持
  45. function createComputedGetter (key) {
  46. return function computedGetter () {
  47. const watcher = this._computedWatchers && this._computedWatchers[key]
  48. if (watcher) {
  49. //依赖收集,将订阅类添加到
  50. watcher.depend()
  51. //返回值
  52. return watcher.evaluate()
  53. }
  54. }
  55. }

在该方法中,循环所定义的计算属性,为每个计算属性创建watcher,并设置getter方法,监听数据变化(计算属性setter方法用的较少)。watcher的主要入参:vm,即vue对象;getter,计算属性的表达式,即本例中computeMsg对应的表达式。继续看下watcher的构造方法。

1、构造函数

  1. constructor (
  2. vm: Component,//组件对象
  3. expOrFn: string | Function,//待观察的表达式
  4. cb: Function,//回调函数
  5. options?: ?Object,
  6. isRenderWatcher?: boolean
  7. ) {
  8. ...
  9. // parse expression for getter
  10. //1、对于计算属性,表达式即为getter
  11. if (typeof expOrFn === 'function') {
  12. this.getter = expOrFn
  13. } else {
  14. this.getter = parsePath(expOrFn)
  15. if (!this.getter) {
  16. this.getter = function () {}
  17. process.env.NODE_ENV !== 'production' && warn(
  18. `Failed watching path: "${expOrFn}" ` +
  19. 'Watcher only accepts simple dot-delimited paths. ' +
  20. 'For full control, use a function instead.',
  21. vm
  22. )
  23. }
  24. }
  25. if (this.computed) {
  26. //2、对于计算属性,创建了dep,此时没有立即进行依赖收集,
  27. this.value = undefined
  28. this.dep = new Dep()
  29. } else {
  30. this.value = this.get()
  31. }
  32. }

watcher的入参,vm表示vue对象,getter为待观察的表达式,即计算属性函数。

与user watcher相比,有两个不同的地方。

(1)、由于expOrFn 是计算属性的表达式,类型是function,所以走一个分支,将getter设置为计算属性表达式。

(2)、并没有调用get方法进行依赖收集,只是创建了dep对象,该对象保存依赖该计算属性的watcher。

计算属性的依赖关系不同于普通的属性,它即依赖于其表达式包含的普通属性,比如说本例中的属性msg,同时又被其他调用者依赖,如本例中调用computeMsg的模板表达式({{computeMsg}})。本例的依赖模型如下:

那么问题来了,既然这些依赖关系没有在定义的时候进行收集,那是什么时候做的呢?答案就是在模板调用的时候。

计算属性的设计初衷是简化模板的表达式,避免太多的逻辑导致模板的复杂。所以,只有在模板调用的情况下才会触发依赖,如果只定义不调用,进行依赖就会造成浪费。

下面我们来介绍与模板render相关的watcher(render watcher),并看下如何完成红色标注的依赖收集。

四、render watcher

我们先回顾下第六章节介绍挂载时的mountComponent方法。

  1. export function mountComponent (
  2. vm: Component,
  3. el: ?Element,
  4. hydrating?: boolean
  5. ): Component {
  6. ....
  7. //2、定义updateComponent,vm._render将render表达式转化为vnode,vm._update将vnode渲染成实际的dom节点
  8. updateComponent = () => {
  9. vm._update(vm._render(), hydrating)
  10. }
  11. //3、首次渲染,并监听数据变化,并实现dom的更新
  12. new Watcher(vm, updateComponent, noop, {
  13. before () {
  14. if (vm._isMounted) {
  15. callHook(vm, 'beforeUpdate')
  16. }
  17. }
  18. }, true /* isRenderWatcher */)
  19. ....
  20. }

当时我们说这个watcher有两个作用,1、实现首次dom渲染,并完成依赖收集。2、监听模板所包含表达式的变化,实现update。

该watcher是数据"render"模板的"桥梁",称之为render watcher,其核心部分体现在入参expression(第二个参数,即updateComponent)。我们看下该watcher的初始化的主要过程:

  1. constructor (
  2. vm: Component,//组件对象
  3. expOrFn : string | Function,//待观察的表达式
  4. cb: Function,//回调函数
  5. options?: ?Object,
  6. isRenderWatcher?: boolean
  7. ) {
  8. ....
  9. if (typeof expOrFn === 'function') {//1、设置getter方法为表达式
  10. this.getter = expOrFn
  11. } else {
  12. .....
  13. }
  14. if (this.computed) {
  15. ....
  16. } else {
  17. //2、执行get方法,进行依赖收集
  18. this.value = this.get()
  19. }
  20. }

1、expOrFn为function,所以设置getter方法为表达式,即为updateComponent方法。

2、由于不是计算属性,与user watcher一样,执行get方法,实际就是执行updateComponent方法。

  1. updateComponent = () => {
  2. vm._update(vm._render(), hydrating)
  3. }

该方法包含vm._render(),vm._update两个执行阶段,我们在第六章重点分析过,我们不再做详细介绍。今天我们重点回答前面提出的问题,如何实现依赖关系的收集。

以上面的计算属性为例,在模板中,我们使用了"{{computeMsg}}"。

  1. <div id="app">
  2. {{computeMsg}}
  3. ...
  4. </div>

该模板经过编译(参见前面的编译部分)后,该部分的render表达式"_s(computeMsg)",在执行vm._render时,就会触发computeMsg所设置的getter方法。

  1. function createComputedGetter (key) {
  2. return function computedGetter () {
  3. const watcher = this._computedWatchers && this._computedWatchers[key]
  4. if (watcher) {
  5. //1、依赖收集,将订阅类添加到dep中
  6. watcher.depend()
  7. //2、返回值
  8. return watcher.evaluate()
  9. }
  10. }
  11. }

1、调用watcher.depend,将当前的render watcher对象添加到compteMsg的dep中,完成依赖关系的收集。

  1. depend () {
  2. if (this.dep && Dep.target) {
  3. this.dep.depend()
  4. }
  5. }

注意这里的Dep.target指的是render watcher。依赖模型如下:

2、调用watcher.evaluate()。

  1. evaluate () {
  2. //如果有更新,则重新计算,否则返回缓存值
  3. if (this.dirty) {
  4. this.value = this.get()
  5. this.dirty = false
  6. }
  7. return this.value
  8. }

这是this指的是compute watcher。其get方法就是属性表达式。

  1. function(){
  2. return "this is computed msg:"+this.msg
  3. }

由于表达式中包含了msg属性,在执行过程中,又触发msg的get监听方法,将该compute watcher 添加到msg的dep中,完成被依赖关系的收集。最终的依赖模型如下:

至此,computeMsg的依赖和被依赖关系收集完成。大家也可以尝试分析下模板中调用普通属性的依赖关系收集过程。

五、派发更新

目前msg属性的dep发布器中收集了两个依赖的watcher对象,当我们重新设置msg值,会发生什呢?

当重新设置msg值,就会触发set方法。

  1. Object.defineProperty(obj, key, {
  2. ...
  3. set: function reactiveSetter (newVal) {
  4. const value = getter ? getter.call(obj) : val
  5. /* eslint-disable no-self-compare */
  6. if (newVal === value || (newVal !== newVal && value !== value)) {
  7. return
  8. }
  9. /* eslint-enable no-self-compare */
  10. if (process.env.NODE_ENV !== 'production' && customSetter) {
  11. customSetter()
  12. }
  13. if (setter) {
  14. setter.call(obj, newVal)
  15. } else {
  16. val = newVal
  17. }
  18. //核心部分,通知更新
  19. childOb = !shallow && observe(newVal)
  20. dep.notify()
  21. }
  22. })

调用其发布器的notify方法。

  1. //通知相关的watcher类更新
  2. notify () {
  3. // stabilize the subscriber list first
  4. const subs = this.subs.slice()
  5. for (let i = 0, l = subs.length; i < l; i++) {
  6. subs[i].update()
  7. }
  8. }

循环属性关联的watcher类,由前面可知,此时dep的sub集合中有user watcher和computed watcher两个对象。分别调用其update方法。

  1. update () {
  2. /* istanbul ignore else */
  3. if (this.computed) {//计算属性watcher处理
  4. // A computed property watcher has two modes: lazy and activated.
  5. // It initializes as lazy by default, and only becomes activated when
  6. // it is depended on by at least one subscriber, which is typically
  7. // another computed property or a component's render function.
  8. //当没有订阅计算属性时,不需要计算,仅仅设置dirty为true,表示下次重新计算
  9. if (this.dep.subs.length === 0) {
  10. // In lazy mode, we don't want to perform computations until necessary,
  11. // so we simply mark the watcher as dirty. The actual computation is
  12. // performed just-in-time in this.evaluate() when the computed property
  13. // is accessed.
  14. this.dirty = true
  15. } else {
  16. // In activated mode, we want to proactively perform the computation
  17. // but only notify our subscribers when the value has indeed changed.
  18. this.getAndInvoke(() => {
  19. this.dep.notify()
  20. })
  21. }
  22. } else if (this.sync) {//同步处理,立即执行
  23. this.run()
  24. } else {//异步处理,进入堆栈
  25. queueWatcher(this)
  26. }
  27. }

update分为三个分支处理,我们分别对三种情况进行分析。

(1)、计算属性处理。

(2)、同步处理,立即执行。

(3)、异步处理,加入到堆栈中。

1、计算属性

对于计算属性的watcher对象,判断是否有订阅过该计算属性,如果没有(即该计算属性的dep的sub集合是否为空),则设置dirty为true,下次将重新计算。

如本例中,computeMsg是被render watcher依赖,所以会进入else分支,执行getAndInvoke方法。

  1. this.getAndInvoke(() => {
  2. this.dep.notify()
  3. })
  4. getAndInvoke (cb: Function) {
  5. //获取最新的值
  6. const value = this.get()
  7. if (
  8. //
  9. value !== this.value ||//值发生变化
  10. // Deep watchers and watchers on Object/Arrays should fire even
  11. // when the value is the same, because the value may
  12. // have mutated.
  13. isObject(value) ||//object对象
  14. this.deep//深度watcher
  15. ) {
  16. // set new value
  17. //更新值
  18. const oldValue = this.value
  19. this.value = value
  20. this.dirty = false
  21. //执行回调函数
  22. if (this.user) {
  23. try {
  24. cb.call(this.vm, value, oldValue)
  25. } catch (e) {
  26. handleError(e, this.vm, `callback for watcher "${this.expression}"`)
  27. }
  28. } else {
  29. cb.call(this.vm, value, oldValue)
  30. }
  31. }
  32. }

该方法核心部分是执行传入的回调cb方法,即this.dep.notify(),此时递归调用render watcher的update,又回到了update方法。

2、同步处理

如果不是计算属性(本例中的user watcher,render watcher),并设置了同步处理,则调用run方法。如:

  1. run () {
  2. if (this.active) {
  3. this.getAndInvoke(this.cb)
  4. }
  5. }

run方法调用getAndInvoke,该方法核心部分是:

(1)执行get方法获取value,对于render watcher ,执行updateComponent方法,重新生成Vnode,并patch,实现dom的更新(下一章节将详细说明)。

(2)回调cb方法,如本例中的msg的watch表达式(注意:render watcher的cb是noon)

3、异步处理

对于非同步,则调用queueWatcher将watcher压入堆栈中。在将这个方法之前,我们先看如果更新queue中的watcher,即flushSchedulerQueue方法,在src/core/observer/scheduler.js中

  1. function flushSchedulerQueue () {
  2. //1、设置标识位flush为true,标识正在刷新中
  3. flushing = true
  4. let watcher, id
  5. // Sort queue before flush.
  6. // This ensures that:
  7. // 1. Components are updated from parent to child. (because parent is always
  8. // created before the child)
  9. // 2. A component's user watchers are run before its render watcher (because
  10. // user watchers are created before the render watcher)
  11. // 3. If a component is destroyed during a parent component's watcher run,
  12. // its watchers can be skipped.
  13. //2、将queue数组从小到大排序
  14. queue.sort((a, b) => a.id - b.id)
  15. // do not cache length because more watchers might be pushed
  16. // as we run existing watchers
  17. //3、循环队栈queue,执行watcher.run方法,实现更新。
  18. for (index = 0; index < queue.length; index++) {
  19. watcher = queue[index]
  20. if (watcher.before) {
  21. watcher.before()
  22. }
  23. id = watcher.id
  24. has[id] = null
  25. watcher.run()
  26. // in dev build, check and stop circular updates.
  27. if (process.env.NODE_ENV !== 'production' && has[id] != null) {
  28. circular[id] = (circular[id] || 0) + 1
  29. if (circular[id] > MAX_UPDATE_COUNT) {
  30. warn(
  31. 'You may have an infinite update loop ' + (
  32. watcher.user
  33. ? `in watcher with expression "${watcher.expression}"`
  34. : `in a component render function.`
  35. ),
  36. watcher.vm
  37. )
  38. break
  39. }
  40. }
  41. }
  42. // keep copies of post queues before resetting state
  43. const activatedQueue = activatedChildren.slice()
  44. const updatedQueue = queue.slice()
  45. //4、重置相关的状态
  46. resetSchedulerState()
  47. // call component updated and activated hooks
  48. callActivatedHooks(activatedQueue)
  49. callUpdatedHooks(updatedQueue)
  50. // devtool hook
  51. /* istanbul ignore if */
  52. if (devtools && config.devtools) {
  53. devtools.emit('flush')
  54. }
  55. }

我们看下核心的步骤:

(1)设置标识位flush,表示queue正在处理中。

(2)根据queue中的watcher的id,从小到大进行排序(也就是创建的先后)

(3)循环queque中的watcher,执行run方法,实现更新。

(4)queue中的执行完成后,则重置相关的状态,包括flush,wait等,等待下一次执行。

整个处理过程还是比较清晰的,这里注意一点,在第3步中,queue是可能动态变化的。

现在回过头来,继续看queueWatcher,是如何将watcher加入到queue中,又是如何触发flushSchedulerQueue执行的。

  1. export function queueWatcher (watcher: Watcher) {
  2. const id = watcher.id
  3. if (has[id] == null) {//对于同一个watcher,不会重复加入到queue,避免多次触发
  4. has[id] = true
  5. //1、将watcher加入队列中
  6. if (!flushing) {//尚未刷新,则加入队栈,待执行
  7. queue.push(watcher)
  8. } else {//2、正在刷新中,则动态的插入到到对应位置。
  9. // if already flushing, splice the watcher based on its id
  10. // if already past its id, it will be run next immediately.
  11. let i = queue.length - 1
  12. //从后往前,查找对应位置
  13. while (i > index && queue[i].id > watcher.id) {
  14. i--
  15. }
  16. queue.splice(i + 1, 0, watcher)
  17. }
  18. // queue the flush
  19. //3、通过nexttick执行queue中的watcher
  20. if (!waiting) {
  21. waiting = true
  22. nextTick(flushSchedulerQueue)
  23. }
  24. }
  25. }

将watcher加入queue队栈中,分两种情况,

(1) flushSchedulerQueue未执行,则将watcher加入到queue即可,待下一次执行。

(2)flushSchedulerQueue执行中,则从后往前查找对应的位置,然后插入到queue。

(3)通过nextTick,调用flushSchedulerQueue,实现queue中watcher的更新。vue的DOM是异步更新的,nextTick确保了在DOM更新后再执行,在这里可以认为下一个事件循环的"tick"。nextTick机制实现了"批量"的更新,效率更高。

这里要注意,对于同一个watcher,不能重复的加入到queue中,避免多次触发。

六、总结

本章节的逻辑还是比较复杂。我们将各个方法间的调用关系总结下,便于大家理解。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/248247
推荐阅读
相关标签
  

闽ICP备14008679号