Vue源码分析(1)— 首次渲染做了哪些

lxf2023-04-12 21:16:01

一切技术都是以寻找问题最优解为目的的,永远不要为了学习某项技术而把问题复杂化

简介

Vue2.x 源码分析 - import Vue 到 new Vue() 实例初始化到页面渲染做了些啥

源码目录

src
├── compiler        # 编译相关 
├── core            # 核心代码 
├── platforms       # 不同平台的支持
├── server          # 服务端渲染
├── sfc             # .vue 文件解析
├── shared          # 共享代码

core

core 目录包含了 Vue.js 的核心代码,包括内置组件、全局 API 封装,Vue 实例化、观察者、虚拟 DOM、工具函数等等。

core 核心代码入口

目录:/src/core/index.js
主要处理了
// 1. 执行文件 /src/core/instance/index.js
import Vue from './instance/index'
// 2. 初始化全局API
initGlobalAPI(Vue)
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

initGlobalAPI(Vue)

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'

export default Vue

import Vue 阶段,Vue 构造函数还未实例化

Vue 构造函数

目录:/src/core/instance/index.js
// 1. 初始化 Vue 构造函数
// 2. 执行下列函数、把 Vue 作为参数传入
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // initMixin 方法中将 _init 挂载到了Vue.prototype
  // new Vue()/new VueComponent() 调用
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

initMixin(Vue)

目录:/src/core/instance/init.js
1. 挂载 _init 方法到 Vue.prototypeVue 构造函数调用、VueComponent 构造函数调用)
2.new Vue() 的时候去执行 this._init()
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    ...
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

stateMixin(Vue)

目录:/src/core/instance/state.js
1. 给 vm.$data、vm.$props 属性设置只能读取
2. vm.$data === vm._data  等于组件 data 函数里return的数据
3. vm.$props 等于组件props里的数据
4. 挂载 $set$del 方法到 Vue.prototype
5. 挂载 $watch 方法到 Vue.prototype,里边的 options.user = true 说明是创建 userWatcher 的方法(可以存在多个userWatcher)
export function stateMixin (Vue: Class<Component>) {
  const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)

  Vue.prototype.$set = set
  Vue.prototype.$delete = del

  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) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}

eventsMixin(Vue)

目录:/src/core/instance/events.js
1. 挂载 $on$once$off$emit 方法到 Vue.prototype
2. Vue 的 event Bus 是利用这些方法处理的,发布订阅模式
export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    ...
    return vm
  }

  Vue.prototype.$once = function (event: string, fn: Function): Component {
    ...
    return vm
  }

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    ...
    return vm
  }

  Vue.prototype.$emit = function (event: string): Component {
    ...
    return vm
  }
}

lifecycleMixin(Vue)

目录:/src/core/instance/lifecycle.js
1. 挂载 _update 方法到 Vue.prototype,渲染时会调用,里边会去执行 Vue 的 diff 算法
2. 挂载 $forceUpdate 方法到 Vue.prototype,走 vm._watcher.update() 渲染
3. 挂载 $destroy 方法到 Vue.prototype,销毁的时候调用,执行 beforeDestroy、destroyed 钩子
export function lifecycleMixin (Vue: Class<Component>) {
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    ...
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    ...
  }

  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

  Vue.prototype.$destroy = function () {
    ...
    callHook(vm, 'beforeDestroy')
    ...
    callHook(vm, 'destroyed')
    ...
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
}

renderMixin(Vue)

目录:/src/core/instance/render.js
1. 挂载 $nextTick 方法到 Vue.prototype,nextTick 是一个异步处理的微任务,页面更新是异步处理的,执行 $nextTick 就是把 nextTick 的回调函数放到当前事件循环中的微任务最尾端,所以回调函数中能实时拿到数据更新后的最新 dom
2. nextTick:先判断是否支持 Promise,不支持 Promise 则判断是否支持 MutationObserver,不支持 MutationObserver 则判断是否支持 setImmediate,都不支持执行 setTimeout(callback, 0)
3. 挂载 _render 方法到 Vue.prototype,渲染时会调用,里边创建了虚拟DOM(vnode)
export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  installRenderHelpers(Vue.prototype)

  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }

  Vue.prototype._render = function (): VNode {
    ...
    return vnode
  }
}

initGlobalAPI(Vue)

目录:/src/core/global-api/index.js
// 1. 处理了 Vue.use() 方法
initUse(Vue)
// 2. 处理了 Vue.mixin() 方法
initMixin(Vue)
// 3. 处理了 Vue.extend() 方法,里边声明了 VueComponent 构造函数,也就是组件实例的构造函数,用原型式继承绑定了原型链关系,VueComponent.prototype = new Vue()
initExtend(Vue)
// 4. 处理了 Vue.component()、Vue.directive()、Vue.filter(),Vue.component() 内部去调用了 Vue.extend()
initAssetRegisters(Vue)
export function initAssetRegisters (Vue: GlobalAPI) {
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}
export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

new Vue() 阶段,执行 this._init()

目录:/src/core/instance/init.js
// 1. 初始化生命周期
initLifecycle(vm)
// 2. 初始化事件
initEvents(vm)
// 3. 初始化渲染
initRender(vm)
// 4. 执行 beforeCreate 钩子
callHook(vm, 'beforeCreate')
// 5. 初始化 inject
initInjections(vm) // resolve injections before data/props
// 6. 初始化状态(数据)【props、methods、data、computed、watch】
initState(vm)
// 7. 初始化 provide
initProvide(vm) // resolve provide after data/props
// 8. 执行 created 钩子
callHook(vm, 'created')
// 9. 执行挂载,和构建方式有关,不同的构建方式有不同的挂载方式
vm.$mount(vm.$options.el)
Vue.prototype._init = function (options?: Object) {
  const vm: Component = this
  // a uid
  vm._uid = uid++

  let startTag, endTag
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    startTag = `vue-perf-start:${vm._uid}`
    endTag = `vue-perf-end:${vm._uid}`
    mark(startTag)
  }

  // a flag to avoid this being observed
  vm._isVue = true
  // merge options
  if (options && options._isComponent) {
    initInternalComponent(vm, options)
  } else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    )
  }
  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    initProxy(vm)
  } else {
    vm._renderProxy = vm
  }
  // expose real self
  vm._self = vm
  initLifecycle(vm)
  initEvents(vm)
  initRender(vm)
  callHook(vm, 'beforeCreate')
  initInjections(vm) // resolve injections before data/props
  initState(vm)
  initProvide(vm) // resolve provide after data/props
  callHook(vm, 'created')

  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    vm._name = formatComponentName(vm, false)
    mark(endTag)
    measure(`vue ${vm._name} init`, startTag, endTag)
  }

  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}

initLifecycle(vm)

目录:/src/core/instance/lifecycle.js
初始化生命周期属性
export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

initEvents(vm)

目录:/src/core/instance/events.js
处理事件监听,用发布订阅的模式进行了回调处理,这块理解的不够透彻,有没有大佬给推文指点下
export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

export function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm
  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
  target = undefined
}

export function updateListeners (
  on: Object,
  oldOn: Object,
  add: Function,
  remove: Function,
  createOnceHandler: Function,
  vm: Component
) {
  let name, def, cur, old, event
  for (name in on) {
    def = cur = on[name]
    old = oldOn[name]
    event = normalizeEvent(name)
    /* istanbul ignore if */
    if (__WEEX__ && isPlainObject(def)) {
      cur = def.handler
      event.params = def.params
    }
    if (isUndef(cur)) {
      process.env.NODE_ENV !== 'production' && warn(
        `Invalid handler for event "${event.name}": got ` + String(cur),
        vm
      )
    } else if (isUndef(old)) {
      if (isUndef(cur.fns)) {
        cur = on[name] = createFnInvoker(cur, vm)
      }
      if (isTrue(event.once)) {
        cur = on[name] = createOnceHandler(event.name, cur, event.capture)
      }
      add(event.name, cur, event.capture, event.passive, event.params)
    } else if (cur !== old) {
      old.fns = cur
      on[name] = old
    }
  }
  for (name in oldOn) {
    if (isUndef(on[name])) {
      event = normalizeEvent(name)
      remove(event.name, oldOn[name], event.capture)
    }
  }
}

initRender(vm)

目录:/src/core/instance/render.js
创建了 vm.$createElement 方法
vm.$attrs、vm.$listeners 属性响应式处理
export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  const parentData = parentVnode && parentVnode.data

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  }
}

callHook(vm, 'beforeCreate')

目录:/src/core/instance/lifecycle.js
vm.$emit('hook:' + hook) 订阅钩子
export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  popTarget()
}

initInjections(vm)

目录:/src/core/instance/inject.js
在处理 data/props 之前处理 inject 的数据并响应式处理
export function initInjections (vm: Component) {
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
            `overwritten whenever the provided component re-renders. ` +
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }
}

initState(vm)

目录:/src/core/instance/state.js
按照顺序处理了,并且验证唯一性
1. props:initProps(vm, opts.props),响应式处理
2. methods:initMethods(vm, opts.methods),里边用了 bind() 使得 this 指向 vm,所以 methods 里边的函 数不能用箭头函数去处理,箭头函数本身没有this,会导致函数偏离原来处理
3. data:initData(vm),没有data会默认一个{},会深层遍历数组和对象,并利用 Object.definedPrototype 响应式处理
4. computed:initComputed(vm, opts.computed),遍历 computed 里的计算属性,并给 vm 实例绑定 _computedWatchers 属性,会创建多个 new Watcher() 实例,lazy: true 表示是 computedWatcher(每个组件可以存在多个computedWatcher)
5. watch:initWatch(vm, opts.watch) 深层遍历 watch 去创建 new Watcher() 实例,user:true 表示是 userWatcher
export 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)
  }
}

initProvide(vm)

目录:/src/core/instance/inject.js
在处理 data/props 之后处理 provide
export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

callHook(vm, 'created')

目录:/src/core/instance/lifecycle.js
vm.$emit('hook:' + hook) 订阅钩子

vm.mount(vm.mount(vm.options.el)

render 方式运行

场景:
new Vue({
  el: '#app',
  router,
  i18n,
  store,
  render: h => h(App)
})
render => vdom => UI
目录:/src/platforms/web/entry-runtime.js
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

mountComponent(this, el, hydrating)

1. vm.$options.render 不存在时创建虚拟根节点并赋值给 vm.$options.render
2. 执行钩子 callHook(vm, 'beforeMount')
3. 创建渲染 Watcher,每个组件仅有一个渲染 Watcher,并设置了回调钩子 callHook(vm, 'beforeUpdate'),当有数据更新时,会在 scheduler.js 中执行 beforeUpdate 钩子,相应的 updated 钩子也会跟着执行,如果组件中使用了 keepAlive 组件则会循环遍历子组件去执行对应的 activated、deactivated 钩子
4. vm._render()
5. vm._update()
6. 执行钩子 callHook(vm, 'mounted') 首次渲染结束

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    ...
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    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 = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

Watcher类(观察者)

目录:/src/observer/watcher.js
三种类型 Watcher
1. computedWatcher(computed 计算属性),每个组件能有多个,不能执行异步操作,需要return
2. userWatcher(watch 监听),每个组件能有多个,能执行异步操作
3. renderWatcher(页面渲染),每个组件只能有一个
创建顺序和执行顺序都是 computedWatcher > userWatcher > renderWatcher,组件中 this._watchers 数组记录了 watcher 数据
if (options) {
  this.deep = !!options.deep
  this.user = !!options.user
  this.lazy = !!options.lazy
  this.sync = !!options.sync
  this.before = options.before
} 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
deep:值为 true userWatcher 深度监听属性
user:值为 true 用于 userWather
lazy:值为 true 用于 computedWatcher;值为 false 执行 this.get()
sync:不太理解,字面意思同步处理
before:用于触发 beforeUpdate 钩子
dirty:用于 computedWatcher 中判断是返回缓存数据还是最新数据,值为 true 获取最新数据并返回,值为 false 返回上一次获取的缓存数据
cb:userWatcher 的回调函数
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
}
computedWatcher
目录:/src/core/instance/state.js
// 怎么理解?假设当前 watcher = b
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 根据 watcher.dirty 标识来决定是获取最新数据还是上一次的缓存数据
      if (watcher.dirty) {
        watcher.evaluate()
      }
      // 这里的 Dep.target 一定不是当前 watcher b,是 vm.a 属性的处理的时候需要用到 b 这个计算属性,然后触发此处的getter,并把 b 收集到 vm.a 对应的 watcher 对应的 deps 中
      // defineReactive 中的处理也是一样的道理
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}
<template>
  <div>
    {{ test }}
  </div>
</template>

<script>
export default {
  data() {
    return {
      testValue: "is test value"
    }
  },
  computed: {
    test() {
      return this.testValue;
    }
  }
}
/**
 * 怎么理解 computedWatcher?
 * 0. 按照步骤依次执行
 * 1. 解析组件用 Object.definedPrototype 给 this.testValue 属性绑定 get set 方法做数据劫持
 * 2. 解析组件遍历 computed 创建 computedWatcher new Watcher(),并给 computed 计算属性绑定 get set 方法做数据劫持(createComputedGetter)
 * 3. 初始化渲染 renderWatcher
 * 4. 开始渲染解析模板语法 {{ test }},使用到 test 计算属性,触发 get 方法 createComputedGetter,执行计算属性的回调函数,返回 this.testValue
 * 5. 触发 this.testValue 的 get 方法,触发里边的 dep 依赖收集,当前 Dep.target 是 test 计算属性对应的 computedWatcher
 * 6. 收集依赖,test 计算属性对应的 computedWatcher 依赖 this.testValue
 */
</script>
0. 假设当前计算属性中 test 使用了 vm.testValue 的数据,并且渲染在页面上 {{ test }}
1. initComputed() 中遍历计算属性初始化 computedWatcher 实例 new Wather()
2. 初始化 this.dirty = this.lazy = truethis.lazy 为 true 不执行 this.get()
3. userDef/userDef.get 用于 Object.defineProperty 里的 get 函数,computed 可以是函数也可以是含有 getset 的对象
4. 当用到了计算属性就会执行 Object.defineProperty 里的 get 函数触发 computedGetter(),接着执行 watcher.evaluate()
5. 执行 this.get(),并且设置 Dep.target = 当前 computedWatcher
6. this.get() 里边执行了 value = this.getter.call(vm, vm),此时 this.getter 为计算属性的回调函数,执行返回了 vm.testValue,则会触发 vm.testValue 的 get 处理
7. vm.testValue 的 get 处理在 defineReactive 函数中,判断 Dep.target 是否存在进行依赖收集并存入computedWatcher.deps
8. 到此 computedWatcher 单个实例的处理和依赖收集结束
userWatcher
目录:/src/core/instance/state.js
<template></template>
<script>
export default {
  data() {
    return {
      test: "is test",
      name: "is name",
      arr: [1],
      other: [1],
    };
  },
  mounted() {
    setTimeout(() => {
      this.name = "change name";
      this.other.push(2);
      this.arr.push(2);
    });
    // 打印 watch 对象
    console.log(this.$options.watch);
  },
  methods: {
    watcherHandle(val) {
      console.log(val, "这是 watcherHandle"); // [1,2] "这是 watcherHandle"
    },
    otherHandler(val) {
      console.log(val, "这是 otherHandler"); // [1,2] "这是 otherHandler"
    }
  },
  /**
   * watch 的几种写法
   * 1. watch 属性是一个对象,immediate 在方法1上有效
   * 2. watch 属性是一个函数
   * 3. watch 属性是一个字符串(string),绑定在vm.string
   * 4. watch 属性是一个数组,里边可以是对象,接下来的处理和方法1相同
   * 5. watch 属性是一个数组,里边可以是字符串,接下来的处理和方法3相同
   */
  watch: {
    test: {
      handler(val) {
        console.log(val); // is test
      },
      immediate: true,
    },
    name: function (val) {
      console.log(val); // change name
    },
    other: "otherHandler",
    arr: [
      {
        handler(val) {
          console.log(val, "这是数组里的 handler"); // [1,2] 这是数组里的 handler
        },
      },
      "watcherHandle"
    ],
  },
};
/**
 * 怎么理解 userWatcher?这里解释 immediate 为 true 的情况
 * 0. 按照步骤依次执行
 * 1. 解析组件用 Object.definedPrototype 给 this.test 里的属性绑定 get set 方法做数据劫持
 * 2. 解析组件遍历 watch 创建 userWatcher new Watcher(),遇到 watch 里的属性是数组则深层遍历
 * 3. new Watcher() 解析 this.getter = parsePath(expOrFn) 返回一个闭包函数 a
 * 4. watcher 中执行 this.get() 去相应的执行 value = this.getter.call(vm, vm)
 * 5. watcher this.getter.call(vm, vm) 执行本质上是执行 parsePath(expOrFn) 返回的闭包函数 a
 * 6. 闭包函数 a() 里面使用 this.test
 * 7. 触发 this.test 的 get 方法,触发里边的 dep 依赖收集,当前 Dep.target 是 test 计算属性对应的 computedWatcher
 * 8. 收集依赖,test watcher对应的 userWatcher 依赖 this.test
 * 9. options.immediate 为 true 的情况下 触发 invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
 * 10. invokeWithErrorHandling 里边触发了 test watch 的 handler 处理,并执行后续的程序
 */
</script>

<style>
</style>
// /src/core/instance/state.js
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) {
    const info = `callback for immediate watcher "${watcher.expression}"`
    pushTarget()
    invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
    popTarget()
  }
  return function unwatchFn () {
    watcher.teardown()
  }
}
// /src/core/observer/watcher.js
if (typeof expOrFn === 'function') {
  this.getter = expOrFn
} else {
  this.getter = parsePath(expOrFn)
  if (!this.getter) {
    this.getter = noop
    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
    )
  }
}
// /src/core/util/lang.js
export function parsePath (path: string): any {
  if (bailRE.test(path)) {
    return
  }
  const segments = path.split('.')
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      // obj[segments[i]] 等价于去访问 vm 实例的属性
      obj = obj[segments[i]]
    }
    return obj
  }
}
renderWatcher
目录:/src/core/instance/lifecycle.js
1. 这里讲述页面首次渲染,不包含数据更新
2. new Watcher() 后执行 this.get() 将 Dep.target 设置为当前 renderWatcher
3. 执行 this.getter.call(vm, vm),等价于去执行了 vm._update(vm._render(), hydrating)
4. vm._render() 生成虚拟DOM,使用到数据
5. 触发数据中对应的 get,执行依赖收集
6. vm._update() 渲染到真实DOM上,执行 mounted 钩子,挂载结束
7. 有个地方不是很确认,第一次渲染在没有数据更新的情况下到底是同步还是异步,源码中 renderWatcher 到 mounted 钩子这里的操作一直是同步的,是在 vm._update() 中把虚拟DOM挂载到真实DOM上的;但是数据更新是会去触发 dep.notify(),然后通过 nextTick 异步去执行 watcher.run() 再回到 watcher.get() 重新去更新依赖。
let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    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 = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)

总结

第一次写文章,源码中有很多不理解的地方,希望大佬们多多指导!!!