Vue源码解读(十三)---派发更新

通过上⼀节分析我们了解了响应式数据依赖收集过程,收集的⽬的就是为了当我们修改数据的时候, 可以对相关的依赖派发更新,那么这⼀节我们来详细分析这个过程。

我们先来回顾⼀下 setter 部分的逻辑,对应路径:src/core/observe/index

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

setter 的逻辑有 2 个关键的点,⼀个是 childOb = !shallow && observe(newVal) ,如果 shallow(浅薄) 为 false 的情况,会对新设置的值变成⼀个响应式对象,也就是说初始化的时候数据都监听上了,重新赋值为一个对象,对象里面的数据我们同样需要把他变成响应式数据

app.message
"Hello Vue!"
app.message = {name:122}
app.message.name = 333

另⼀个是 dep.notify() ,通知所有的订阅者,这是本节的关键,接下来我会带⼤家完整的分析整个派发更新的过程。

过程分析

当我们在组件中对响应的数据做了修改,就会触发 setter 的逻辑,最后调⽤ dep.notify() ⽅法, 它 是 Dep 的⼀个实例⽅法,定义在 src/core/observer/dep.js 中:

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

这⾥的逻辑⾮常简单,遍历所有的 subs ,也就是 Watcher 的实例数组,然后调⽤每⼀个 watcher 的 update ⽅法,它的定义在 src/core/observer/watcher.js 中:

update () {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true
  } else if (this.sync) {
    this.run()
  } else {
    queueWatcher(this)
  }
}

组件数据更新的场景,会⾛到最后⼀个 queueWatcher(this) 的逻 辑, queueWatcher 的定义在 src/core/observer/scheduler.js 中:

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

这⾥引⼊了⼀个队列的概念,这也是 Vue 在做派发更新的时候的⼀个优化的点,它并不会每次数据改 变都触发 watcher 的回调,⽽是把这些 watcher 先添加到⼀个队列⾥,然后在 nextTick 后执 ⾏ flushSchedulerQueue 。 这⾥有⼏个细节要注意⼀下,⾸先⽤ has 对象保证同⼀个 Watcher 只添加⼀次;接着对 flushing 的判断,else 部分的逻辑稍后我会讲;最后通过 wating 保证对 nextTick(flushSchedulerQueue) 的调⽤逻辑只有⼀次,另外 nextTick 的实现我之后会抽⼀⼩节 专门去讲,⽬前就可以理解它是在下⼀个 tick,也就是异步的去执⾏ flushSchedulerQueue 

接下来我们来看 flushSchedulerQueue 的实现,它的定义在 src/core/observer/scheduler.js 中。在对 queue 排序后,接着就是要对它做遍历,拿到对应的 watcher ,执⾏ watcher.run() 。

/**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  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) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

run 函数实际上就是执⾏ this.get()获取当前的值,那么对于渲染 watcher ⽽⾔,它在执⾏ this.get() ⽅法求值的时候,会执⾏ getter ⽅法:

src/core/instance/lifecycle

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

所以这就是当我们去修改组件相关的响应式数据的时候,会触发组件重新渲染的原因,接着就会重新 执⾏ patch 的过程,但它和⾸次渲染有所不同

总结

通过这⼀节的分析,我们对 Vue 数据修改派发更新的过程也有了认识,实际上就是当数据发⽣变化的 时候,触发 setter 逻辑,把在依赖过程中订阅的的所有观察者,也就是 watcher ,都触发它们的 update 过程,这个过程⼜利⽤了队列做了进⼀步优化,在 nextTick 后执⾏所有 watcher 的 run ,最后执⾏它们的回调函数。 

你可能感兴趣的:(vue)