# 渲染函数的观察者

理解数据响应系统中另一个很重要的部分,即 Watcher ,我们知道正是由于 Watcher 对所观察字段的求值才触发了字段的 get,从而才有了收集到该观察者的机会

//通过 mountComponent 函数去真正的挂载组件,在src/core/instance/lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {//渲染函数是否存在
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn('something',          
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  callHook(vm, 'beforeMount')
//定义并初始化 updateComponent 函数,这个函数将用作创建 Watcher 实例时传递给 Watcher 构造函数的第二个参数
  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    //统计了 vm._render() 函数以及 vm._update() 函数的运行性能
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    //updateComponent 把渲染函数生成的虚拟DOM渲染成真正的DOM,其实在 vm._update 内部是通过虚拟DOM的补丁算法(patch)来完成的
    //vm._render 函数的作用是调用 vm.$options.render 函数并返回生成的虚拟节点(vnode)
    // vm._update 函数的作用是把 vm._render 函数生成的虚拟节点渲染成真正的 DOM
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
  hydrating = false
//上面的代码中 Watcher 观察者实例将对 updateComponent 函数求值,我们知道 updateComponent 函数的执行会间接触发渲染函数(vm.$options.render)的执行,而渲染函数的执行
//则会触发数据属性的 get 拦截器函数,从而将依赖(观察者)收集,当数据变化时将重新执行 updateComponent 函数,这就完成了重新渲染
  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

# 初识 Watcher

export default class Watcher {
//expOrFn观察的表达式,被观察的表达式的值变化时的回调函数 cb,一些传递给当前观察者对象的选项 options
// 以及一个布尔值 isRenderWatcher 用来标识该观察者实例是否是渲染函数的观察者
//Watcher 的原理是通过对“被观测目标”的求值,触发数据属性的 get 拦截器函数从而收集依赖
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    //是否是渲染函数的观察者,只有在 mountComponent 函数中创建渲染函数观察者时这个参数为真
    if (isRenderWatcher) {
      //组件实例的 _watcher 属性的值引用着该组件的渲染函数观察者
      vm._watcher = this
    }
    vm._watchers.push(this)
    // 是否传递了 options 参数
    if (options) {
      this.deep = !!options.deep//深度观测
      this.user = !!options.user //标识当前观察者实例对象是 开发者定义的 还是 内部定义的
      this.lazy = !!options.lazy 
      this.sync = !!options.sync //观察者当数据变化时是否同步求值并执行回调
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching 标识
    this.active = true //识着该观察者实例对象是否是激活状态
    this.dirty = this.lazy // for lazy watchers 惰性求值。
    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
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      const bailRE = /[^\w.$]/  //不能是 字母 或 数字 或 下划线 . $
      function parsePath (path: string): any {
        if (bailRE.test(path)) {
          return
        }
        const segments = path.split('.')
      //遍历 segments 数组循环访问 path 指定的属性值。这样就触发了数据属性的 get 拦截器函数。
        return function (obj) {
          for (let i = 0; i < segments.length; i++) {
            if (!obj) return
            obj = obj[segments[i]]
          }
          return obj
        }
      }
      //Watcher 只接受简单的点(.)分隔路径
      if (!this.getter) {
        this.getter = function () {}
        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
      ? undefined
      : this.get()
  }
//求值的目的有两个,第一个是能够触发访问器属性的 get 拦截器函数,是依赖被收集的关键,第二个是能够获得被观察目标的值
  get () {
     class Dep {
  // 省略...
    }
    Dep.target = null
    const targetStack = []

   function pushTarget (_target: ?Watcher) {
      if (Dep.target) targetStack.push(Dep.target)
      Dep.target = _target
    }

   function popTarget () {
      Dep.target = targetStack.pop()
    }
    //Dep.target 属性赋值的,pushTarget 函数会将接收到的参数赋值给 Dep.target 属性
    //传递给 pushTarget 函数的参数就是调用该函数的观察者对象,所以 Dep.target 保存着一个观察者对象,这个观察者对象就是即将要收集的目标
    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
  }

  addDep (dep: Dep) {
     const id = dep.id
    if (!this.newDepIds.has(id)) {//避免收集重复依赖
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

  cleanupDeps () {
     let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    //典型的引用类型变量交换值的过程,最终的结果就是 newDepIds 属性和 newDeps 属性被清空,
    //并且在被清空之前把值分别赋给了 depIds 属性和 deps 属性,这两个属性将会用在下一次求值时避免依赖的重复收集
    //newDepIds 和 newDeps 这两个属性的值所存储的总是当次求值所收集到的 Dep 实例对象,
    //而 depIds 和 deps 这两个属性的值所存储的总是上一次求值过程中所收集到的 Dep 实例对象。
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  update () {
     if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {//创建观察者实例对象时传递的第三个选项参数中的 sync 属性的值,代表了当变化发生时是否同步更新变化
      this.run()
    } else {
      queueWatcher(this) //将当前观察者对象放到一个异步更新队列,这个队列会在调用栈被清空之后按照一定的顺序执行
    }
  }

  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)  //回调函数的作用域修改为当前 Vue 组件对象,然后传递了两个参数,分别是新值和旧值。
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  teardown () {
     if (this.active) {//为假则说明该观察者已经不处于激活状态,什么都不需要做,
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {//组件实例是否已经被销毁
        remove(this.vm._watchers, this)
      }
      //当一个属性与一个观察者建立联系之后,属性的 Dep 实例对象会收集到该观察者对象,同时观察者对象也会将该 Dep 实例对象收集,
      //这是一个双向的过程,并且一个观察者可以同时观察多个属性
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this) //将当前观察者实例对象从所有的 Dep 实例对象中移除
      }
      this.active = false
    }
  }
}

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

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

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

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
  }
}

# 异步更新队列

在watcher类中的update方法调用

let has: { [key: number]: ?true } = {} //空对象
const queue: Array<Watcher> = []
let flushing = false //更新开始时会将 flushing 变量的值设置为 true,代表着此时正在执行更新
let waiting = false
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() //flushSchedulerQueue 函数的作用之一就是用来将队列中的观察者统一执行更新的
        return
      }
      nextTick(flushSchedulerQueue)  //把nextTick 看做 setTimeout(fn, 0)
    }
  }
}

# nextTick

任务队列并非只有一个队列,总的来说我们可以将其分为 microtask(微任务) 和 macrotask(宏任务),当调用栈空闲后每次事件循环只会从 (macro)task 中读取一个任务并执行,而在同一次事件循环内会将 microtask 队列中所有的任务全部执行完毕,且要先于 (macro)task。另外 (macro)task 中两个不同的任务之间可能穿插着UI的重渲染,那么我们只需要在 microtask 中把所有在UI重渲染之前需要更新的数据全部更新,这样只需要一次重渲染就能得到最新的DOM,要优先选用 microtask 去更新数据状态而不是 (macro)task,最优的选择是使用 Promise
以下事件属于宏任务:

  • setTimeout
  • MessageChannel
  • postMessage
  • setImmediate

以下事件属于微任务:

  • Promise()
  • new MutaionObserver()
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0) //常量保存一份 callbacks 的复制
  callbacks.length = 0//清空
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

//将一个回调函数注册为 (macro)task 的方式有很多,如 setTimeout、setInterval 以及 setImmediate
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {//setImmediate 拥有比 setTimeout 更好的性能,ie支持
  macroTimerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else if (typeof MessageChannel !== 'undefined' && (//messageChannel
  isNative(MessageChannel) ||
  // PhantomJS
  MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
  const channel = new MessageChannel()
  const port = channel.port2
  channel.port1.onmessage = flushCallbacks
  macroTimerFunc = () => {
    port.postMessage(1)
  }
} else {
  /* istanbul ignore next */
  macroTimerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}


if (typeof Promise !== 'undefined' && isNative(Promise)) {//支持promise?
  const p = Promise.resolve()
  microTimerFunc = () => {
    p.then(flushCallbacks)
    
    if (isIOS) setTimeout(noop)
  }
} else {
  // fallback to macro
  microTimerFunc = macroTimerFunc
}

function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)//函数 cb 的作用域设置为 ctx
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {//直接调用 _resolve 函数,我们知道这个函数就是返回的 Promise 实例对象的 resolve 函数
      _resolve(ctx)
    }
  })
  if (!pending) {//回调队列是否处于等待刷新的状态
    pending = true
    if (useMacroTask) {
      macroTimerFunc()
    } else {
      microTimerFunc()
    }
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

# $watch和watch选项的实现

无论是 $watch 方法还是 watch 选项,他们的实现都是基于 Watcher 的封装
$watch在 src/core/instance/state.js 文件的 stateMixin 函数中

//$watch 方法允许我们观察数据对象的某个属性,当属性变化时执行回调,
 Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) { //用来在属性或函数被侦听后立即执行回调
      cb.call(vm, watcher.value)
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
  //将纯对象形式的参数规范化一下,然后再通过 $watch 方法创建观察者
  function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]//methods中同名函数作为回调
  }
  return vm.$watch(expOrFn, handler, options)//通过 $watch 方法创建观察者
}

那么watch 选项是如何初始化的,找到 initState 函数












 
 


















function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

function initWatch (vm: Component, watch: Object) {
  //对 watch 选项遍历,然后通过 createWatcher 函数创建观察者对象
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {//watch[key]可以是数组
    //使用 watch 选项时可以通过传递数组来实现创建多个观察者
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}